Documentation

Build something
the runtime can use.

Start with one small package. Give it data, an interface and clear entry points for people, agents and other apps. Lógos discovers the rest.

Lógos 2.0.0 · Contracts generated 2026-08-21 · Package contract v1

FolderBehaviorSurfacesLive

Lógos / The development library

A small kernel for software that can change while it runs.

Lógos is the independent runtime underneath ThinkIac. Point it at a packages/ folder and it discovers routes, tools, events, data models and screens. You can use that kernel to build your own product without adopting the ThinkIac interface.

01
Kernel

The event bus, package registry and stabilizer stay resident and coordinate the runtime.

02
Modules

HTTP, tools, views, data and processes install capabilities around the kernel and can be extended.

03
Packages

Your application code declares what it offers. Lógos wires those declarations into the active modules.

Start a standalone Lógos runtime
// server.js
import { bootstrap } from 'logos'

await bootstrap({
  port: 5040,
  http: { public: 'public' },
  views: { index: { package: 'notes', page: 'Index' } }
})

// Drop a folder into packages/ and Lógos discovers its surfaces.

Start here / Architecture target

Lógos becomes only the launcher.

The approved direction is to remove every resident capability from Lógos. The executable will only resolve a workspace boot manifest, start the declared host module and return its exit status. It will not contain a router, WebSocket server, event bus, database, vault, stabilizer or isolated runner.

External hostA normal module package composes the runtime for a product such as ThinkIac.
External capabilitiesRouter, WebSocket, events, registry, tools, data, views, isolation and rollback live under packages/modules/.
No hidden defaultsA bare Lógos installation intentionally does nothing until a host is declared.
Current statusThis is a migration target. The current release still ships these capabilities internally.
Isolation is a module too.The isolated runner is not privileged inside Lógos. A host composes it when a package declares isolated: true, just as it composes the router or WebSocket capability.
Inspect the active runtime reference

Start here / Quick start

Your first package in three files.

Create packages/notes. The folder name becomes the URL namespace, while the package file names the capability people will see.

packages/notes/package.json identity and visual slotsroutes.js HTTP entry pointsviews/Index.svelte the main screen
packages/notes/package.json
{
  "name": "notes",
  "version": "1.0.0",
  "description": "Private notes with an agent-friendly API.",
  "type": "module",
  "manifest": {
    "name": "Notes",
    "icon": "assets/icon.svg",
    "iconDark": "assets/icon-dark.svg"
  }
}

Save the route below. Lógos finds its comment, validates the package and publishes it at /notes/health.

packages/notes/routes.js
// packages/notes/routes.js
export function health() {
  /**
   * @register_router health
   * @method GET
   */
  return { data: { ready: true } }
}

// GET /notes/health
// { "data": { "ready": true } }
The comment is the contract.You do not maintain a separate route registry. The function and its declaration stay together.

Start here / Package anatomy

Identity is declared. Capabilities are discovered.

package.json holds the package identity, icon and places where its interface may appear. Tools, hooks and routes are read from the code and can be exported as a generated logos.json snapshot when another system needs to inspect them.

package.json

What you choose

Name, version, icon, menu, InputBar, widget and runtime policies.

logos.json

What Lógos finds

Tools, parameter schemas, hooks, routes and commands. Generate it; do not hand-edit it.

Icon rules

Put the files inside the package. Use icon for the light/default asset and iconDark when the dark surface needs a different one. SVG stays sharp; PNG, JPG and WebP are also accepted. If no icon exists, Aurora falls back to the package initial or Sigma.

icon / iconDarkTheme-aware app artworkpaddingSpace around artwork in its tilenoBorderRemove the tile borderhomeShortcutAllow a native home-screen shortcut

Start here / Runtime configuration

Make lifecycle policy explicit in the manifest.

The package manifest accepts a runtime object for its ordinary contract. Critical data isolation is declared directly on package.json: isolated: true and, when required, database: "private". Package-local .env values are settings, not a vault; never commit credentials or encryption keys.

runtime.contractVersionEnables the versioned authority contract.
runtime.modeChooses the mediated enforcement mode.
isolatedRuns in the Lógos core/isolated child-process boundary.
database: "private"Owner-only encrypted tenant; valid only with isolated: true.
Canonical package manifest contract

Build packages / Tools

Describe the decision, not the implementation.

A tool is a named function an agent can call. Its description explains when it helps; each parameter explains exactly what a valid input means. Lógos turns those declarations into the schema shown to agents and visual builders.

packages/notes/tools.js
// packages/notes/tools.js
import { saveNote } from './lib/notes.js'

export async function createNote({ title, body, visibility = 'private', tags = [] }) {
  /**
   * @register_tool createNote
   * @description Create a note in the private notebook
   * @reach auto
   * @param {string} title - Short, descriptive title
   * @param {string} body - Full note content
   * @param {string?} visibility - Who can read it @enum private,shared
   * @param {string[]} tags[] - Labels used to find the note later
   */
  return saveNote({ title, body, visibility, tags })
}

export function assignNote({ note, reviewer }) {
  /**
   * @register_tool assignNote
   * @description Assign a note to a reviewer
   * @param {string} note.id - Existing note ID
   * @param {string} note.title - Title shown to the reviewer
   * @param {string?} reviewer - Registered reviewer @enumSource tool:team/listMembers#id
   */
  return { ok: true, noteId: note.id, reviewer }
}
{string} titleRequired text{number?} limitOptional number{boolean} archivedTrue or false{string[]} tags[]List of text values{string} note.idField inside an object@enum draft,publishedClosed set of choices@enumSource tool:team/list#idChoices loaded at runtime
Optional means optional

Use ? in the type and give the function a useful default.

Use choices when choices exist

@enum prevents invented values. @enumSource keeps a changing list live.

Control discovery

@reach auto is normal, always is always visible, and manual is only called explicitly.

Build packages / Commands

Give operators a repeatable way to act.

A command is a package function exposed to the local Lógos CLI. Use it for migrations, imports, exports and maintenance jobs that should be named, validated and testable. It receives the same declared parameters as a tool, but it is invoked deliberately by an operator instead of being offered to an agent.

packages/notes/commands.js
// packages/notes/commands.js
import fs from 'node:fs/promises'
import path from 'node:path'
import { listNotes } from './lib/notes.js'

export async function exportNotes({ format = 'json', output = './notes.json' }) {
  /**
   * @register_command export
   * @description Export notes to a local file
   * @param {string?} format - Output format @enum json,csv
   * @param {string?} output - Destination path
   */
  const notes = listNotes()
  const content = format === 'csv'
    ? ['title,body', ...notes.map(note => JSON.stringify(note.title) + ',' + JSON.stringify(note.body))].join('\n')
    : JSON.stringify(notes, null, 2)
  const destination = path.resolve(output)
  await fs.mkdir(path.dirname(destination), { recursive: true })
  await fs.writeFile(destination, content, 'utf8')
  return { output: destination, records: notes.length }
}

// Discover: logos list
// Run:      logos notes:export --format csv --output ./notes.csv
// JSON:     logos notes:export --params '{"format":"json"}'
// Validate and swap now: logos commit notes
logos listDiscover installed packages and their commands.
logos statusInspect the lifecycle state of every package.
logos manifest notesPrint the capabilities discovered from source.
logos commit notesClose the edit transaction and validate it now.
Local by design.The Auto‑Repository SSH gateway currently accepts Git operations only. It does not expose these commands, a shell, PTY, SFTP or port forwarding. Remote commands require a separate allowlisted, authenticated and audited dispatcher.

Build packages / Scriba

Give each package its own data boundary.

Add a schema and Lógos provisions a private tenant for that package. The model becomes a lowercase collection on the proxy: Note becomes db.note.

packages/notes/schema.prisma
// packages/notes/schema.prisma
model Note {
  id         String  @id @default(uuid())
  title      String
  body       String
  visibility String  @default("private")
  tags       String?
  archived   Boolean @default(false)
}
packages/notes/lib/notes.js
// packages/notes/lib/notes.js
import { getProxy } from 'logos'

const notes = () => getProxy('notes').note

export async function saveNote(input) {
  return notes().push({
    title: input.title.trim(),
    body: input.body.trim(),
    visibility: input.visibility ?? 'private',
    tags: JSON.stringify(input.tags ?? []),
    archived: false
  })
}

export function listNotes() {
  return notes().query(
    { archived: false },
    { sort: 'updatedAt DESC', limit: 50 }
  )
}

export async function archiveNote(id) {
  const note = notes()[id]
  if (!note) return null
  note.archived = true
  return note
}

export async function deleteNote(id) {
  await notes().delete(id)
  return { ok: true }
}
pushCreatequeryFilter and sort[id]Read and updatedeleteRemove
Keep the proxy behind domain functions.Routes, tools and hooks should call saveNote or listNotes. This gives every surface the same validation and behavior.

What tenant isolation means

Each schema receives a separate database path and a tenant name, which prevents accidental table collisions and lets backup or rollback target one package. This is an ownership convention, not a security sandbox: trusted code can still call getProxy('other-package'). Do not install untrusted packages in the same process when data confidentiality depends on process isolation.

Build packages / Package context

Use a runtime-bound context for mediated work.

New package code should prefer container.packageContext('notes'). Its owner is bound by Lógos, so database, event, tool, file, secret, network and process calls remain attributable to the package that made them. It is an authority boundary around runtime services, not a sandbox for arbitrary JavaScript.

A package-scoped capability context
// Inside a module or package installer
const ctx = container.packageContext('notes')

const db = ctx.db.self()
await ctx.events.publish('notes:created', { noteId: 'n_123' })
const result = await ctx.tools.invoke('ntfy.send', { message: 'Saved' })
const config = await ctx.files.readFile('data/config.json', 'utf8')

// Never stringify a secret. The value only exists in this callback.
await ctx.secrets.get('API_TOKEN').withValue(async (token) => {
  return ctx.network.fetch('https://api.example.test/status', {
    headers: { authorization: `Bearer ${token}` }
  })
})
legacy / observe / warnUndeclared mediated operations continue but are retained for reports.
strict / isolatedUndeclared mediated operations are denied before the side effect.
Redacted secretsSecret handles do not stringify; resolve them only inside withValue().
Process isolationisolated runs only in the Lógos child runner; the main process receives descriptors and invokes opaque handlers through IPC.

Build packages / Private offline vault

Encrypt the database without depending on a cloud service.

For credentials, wallet data and other owner-only records, declare a private database. Lógos derives a separate 32-byte key for each package and opens its tenant with SQLCipher. The installation stays offline: it uses the local OS credential store when available, or an explicit password unlock that remains in memory only.

packages/wallet/package.json
{
  "name": "wallet",
  "version": "1.0.0",
  "isolated": true,
  "database": "private"
}

// database: "private" requires isolated: true.
// Use ctx.db.self() inside the owner package.
// Share only minimized, validated data through its tools, routes or events.
Root keyCreated locally and stored in Keychain on macOS or libsecret through secret-tool on Linux. A host can supply an equivalent local keyring adapter.
Password fallbackAn explicit password provider derives the root key with scrypt and never writes it to disk. If no secure source can unlock the vault, startup fails closed.
Per-package keyHKDF-SHA-256 derives a distinct key for each package; SQLCipher receives only that derived key and the buffer is wiped after handoff.
At restSQLCipher mode uses a hardened KDF, memory security and secure deletion. A database file cannot be opened as plaintext SQLite or with a different key.

Authority and sharing

Only the owning package may acquire a private tenant; new package code should use ctx.db.self(). Cross-package getProxy() access is denied regardless of a declared grant. Publish a narrowly shaped tool, route or event from the owning package when another package needs a result; never share rows or encryption keys.

Process boundary.The isolated runner is active: package code is not imported by the main process. The child runs in Node permission mode and cannot use native Node modules, ambient process, global fetch or native addons. Events and the package's own database cross the boundary only through contract-checked IPC. Files, secrets, network and processes remain denied until their dedicated mediated IPC capabilities are introduced.
Review the deployment security boundaries

Build packages / Routes

Choose the route by how it behaves.

Normal routes live at /<package>/<path>. Use GET for reads, POST for creation, path parameters for one resource, @auth for protected endpoints and @upload for multipart files.

packages/notes/routes.js
// packages/notes/routes.js
import { getProxy } from 'logos'
import { listNotes, saveNote } from './lib/notes.js'

export function list(req) {
  /**
   * @register_router notes
   * @method GET
   * @offline cache
   */
  return { data: listNotes() }
}

export async function create(req) {
  /**
   * @register_router notes
   * @method POST
   * @auth
   */
  const note = await saveNote(req.body)
  return { status: 201, body: { data: note } }
}

export function findOne(req) {
  /**
   * @register_router notes/:id
   * @method GET
   */
  return { data: { id: req.params.id } }
}

export function update(req) {
  /**
   * @register_router notes/:id
   * @method PATCH
   * @auth
   */
  const note = getProxy('notes').note[req.params.id]
  if (!note) return { status: 404, body: { error: 'Note not found' } }
  if (typeof req.body.title === 'string') note.title = req.body.title.trim()
  return { data: note }
}

export async function remove(req) {
  /**
   * @register_router notes/:id
   * @method DELETE
   * @auth
   */
  await getProxy('notes').note.delete(req.params.id)
  return { data: { ok: true } }
}

export function importFile(req) {
  /**
   * @register_router import
   * @method POST
   * @upload file 1 10mb disk
   */
  return { data: { name: req.file.originalname } }
}
Return { data }Standard JSON response
Return { status, headers, body }Control status and headers
@streamingThe handler writes a file, SSE or chunks
@streamOpen a WebSocket channel
@offline cacheAllow a GET to work from cache
Root path /…Only for fixed protocol URLs

Build packages / Testing

Test plain behavior first, discovery second.

Tools, hooks and domain handlers are ordinary functions, so import and test them directly. For scanner and decorator coverage, create real named JavaScript fixtures in a temporary workspace: source comments must survive for discovery. Redirect HOME and USERPROFILE before importing modules that derive state paths, then restore globals and remove every fixture.

Package unit

Run node --test packages/your-package/test/*.test.js without network access.

Runtime suite

Run npm test for one CI pass or npm run test:watch during development.

End to end

Point registerTools, registerHooks or registerRoutes at a disposable on-disk package.

Preserve isolation.Clean temporary workspaces, restore environment variables and reset imported modules when a test changes home-directory state.

Connect & extend / Events

Use events where capabilities meet.

dispatchEvent starts a pipeline. Hooks run by priority; when one returns an object, that object becomes the payload for the next hook. This is useful for enrichment, policy and optional integrations.

A pipeline with two listeners
// packages/notes/lib/create-note.js
import { dispatchEvent, getProxy } from 'logos'

export async function handleCreateNote(input) {
  const note = await getProxy('notes').note.push(input)
  await dispatchEvent('notes:created', { note })
  return note
}

// packages/notes/hooks.js
export function enrichCreated(payload) {
  /**
   * @register_hook notes:created
   * @priority 20
   */
  return {
    ...payload,
    note: { ...payload.note, searchable: true }
  }
}

export function observeAllNotes(payload, context) {
  /**
   * @register_hook notes:*
   * @priority 90
   */
  console.log(context.matchedEvent, payload.note?.id)
}
Call a function

When one package owns the whole behavior and needs a direct result.

Dispatch an event

When other packages may observe, enrich or veto without becoming a hard dependency.

Use a wildcard hook

For observability across one namespace, such as notes:*.

One behavior, several entry points

Put the real work in a plain function. The route, agent tool and integration hook become small adapters around it.

packages/notes/index.js
// One behavior, three entry points
import { handleCreateNote } from './lib/create-note.js'

export function createFromRoute(req) {
  /**
   * @register_router notes
   * @method POST
   */
  return handleCreateNote(req.body)
}

export function createFromAgent(input) {
  /**
   * @register_tool createNote
   * @description Create a private note
   * @param {string} title - Short title
   * @param {string} body - Full note content
   */
  return handleCreateNote(input)
}

export function createFromIntegration(payload) {
  /**
   * @register_hook inbox:note-requested
   */
  return handleCreateNote(payload)
}

Runtime event catalog

These names and payloads come from the active Lógos, Aurora and Hermes contracts. Filter by system or search for the capability you want to observe.

22 events

runtime:readylogosemit

The HTTP server is listening and the boot checkpoint exists.

Payload{ port }
Listen to runtime:ready
export function handle(payload, context) {
  /** @register_hook runtime:ready */
  return payload
}
package:wiringlogosemit

A package is being connected to the installed runtime modules.

Payload{ pkg }
Listen to package:wiring
export function handle(payload, context) {
  /** @register_hook package:wiring */
  return payload
}
route:registeringlogospipeline

Immediately before a discovered route is mounted; return skip to omit it.

Payload{ pkg, route }
Listen to route:registering
export function handle(payload, context) {
  /** @register_hook route:registering */
  return payload
}
http:requestlogosemit

A request reaches any route owned by a package.

Payload{ pkg, path, method }
Listen to http:request
export function handle(payload, context) {
  /** @register_hook http:request */
  return payload
}
auth:checklogospipeline

An authenticated route asks the host to decide whether access is allowed.

Payload{ allowed, status, message }
Listen to auth:check
export function handle(payload, context) {
  /** @register_hook auth:check */
  return payload
}
tool_beforelogosemit

Just before a discovered tool starts running.

Payload{ tool, payload }
Listen to tool_before
export function handle(payload, context) {
  /** @register_hook tool_before */
  return payload
}
tool_afterlogosemit

After a discovered tool returns its result.

Payload{ tool, payload }
Listen to tool_after
export function handle(payload, context) {
  /** @register_hook tool_after */
  return payload
}
agent:runlogospipeline

A host asks the kernel agent loop to complete a tool-using run.

Payload{ prompt, tools, model, context }
Listen to agent:run
export function handle(payload, context) {
  /** @register_hook agent:run */
  return payload
}
agent:steplogosemit

Each observable step of an agent run is produced.

Payload{ type, iteration, tool, result }
Listen to agent:step
export function handle(payload, context) {
  /** @register_hook agent:step */
  return payload
}
reload:validatinglogosveto

The staged candidate passed initial checks and is about to hot swap.

Payload{ pkg, checks }
Listen to reload:validating
export function handle(payload, context) {
  /** @register_hook reload:validating */
  return payload
}
reload:swappedlogosemit

A validated package candidate became the live version.

Payload{ pkg, checks }
Listen to reload:swapped
export function handle(payload, context) {
  /** @register_hook reload:swapped */
  return payload
}
package:reload_failedlogosemit

Staging or validation failed and the live package stayed in service.

Payload{ pkg, error, checks, files, attempt, maxRetries }
Listen to package:reload_failed
export function handle(payload, context) {
  /** @register_hook package:reload_failed */
  return payload
}
views:file_changedlogosemit

A package view changed and browser update policy must be evaluated.

Payload{ pkg, file }
Listen to views:file_changed
export function handle(payload, context) {
  /** @register_hook views:file_changed */
  return payload
}
db:schema_changedlogosemit

A package data schema changed and needs snapshot and validation.

Payload{ pkg }
Listen to db:schema_changed
export function handle(payload, context) {
  /** @register_hook db:schema_changed */
  return payload
}
aurora:messageaurorapipeline

A person sends a new message into an Aurora conversation.

Payload{ message, sessionId }
Listen to aurora:message
export function handle(payload, context) {
  /** @register_hook aurora:message */
  return payload
}
agent:skills:classifiedaurorapipeline

Aurora finishes routing AGENT.md specialists for the current request.

Payload{ sessionId, selected, pinned, status, model, latencyMs }
Listen to agent:skills:classified
export function handle(payload, context) {
  /** @register_hook agent:skills:classified */
  return payload
}
agent:token:usageaurorapipeline

A model provider reports token use for a completed or streamed call.

Payload{ usage, model, provider, sessionId }
Listen to agent:token:usage
export function handle(payload, context) {
  /** @register_hook agent:token:usage */
  return payload
}
llm:callaurorapipeline

A package requests a model call from the provider pool owned by Aurora.

Payload{ system, messages, tools, model, requirements }
Listen to llm:call
export function handle(payload, context) {
  /** @register_hook llm:call */
  return payload
}
device:emithermesbridge

Runtime data must reach one connected device, or all connected devices.

Payload{ event, payload, appId? }
Listen to device:emit
export function handle(payload, context) {
  /** @register_hook device:emit */
  return payload
}
device:notifyhermesbridge

The runtime sends an interactive or visible notification through Hermes.

Payload{ message, title?, appId?, interaction?, discreteId?, hold? }
Listen to device:notify
export function handle(payload, context) {
  /** @register_hook device:notify */
  return payload
}
device:responsehermesbridge

A paired device answers a confirm or prompt interaction.

Payload{ appId, id, action, value }
Listen to device:response
export function handle(payload, context) {
  /** @register_hook device:response */
  return payload
}
device:*hermesbridge

A paired device emits an allowed custom event into its reserved namespace.

Payload{ ...payload, appId }
Listen to device:*
export function handle(payload, context) {
  /** @register_hook device:* */
  return payload
}

Connect & extend / Runtime modules

Extend the kernel through an install contract.

A module exports a named installer decorated with @register_module and declares runtime.role: "module". @phase boot installs foundations before package wiring; @phase wire attaches runtime capabilities. Repeat @after for ordering and keep mutable state in container.state(namespace), never module globals.

Override by nameA workspace module with a built-in name replaces that module after validation.
@swappable falseResource-owning replacements wait for the next boot so live handles remain valid.
ContainerProvides the bus, app, registry, workspace, config, logger and namespaced state.
Failure boundaryAn unhealthy replacement rolls back to the last good implementation.

Connect & extend / Process modules

Bridge another language without hiding the process boundary.

Add @runtime python, deno, ruby or bun to a module. Lógos starts it as a child and exchanges one NDJSON message per stdio line. Requests carry IDs, event names and payloads; responses correlate by ID. Declared @events patterns limit forwarded bus traffic.

stdout

Protocol only: valid single-line JSON messages. Send diagnostics to stderr.

Failure

proc:crashed records the exit and the bridge restarts the process.

Recovery

Pending calls fail explicitly; callers choose retry or compensation rather than assuming success.

Connect & extend / Middleware & predicates

Compose policy around handlers.

@register_predicate names a boolean decision over request context. @register_middleware wraps routes, tools and hooks in its package and may select a predicate with @predicate. Put authentication composition, authorization, audit context and rate limiting here when the policy spans multiple entry points.

Keep domain checks in the domain.Middleware gates entry. Sensitive operations must still verify ownership and invariants where the state changes.

Connect & extend / Agent Runtime

Run bounded tool loops with observable results.

runAgentTask() executes one tool-calling task. Restrict exposure with toolNames, bound tool iterations with maxSteps, and validate structured final output with outputSchema. Each transition emits an agent:step trace event.

One agent task with structured output
import { runAgentTask } from 'logos'

const result = await runAgentTask({
  system: 'You are a financial analyst.',
  goal: 'Calculate this month balance.',
  toolNames: ['wallet_summary'],
  maxSteps: 6,
  outputSchema: {
    type: 'object',
    properties: {
      balance: { type: 'number' },
      status: { type: 'string' }
    },
    required: ['balance', 'status']
  },
  maxOutputRetries: 2
})

if (!result.success) throw new Error(result.error)
console.log(result.output, result.trace)
InputSystem prompt, goal/messages, model hint, allowed tools, max steps and optional output schema.
Resultsuccess, text or parsed output, model, error when present, trace and step count.
llm:callA host-provided pipeline; the kernel does not contain a model provider.
Output correctionmaxOutputRetries only retries invalid outputSchema responses. There is no generic tool/step retry or timeout option.

Sequential chains

Use runChainTask() with the default runtime container. The lower-level runChain(container, steps, initial) form is for callers that already own a container. Step inputs accept {{initial.topic}}/{{results.step.text}} templates or a function receiving the full context. A failed step stops the chain and later steps are absent from results.

Chain with the default container
import { runChainTask } from 'logos'

const context = await runChainTask([
  {
    name: 'research',
    input: 'Research {{initial.topic}}',
    toolNames: ['web_search'],
    maxSteps: 5
  },
  {
    name: 'summary',
    input: ctx => `Summarize:\n${ctx.results.research.text}`,
    maxSteps: 3
  }
], { topic: 'quantum computing' })

console.log(context.results.summary.text)

// Low-level API: runChain(container, steps, initial)

Connect & extend / AGENT.md

Give Aurora expertise only when it is useful.

An AGENT.md is a package-owned instruction guide. Aurora reads its front matter, keeps truly universal guides pinned and asks its router which of the remaining guides match the current message. A narrow description makes that decision reliable.

packages/notes/AGENT.md
---
name: Notes guide
description: Use when a request involves notes, notebooks, tags or archiving.
language: en
always: false
event: notes:agent-context
---

You help operate the private notebook.

{#if notes.length > 0}
There are {{ notes.length }} active notes.
{#each notes as note, i}
{{ i + 1 }}. {{ note.title }}
{/each}
{:else}
The notebook is empty.
{/if}

Never expose a private note without an explicit request.
nameThe human-readable specialist name.
descriptionThe routing signal: say exactly which requests need this guide.
alwaysUse true only for rules every conversation needs; otherwise leave it false.
eventOptional live context fetched after the guide is selected.
languageMetadata for the language the guide uses.
bodyInstructions rendered into the system context only while the guide is active.

How classification works

User messageDescriptionsRelevant guidesRendered context

always: true skips relevance classification. Other guides become candidates identified by name and description; the selected files are rendered again for each prompt. The routing choice may be cached briefly, but dynamic event data is not reused.

Dynamic context hook
// packages/notes/hooks.js
export async function provideAgentContext(payload) {
  /** @register_hook notes:agent-context */
  const { sessionId, message, agent } = payload
  return {
    notes: await listNotesForSession(sessionId),
    request: message,
    agent
  }
}
Treat rendered context as prompt content.Do not return secrets. Keep the event payload an object, preserve fields needed by later hooks and let a failing guide be omitted without breaking the other guides.

Inflect / Dynamic instructions

Render current context without turning the guide into code.

Inflect runs only after an AGENT.md is selected. Expressions insert values; conditions choose guidance; loops summarize small collections; switches select a mode; includes keep reusable rules in package-local partials.

AGENT.md using every Inflect block
---
name: Notes guide
description: Use for notes, notebooks, tags, review and archiving.
event: notes:agent-context
---

{#include ./partials/safety}

{#if user.canRead}
The notebook has {{ notes.length }} active notes.
{#each notes as note, i}
{{ i + 1 }}. {{ note.title }} — {{ note.visibility }}
{/each}
{:else if user.signedIn}
The account is not allowed to read this notebook.
{:else}
Ask the person to sign in before reading notes.
{/if}

{#switch request.mode}
{#case 'summary'}Return a short summary.{/case}
{#case 'review'}Check each note for a next action.{/case}
{:default}Follow the person's explicit request.{/default}
{/switch}
{{ value }}Read a value or expression from the event context.
{#if …}Choose content; supports else-if and else.
{#each …}Iterate a list with an optional index.
{#switch …}Choose one explicit operating mode.
{#include …}Load a relative .md or .mdjs partial.
Missing valuesStay visible in the rendered text so a broken context is diagnosable.
Keep context bounded.Return the few records needed for this request, never credentials or entire private tables. Includes cannot leave the package directory and circular includes fail rendering.

Connect & extend / Hermes

Extend the event bus to a paired device.

Hermes is an authenticated live link between a Machine and its devices. Use it when the app must react to runtime data or return a structured answer. Use ntfy when the goal is simply to alert the person.

device:emit

Runtime → connected app. Live data is not queued because stale events can mislead the device.

device:notify

Runtime → person. Can ask for confirmation or text and supports an offline queue.

device:response

Device → runtime. Carries confirm, cancel or submitted text back to a hook.

device:*

Device → runtime custom events. The device cannot emit protected system namespaces.

Send data and ask for a decision
import { dispatchEvent } from 'logos'

// Live data for the app. This is not a visible notification.
await dispatchEvent('device:emit', {
  event: 'notes:sync-complete',
  payload: { changed: 3 },
  appId: 'phone_42' // optional; omit to reach all connected devices
})

// A question the person can answer from the device.
await dispatchEvent('device:notify', {
  message: 'Archive the reviewed notes?',
  interaction: 'confirm',
  appId: 'phone_42'
})
Receive device responses
export function handleDeviceAnswer(payload) {
  /** @register_hook device:response */
  const { appId, id, action, value } = payload
  if (action === 'confirm') return archiveReviewedNotes({ appId, requestId: id })
  if (action === 'submit') return saveDeviceText({ appId, value })
  return payload
}

export function handleDeviceEvent(payload) {
  /** @register_hook device:notes-opened */
  // Device-originated custom events are restricted to device:*.
  return { ...payload, observedAt: Date.now() }
}
Pairing

Trust the token

The first registration creates a device token. Every later socket must present the current token; unpairing invalidates it and closes its links.

Known boundary

First registration is open

An unused app ID can currently be claimed before an authenticated web session approves it. Design sensitive flows with that limitation in mind.

Connect & extend / Views

A screen is just another package surface.

Place Index.svelte or Home.svelte in views/ for the entry screen. Other files become named pages at /view/notes/PageName. Fetch the same routes agents and integrations use.

packages/notes/views/Index.svelte
<script>
  import { onMount } from 'svelte'

  let notes = []
  let loading = true

  onMount(async () => {
    const response = await fetch('/notes/notes')
    const json = await response.json()
    notes = json.data ?? []
    loading = false
  })
</script>

<main>
  <h1>Notes</h1>
  {#if loading}
    <p>Loading…</p>
  {:else}
    {#each notes as note}
      <article><h2>{note.title}</h2><p>{note.body}</p></article>
    {/each}
  {/if}
</main>
Design for the real container.A package view can open full-page or inside the Aurora shell. Sync think:theme, use the shared tokens and clean up browser listeners when the view closes.

Connect & extend / Advanced views

Choose how pages compile, refresh and survive offline.

Use .svx for MDSvex/Markdown pages when the optional compiler is installed. The view engine follows the complete import graph for HMR, including first-party ES module helpers. Page reload modes are always, prompt and manual; a views:hmr broadcast describes changes but does not force every page to reload.

Native widgetsDeclare package-owned view surfaces; let the engine and Aurora slots mount them.
Offline modeExplicitly choose off, cache, or offline-first plus visited/all views and reconnect behavior.
@offline cacheOnly declared GET routes use stale-while-revalidate.
LogoutCall window.LogosOffline.purge() so authenticated cached responses are removed.

Connect & extend / Dynamic UI

Contribute to Aurora without editing Aurora.

The visual manifest places package-owned UI into stable slots. Aurora discovers each contribution through the app catalog, so a package can be installed or removed without adding conditionals to the chat shell.

package.json — visual slots
{
  "manifest": {
    "name": "Notes",
    "icon": "assets/icon.svg",
    "iconDark": "assets/icon-dark.svg",
    "padding": 10,
    "homeShortcut": { "name": "My Notes" },
    "menu": {
      "id": "notes",
      "label": "Notes",
      "target": "right",
      "view": "Widget"
    },
    "inputbar": {
      "id": "new-note",
      "kind": "action",
      "label": "New note",
      "hint": "Save an idea without leaving the chat",
      "view": "QuickAdd"
    },
    "widget": {
      "lang": "notes",
      "title": "Notes",
      "view": "MessageWidget",
      "aliases": ["note"],
      "partialMin": 0
    },
    "messageActions": [{
      "id": "listen",
      "kind": "audio",
      "label": "Listen",
      "endpoint": "speak",
      "roles": ["assistant"],
      "order": 15
    }]
  }
}
menu

Sidebar panel

Opens the package view in the chosen desktop/mobile side.

inputbar

Composer control

An action opens a view or emits an event. A state changes how the next message is handled.

widget

MessageList card

A fenced block such as notes becomes a live package view inside the response.

messageActions

Action below a reply

Use the supported audio contract with a readiness endpoint and the correct media response.

Named package slots

Any host package can create an optional extension point by reading a qualified name such as email-manager.account-connect. Providers declare manifest.slots; the app catalog publishes only installed, active package contributions. An absent provider resolves to an empty list, so hosts need no install-time dependency or fallback branch.

Named slot contribution
// Provider package.json
{
  "name": "gmail-bridge",
  "manifest": {
    "slots": {
      "email-manager.account-connect": {
        "id": "google",
        "view": "Connect",
        "href": "/gmail-bridge/oauth/start",
        "label": "Connect Google",
        "order": 100,
        "contract": 1
      }
    }
  }
}

// Host: GET /aura-system/apps/list, collect app.slots,
// then select slot === "email-manager.account-connect".
// No provider installed means an empty list.
Identity and overrides are deterministic.The key is slot + id. Contributions are ordered by order; if the identity repeats, the last active package wins and the resolved record names the package it overrides. A slot controls discovery and presentation, never backend authorization.

Build a MessageList widget

The source block arrives in the URL fragment. Decode it, render the compact result, report the content height and use the UI bus to open the full app.

packages/notes/views/MessageWidget.svelte
// packages/notes/views/MessageWidget.svelte
<script>
  import { onMount, tick } from 'svelte'
  let source = ''

  function post(type, detail) {
    parent.postMessage({ source: 'aurora', type, detail }, '*')
  }

  async function reportHeight() {
    await tick()
    post('widget:height', document.documentElement.scrollHeight)
  }

  onMount(() => {
    const params = new URLSearchParams(location.hash.slice(1))
    const encoded = params.get('data')
    const payload = encoded
      ? JSON.parse(decodeURIComponent(escape(atob(encoded))))
      : {}
    source = payload.source ?? ''
    reportHeight()
  })
</script>

<button on:click={() => post('open:app', '/view/notes/Index')}>
  Open notes from “{source}”
</button>

Send an InputBar or widget event to the backend

Events starting with logos: cross the chat bridge. Other UI events, such as open:app and open:iframe, stay in the shell.

UI bus → Lógos hook
// A package view runs in an iframe and talks to Aurora with postMessage.
parent.postMessage({
  source: 'aurora',
  type: 'logos:notes:refresh',
  detail: { noteId: 'note_42' }
}, '*')

// The backend receives the same event through a normal hook.
export function refreshNote(payload) {
  /**
   * @register_hook logos:notes:refresh
   */
  return { ok: true, noteId: payload.noteId }
}

ThinkIac platform / Standards

Use these conventions when your package lives in ThinkIac.

Lógos runs standalone and does not require these visual conventions. ThinkIac packages do: read think:theme, apply data-theme, use the shared --tk-* tokens, avoid hard-coded colors and make full-screen views 100dvh. Use inline SVG icons rather than emoji, flat surfaces, restrained opacity/transform motion and the shared radius tokens.

ThemeDark by default; keep a live storage listener when a fullscreen view sits outside the shell.
SurfacesUse manifests, widgets, sidebar slots and UI bus contracts instead of editing the shell.
AuthorityUI presence never grants backend authority; enforce ownership where data changes.
LifecycleDeclare package behavior, test browser globals under SSR, and let the Stabilizer validate changes.

Package collaboration

Depend on a capability, not another package's internals.

Packages can collaborate through events, tools or HTTP routes. Events are the loosest contract: a provider may be installed later, multiple packages can observe the request, and the caller can require an explicit handled result. Direct imports and cross-tenant proxies create a hard dependency and should be reserved for packages deployed and versioned together.

A replaceable package capability
// Prefer a capability contract over another package's files.
import { dispatchEvent } from 'logos'

export async function assignReviewer(noteId) {
  const result = await dispatchEvent('team:reviewer-requested', {
    handled: false,
    noteId
  })
  if (!result.handled) throw new Error('No reviewer provider is installed')
  return result.reviewer
}

// Direct data access is technically possible, but it couples trust and schema:
// getProxy('other-package').record
// Package tenants are namespaces, not a security sandbox.
Event

Optional provider, policy pipeline, observation or veto.

Tool

Agent-facing capability with a generated input contract; call it programmatically only when that coupling is intentional.

Route

Network boundary, authentication, independent deployment or external consumer.

Import

Shared code released as one unit. Declare the dependency in package.json.

Structural dependencies

Use logosDependencies only when a named Lógos package must exist for yours to work. Append ? to make a provider optional. Required dependencies that cannot resolve, validate, install, or escape a cycle leave the consumer blocked; it is not wired until the graph is healthy. The host chooses the registry, stages packages and verifies npm dist.integrity before they enter the workspace.

package.json — required and optional package dependencies
{
  "name": "notes",
  "logosDependencies": {
    "@thinkiac/embeddings": "1.4.0",
    "@thinkiac/ntfy?": "^1.0.0"
  }
}

// Required dependency unavailable or cyclic → package state: blocked
// Optional dependency unavailable → package can still start

Authorization & trust

@auth authenticates a token. Your policy authorizes the action.

A protected route expects a Bearer token from the package environment or the global runtime. By itself, Lógos does not enforce roles, resource ownership or a distinction between a human and an agent. Add that policy through the auth:check pipeline, or keep it in a shared domain handler called after authentication.

Role-aware auth:check policy
// packages/notes/hooks.js
export async function authorizeNotes(verdict, context) {
  /**
   * @register_hook auth:check
   * @priority 20
   */
  if (context.pkg !== 'notes') return verdict

  const session = await verifySession(context.req)
  if (!session) return { allowed: false, status: 401, message: 'Sign in required' }
  if (!session.roles.includes('editor')) {
    return { allowed: false, status: 403, message: 'Editor role required' }
  }
  context.req.user = session.user
  return { allowed: true, status: 200 }
}
Use the right status.Return 401 when identity is missing or invalid, 403 when a known identity lacks permission, and verify ownership again inside sensitive domain operations.

Operate safely / Distribution & authority

Deliver bytes first. Grant authority separately.

ThinkIac has two complementary distribution planes. The Docker Registry delivers an immutable runtime image to each Machine, avoiding builds on small VPS hosts. Verdaccio, or another host-selected npm-compatible registry, delivers individual Lógos packages so a capability can change without rebuilding the tenant image. Image digests and npm dist.integrity prove which artifact arrived; neither one grants that artifact new runtime authority.

Docker RegistryBuild once, pull by digest, pass health gates and promote progressively across the fleet.
Verdaccio / npmResolve logosDependencies, stage the tarball and verify dist.integrity before it enters the workspace.
LógosValidate, hash and enforce the effective package contract while the previous approved contract stays live.
ThinkIacOwn identity, first-install acceptance, durable approval and the user-facing decision flow.

Optional event-call contract

A package opts in with the top-level $ field. A local call such as event:created becomes agenda::event:created. A qualified call such as agent::llm:call must appear in the effective contract. "$": "*" requests root event access for a host-trusted package; it does not approve itself.

package.json — contextual event calls
// agenda/package.json — opt in to contextual event calls
{
  "name": "agenda",
  "$": ["agent::llm:call", "ntfy::notification:send"]
}

// Local: normalized to agenda::event:created
await $("event:created", { id: "event_42" })

// External: must be in the effective, approved contract
await $("ntfy::notification:send", { message: "Starting soon" })

// A host-trusted package may request broad event access.
// This requests authority; it never approves itself.
{
  "name": "aurora",
  "$": "*"
}
Compatibility is explicit.Without $, existing packages keep legacy behavior. Today the optional list governs contextual $() and PackageContext.events.call(); dispatchEvent() remains legacy even when a package opts in.

Edits cannot silently expand access

The first installation accepts the exact contract presented by ThinkIac. Later, Lógos hashes the candidate and compares it with the effective contract already running. Reductions can proceed; expansions stay inactive. ThinkIac asks for approval using the exact diff, then returns a grant bound to the package, previous hash, next hash and that diff. A changed candidate needs a new decision.

Stage artifactVerify integrityDiff contractUser approvalActivate

If a contextual call is not approved, Lógos throws EVENT_PERMISSION_REQUIRED before running it. Lógos detects and enforces; ThinkIac presents deny, one-time or durable choices and persists the result. This boundary controls mediated operations, not arbitrary Node.js code in the same process—untrusted packages still require process or container isolation.

Operate safely / Stabilizer

Save first. Become live after validation.

When package code changes, Lógos prepares a candidate on a ghost router beside the running version. It checks that routes mount, declared tools load, required tools exist, smoke routes return successfully and no reload:validating hook vetoes the change. Only then does one atomic router assignment make it live.

LiveStageValidateSwap

If the candidate fails, the working package keeps serving and package:reload_failed records the error, checks and changed files. Every stable state receives a checkpoint; repeated failure restores the package and its database snapshot together. Tests are not run automatically unless you expose them as a smoke route or enforce them with a reload:validating hook.

Operations

Observe the bus, then enforce the limits your product needs.

Use http:request, tool_before, tool_after, agent:step and reload events for structured logs and traces. The observability package persists execution history without changing the package being observed.

Minimal tracing hooks
// Observe without changing the package being observed.
export function traceTools(payload) {
  /** @register_hook tool_after */
  console.log(JSON.stringify({
    tool: payload.tool,
    sessionId: payload.sessionId,
    result: payload.payload
  }))
  return payload
}

export function reportReloadFailure(payload) {
  /** @register_hook package:reload_failed */
  console.error(payload.pkg, payload.error, payload.checks)
  return payload
}
Rate limiting is an application policy.The kernel does not impose one universal quota for routes, tools or model usage. Put rate limiting at the edge or in package middleware, use billing/provider limits for model calls, and emit the decision so operators can audit throttling.

Data evolution

Change shape in reversible steps.

A schema.prisma edit is validated before the candidate runs and the tenant database is snapshotted before the swap. That protects rollback, but it does not invent business migrations or safely backfill required values for you.

Expand, backfill, verify, contract
import { getProxy } from 'logos'

// 1. Back up the tenant data before changing schema.prisma.
// 2. Add nullable/defaulted fields before making them required.
// 3. Save the schema; Lógos validates it and snapshots .database.
// 4. Backfill old records with a one-time command.
// 5. Tighten the schema only after the backfill is verified.

export async function backfillVisibility() {
  /**
   * @register_command migrate:visibility
   * @description Backfill visibility on records created before version 1.1
   */
  const notes = getProxy('notes').note.query({ visibility: { $null: true } })
  for (const note of notes) note.visibility = 'private'
  return { changed: notes.length }
}

For a failed candidate, inspect package:reload_failed; for repeated failures, inspect package:rolled_back. Keep independent backups outside the Machine for disaster recovery—the Stabilizer checkpoint is operational recovery, not a substitute for backup retention.

API & troubleshooting

Start from the small public surface.

Common Lógos exports
import {
  bootstrap,        // start a runtime around packages/
  dispatchEvent,    // run an event pipeline
  getProxy,         // access a named Scriba tenant
  command,          // run a discovered package command
  broadcast,        // send an event to connected browser sessions
  runAgentTask,     // run the host-provided agent loop
  generateManifest, // inspect discovered capabilities
  requestReboot     // calculate impact before a full restart
} from 'logos'
Command not foundRun logos list; confirm the exported function is named and its decorator is inside or immediately above it.
Route stays on the old versionInspect package:reload_failed and the stability checks; the rejected candidate never replaced live code.
Database is unavailableConfirm Scriba is installed, the package has a valid schema and the tenant was wired during boot.
Hook seems ignoredCheck the exact event namespace, wildcard and priority; remember that a pipeline return replaces the next payload.

Lógos capability manifests describe the active package contract. Pin compatible versions of the kernel and packages, read release notes before upgrades, and treat removed exports, decorator behavior or payload changes as breaking changes.

Operate safely / Stabilizer operations

Control when an edit transaction becomes live.

The Stabilizer groups file changes until quiescence, then stages and validates one candidate. Commit immediately with the runtime_commit tool, the reload:commit pipeline or a package .commit marker. A new valid package is wired live and emits package:added; it does not require a process restart.

stopPackage(name)Drops the package handlers and emits package:stopped.
startPackage(name)Stages, validates and restores a stopped package.
runtime_reboot_impactInspect affected work before requesting a full process restart.
Restart requiredNon-swappable modules, process bootstrap changes and host-level settings only.
Production hot reload is opt-in.Enable the watcher and transaction policy deliberately in production; keep health checks, rollback checkpoints and operator visibility active.

Operate safely / HTTP reference

Read every endpoint as a complete contract.

GET/_logos/offlineNo auth · no body · computed offline policy · 200 JSON; 500 when policy assembly fails.
GET/logos-sw.jsNo auth · Service Worker JavaScript · 200; excluded from its own cache scope.
GET/view/:package/:pagePackage page · compiled HTML · 404 for an unknown view; build errors remain explicit.
*/:package/:routeContract comes from @method, @auth, upload/stream tags and the handler return shape.

Protected package routes accept Authorization: Bearer <token>. JSON writes use Content-Type: application/json. Return { status, headers, body } for explicit errors and document package-specific payloads beside their handler.

Operate safely / Reference index

Exports, decorators, events and endpoints in one index.

This compact catalog is generated from a structured local source so navigation and search use the same names. The linked canonical contracts remain authoritative for detailed payloads.

Generated from 12 active exports, decorators and event contracts · compatible with ^2.0.0

exportbootstrap()

Start and configure a Lógos runtime.

exportdispatchEvent() / $()

Run a pipeline and return its transformed payload.

exportrunAgentTask() / runChainTask() / runChain()

Run one agent loop or a sequential chain; the low-level chain receives a container.

decorator@register_router

Publish an HTTP or WebSocket handler.

decorator@register_tool

Publish an agent-callable function.

decorator@register_module

Install or override a runtime module.

decorator@register_predicate / @register_middleware

Compose package-local policy around handlers.

eventagent:step

Observe each trace step of an agent run.

eventreload:commit

Request an explicit Stabilizer commit.

eventpackage:reload_failed

Observe a rejected candidate and its checks.

httpGET /_logos/offline

Inspect the computed offline policy.

httpGET /logos-sw.js

Serve the policy-bearing Service Worker.

Operate safely / Production readiness

Verify the whole operating boundary before launch.

TLS & edge

Terminate modern TLS, restrict exposed ports and define request/body limits.

Secrets

Keep package credentials out of Git, rotate them and limit provider scopes.

Backups

Store encrypted off-machine copies and rehearse tenant and host recovery.

Observability

Collect structured request, tool, agent and reload events with retention and alerts.

Quotas

Set route, tool, storage and provider limits at enforceable boundaries.

Isolation

Run untrusted or critical workloads outside the shared runtime process.

Ship & license / Upgrade guide

Upgrade contracts before implementations.

Record the current Lógos and package versions, read release notes, compare generated manifests and identify breaking changes in exports, decorators, event payloads, views and database schemas. Take an external backup and a deploy checkpoint, upgrade in staging, run package plus runtime suites, then promote with rollback criteria already written.

InventoryStageTestPromote
Changelog policy.Every release should name added, changed, deprecated, removed and security-relevant contracts, plus the first compatible Lógos version.

Ship & license / Auto-Repository

Your Machine has inspectable history.

Each Machine can expose its working tree as a repository. Clone it, branch, push and connect a deployment to a specific change. Runtime edits become reviewable history instead of hidden service state.

Changeworking tree
Commitmain
Branchreview
Deploydeploy-42

Clone, change, deploy and recover

Add only your public SSH key in ThinkIac. The gateway authenticates it and forwards Git operations to the correct Machine; it never receives your private key. A pushed branch creates a deployment candidate tied to that commit. Promotion activates the already-built release, while rollback reactivates the previous immutable release.

Complete Auto‑Repository workflow
# 1. Add your public SSH key in ThinkIac, then clone the Machine.
git clone git@thinkiac.space:ada/my-machine.git
cd my-machine

# 2. Make an isolated branch and verify the package.
git switch -c feature/notes-sharing
npm test -- --test-name-pattern notes
git add packages/notes
git commit -m "Add shared notes"

# 3. Push. The branch receives its own deployment candidate.
git push -u origin feature/notes-sharing

# 4. Review its URL and logs in Git Cloud, then Promote.
# Production moves to this exact commit; it is not rebuilt.

# 5. If health regresses, choose Rollback in Git Cloud.
# The previous immutable release becomes active again.
SSH here means Git transport.The SSH gateway allows clone, fetch and push. Runtime commands remain local until a separate remote command service can enforce an allowlist, identity, authorization, timeouts and an audit record for every invocation.

Ship & license / Two projects

Open kernel. Source-visible platform.

Lógos and ThinkIac are related, but they are not licensed as one product. Choose the boundary that matches what you want to build, and read the complete license before commercial deployment.

Lógos

MIT open source

Use, modify, distribute and embed the independent runtime kernel under the permissive MIT license.

Read the MIT license
ThinkIac

TSAL source-available

The platform source can be studied, modified and run under the ThinkIac Source-Available License. It is not an OSI-approved open source license; commercial and competing-product restrictions apply.

If your organization reaches US$100,000 or R$500,000 in consolidated annual gross revenue, a commercial license is required for each fiscal year in which it reaches that threshold.

This guide is not legal advice.The license text is the authority. If your use is commercial or distributed, evaluate the complete terms rather than relying on this summary.

Ship & license / Self-hosting

Your infrastructure, the same package contract.

Run a Machine on a supported Linux host or a Docker-capable workstation. You operate the host, network, backups and updates; packages, routes, tools and data boundaries behave the same way. Start by deciding whether systemd or a container owns the process—do not run both for the same workspace.

Prerequisites

Node.js 18+, a non-root service account, a persistent workspace volume and a DNS name when the service is public.

Configuration

Keep runtime and package variables in protected environment files. Never commit tokens, database keys or TLS private keys.

Network & TLS

Expose only the reverse proxy. Terminate TLS there, set request limits and forward the original host and protocol headers.

Persistence

Mount package data, Scriba databases, checkpoints and uploads on durable storage outside an ephemeral container layer.

Backups

Encrypt and copy data off-machine, retain multiple points, and test restoring into an isolated Machine.

Updates & recovery

Stage a version, health-check it, promote it, and retain the last known-good release plus a written rollback command.

Typical bootstrap
git clone <your-machine-repository> thinkiac
cd thinkiac
docker compose up -d

# Open the runtime, enroll the Machine, then keep secrets in
# the package .env or the host secret manager — never in source code.

Systemd service

/etc/systemd/system/thinkiac.service
[Unit]
Description=ThinkIac Machine
After=network-online.target

[Service]
User=thinkiac
WorkingDirectory=/srv/thinkiac
EnvironmentFile=/etc/thinkiac/runtime.env
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Reverse proxy and operations

Proxy HTTPS to the local Machine port, preserve Host and X-Forwarded-Proto, and keep the application port bound to loopback. Before every upgrade: take an encrypted backup, record the active versions, deploy to staging, run npm test, check /__health, then promote. Restore by stopping the service, restoring the verified data snapshot to a separate path, validating it, and switching back only after the health check passes.

Compare with ThinkIac Cloud

Ship & license / Security boundaries

State the protection that is actually running.

A plain container, a hardened host and a sandboxed workload do not offer the same isolation. ThinkIac keeps that boundary explicit instead of turning every deployment into the same marketing promise.

On your infrastructureTLS, firewall rules, host updates, secret storage, backups and recovery remain your operational responsibility.