- TypeScript 99%
- CSS <1%
- Python <1%
- JavaScript <1%
- HTML <1%
connectome-host #
connectome-host runs long-lived agents. A recipe — one JSON file — describes an agent: its name, model and provider, memory strategy, MCP servers and modules. The host keeps that agent running: in a terminal, as a headless daemon under systemd or launchd, or as one process in a supervised fleet, with a browser console for whoever looks after it.
Most agents it runs are residents: agents with a name and a history who live on Discord or Zulip for months, remembering in their own voice — sometimes continuing a conversation that began on claude.ai, in Claude Code or in Codex. Most of the host's work goes into what that takes: memory that folds instead of truncating, prompt caches that stay warm at half-million-token contexts, honest cost and subscription-quota accounting, an operator who can see and repair a live context, and ways to bring an agent in from somewhere else. It started as a knowledge-mining TUI, and those recipes still ship (what we set out to build, and what became of it).
Built on the Connectome stack: @animalabs/agent-framework + @animalabs/context-manager + @animalabs/chronicle + @animalabs/membrane.
What people run on it #
| Use | Start here |
|---|---|
| A resident — a persistent agent on Discord (or other surfaces), headless on a VPS, with autobiographical memory | docs/AGENT-ONBOARDING.md, docs/DEPLOYMENTS.md |
| A continued conversation — bring a claude.ai conversation, a Claude Code session or a Codex rollout across and keep going | docs/claudeai-evacuation.md, docs/claude-code-ingest.md, scripts/import-codex-rollout.ts |
| Historical Fleet orchestration — archived conductor/child workflow, not currently exposed tools | docs/fleet-protocol.md, recipes/TRIUMVIRATE-SETUP.md |
| Knowledge mining — standalone miner / reviewer / clerk recipes; historical pipeline design | recipes/SETUP.md, docs/LIBRARY-PIPELINE.md |
| A workbench — a plain assistant with tools in your terminal | Quick start below |
All the documentation is indexed in docs/README.md; how
the host is put together is in ARCHITECTURE.md.
Quick start #
# Prerequisites: Bun and Node.js 22.13+, plus credentials
export ANTHROPIC_API_KEY=sk-ant-... # or ANTHROPIC_AUTH_TOKEN (Claude subscription)
bun install # installs tools deps and builds web UI
bun src/index.ts # generic assistant (or the last recipe you loaded)
bun src/index.ts recipes/mock-test.json # zero-cost offline smoke run, no credentials needed
bun src/index.ts path/to/recipe.json # load a recipe
bun src/index.ts https://example.com/r.json # recipe from URL
Bun loads .env automatically; .env.example lists the common variables.
Built-in tools #
ToolsModule is enabled by default and exposes bare bash, read, write,
edit, grep, glob, ast_grep, ast_edit, search, fetch, browser,
terminal, and repl. Default groups are native, web, browser,
terminal, and repl; acp is opt-in. No tools MCP registration is needed.
Configure modules.tools directly:
{
"modules": {
"tools": {
"groups": ["native", "web", "browser", "terminal", "repl", "acp"],
"workspaceRoot": "/path/to/workspace",
"stateDir": "/path/to/private-state",
"artifactDir": "/path/to/private-state/tool-results",
"webCacheDir": "/path/to/private-state/web-cache",
"antigravityAuthFile": "/path/to/private-auth.json",
"acpExecutable": "omp",
"acpPermissionPolicy": "deny"
},
"acp": {
"workspaceRoot": "/path/to/workspace",
"stateDir": "/path/to/private-state",
"permissionPolicy": "deny"
}
}
}
Set modules.tools: false to disable the built-in suite. Filesystem tools read
and write disk directly; there are no workspace mount or Fleet tools exposed.
Use absolute paths outside the configured workspace. Keep credentials, browser
profiles, interpreter state, artifacts, and broker databases out of source control.
Without explicit paths, the host uses its launch directory as workspaceRoot
and the current session store as stateDir. The ACP completion observer inherits
the built-in suite's workspace, state, executable and permission settings unless
overridden in modules.acp.
The ACP group exposes coding delegation; the optional modules.acp observer
reconciles broker completions. Configure matching workspace/state and permission
authority for both. ACP is not a replacement for the historical Fleet semantic
orchestration workflow.
Root postinstall installs the independent tools/ dependencies automatically.
npm run tools:install remains a repair/manual installation command and is useful
for standalone MCP clients:
npm run tools:install
node tools/src/server.mjs --groups native,web,browser,terminal,repl
# Optional browser download (or set CONNECTOME_BROWSER_EXECUTABLE):
npm --prefix tools run browser:install
Node.js 22.13+ is required for independent tool supervisors and Node-only tests.
Python sessions require Python 3 (override with CONNECTOME_PYTHON_EXECUTABLE).
Browser tools and terminal screenshots require Chrome/Chromium. See
tools/README.md for platform prerequisites and standalone
configuration. The npm tarball includes tool sources and package/lock metadata,
not dependencies or private state. If lifecycle scripts were skipped, approve
and run the root postinstall before using the installed host.
In a source checkout, run host tests with bun test (all existing host suites
under test/); run the independent Node-only suites with npm --prefix tools test.
Share a fresh source export or npm pack tarball without .git. Ignore rules
and package allowlists exclude current private logs/configuration/state; they
do not remove personal records already present in earlier Git commits.
Recipes #
A recipe is a JSON file that configures everything specific to one agent:
{
"name": "My Agent",
"description": "What this agent does",
"agent": {
"name": "researcher",
"model": "claude-opus-4-6",
"timezone": "America/Los_Angeles",
"systemPrompt": "You are a ...",
"maxTokens": 16384
},
"mcpServers": {
"my-server": {
"command": "node",
"args": ["path/to/server.js"],
"env": { "API_KEY": "${MY_SERVER_API_KEY}" }
}
},
"modules": {
"subagents": true,
"tools": { "workspaceRoot": "./notes" }
},
"sessionNaming": {
"examples": ["Thread Archaeology", "Pipeline Debug"]
}
}
Every string value may reference the environment as ${VAR} or
${VAR:-default}; a missing required variable fails the load, naming it.
Values and combinations are validated at load time and fail loudly, naming
the key. Unrecognized keys, though, are generally not rejected — a
misspelled key (mcplServers, a leftover modules.files) is silently
ignored, so check spelling against this README and src/recipe.ts.
agent.timezone is an IANA zone used only for times rendered to the agent.
Chronicle and MCPL protocol timestamps remain epoch/UTC. If the recipe omits
it, AGENT_TIMEZONE is used, then the process timezone.
Memory defaults: agent.strategy may be omitted entirely. The default is
the autobiographical memory strategy with adaptive resolution, KV-stable
folding (compile plans that preserve prompt-cache prefixes), compression by
the agent's own model, and summaries voiced as the agent itself
(summaryParticipant defaults to agent.name). Set a strategy block only
to tune windows/budgets or opt into a different strategy type (frontdesk
for channel-staffing agents, passthrough for none) — see
docs/AGENT-ONBOARDING.md for sizing guidance on long-lived agents and
docs/AGENT-MEMORY-GUIDE.md for how it works.
Prose routing #
Plain assistant text (anything the model writes that is not a tool call) is
delivered by Agent Framework according to agent.proseRouting:
| Mode | Behavior |
|---|---|
"locus" (default) |
Text is auto-published to the current locus — the channel that last woke the agent. Text emitted in a tool-call round is delivered live, as narration, unless that round also calls skip_reply or an explicit send tool. |
"hybrid" |
Like locus, but a leading >>>destination envelope routes that segment elsewhere through the authorized channel resolver. |
"explicit" |
Text must start with >>#channel / >>@person / >>skip_reply; unprefixed text is never delivered and bounces to a clipboard for a prefixed resend. |
"disabled" |
Text is never auto-published. The only way anything reaches a channel is an explicit send tool (send_message, channel_publish, reply_message, send_dm, ...). Authored text stays in Chronicle and the turn-end [delivered] nothing receipt tells the agent how many segments were withheld. |
Use "disabled" for agents that run multi-step tool tasks from a busy shared
channel: in locus mode a stray one-line narration between two tool calls
("checking page 2") is published to that channel as an ordinary message, and
the only mitigation is behavioral (never narrate in tool rounds, always end
tool-only turns with skip_reply). With "disabled" the agent replies by
calling a send tool, and nothing else leaks. Ephemeral subagents inherit the
caller's mode.
agent.sameRoundThinkTextPolicy ("public" default, or "private") governs
only text emitted beside a think() call in the same round. It does not
cover tool-call rounds without think(); use proseRouting: "disabled" for
that. The think policy can be inspected and switched at runtime through the
agent's agent_settings tool and the web UI; proseRouting is fixed for the
process lifetime.
{
"agent": {
"proseRouting": "disabled",
"sameRoundThinkTextPolicy": "private"
}
}
See Agent Framework's docs/disabled-prose-routing.md,
docs/explicit-prose-routing.md, and docs/hybrid-prose-routing.md for the
full semantics of each mode.
Recipe loading #
| Command | Behavior |
|---|---|
bun src/index.ts |
Reuse last saved recipe, or start with generic default |
bun src/index.ts <path> |
Load recipe from local file |
bun src/index.ts <url> |
Fetch recipe from HTTP URL |
bun src/index.ts --no-recipe |
Reset to default generic assistant |
The loaded recipe is saved to $DATA_DIR/.recipe.json (mode 0600) in its
unresolved form — ${VAR} references kept, a URL system prompt kept as
the URL — and re-resolved against the current environment on each bare start,
so rotated credentials take effect on restart. The exceptions are fleet
children's recipe and extensions' path, which are stored resolved: keep
secrets out of them, since a ${TOKEN} in a child-recipe URL lands in the
snapshot as the token itself. Treat the data directory as sensitive anyway —
it holds the agent's history, and a fleet parent currently persists its
children's resolved env there (#162). A variable that has disappeared since
fails the start loudly instead of falling back to the default recipe.
System prompt from URL #
If systemPrompt is an HTTP(S) URL (no spaces or newlines), it's fetched as plain text:
{
"agent": {
"systemPrompt": "https://example.com/prompts/researcher.md"
}
}
MCP servers #
A recipe's mcpServers decides which servers the agent gets. An entry can
define a server outright (command + args, or url for WebSocket), or name
an id from mcpl-servers.json — a registry in the working directory
(edited with /mcp add|remove|env). Registry servers are opt-in: one
loads only when a recipe names its id. For a named id the registry supplies
the command, args and env, and the recipe entry may carry only policy fields —
no command or url needed: channelSubscription, toolPrefix, feature-set
and tool toggles, reconnect settings, WebSocket transport, access,
toolLifecycle and inheritEnv (RECIPE_OVERRIDABLE_SERVER_FIELDS in
src/mcpl-config.ts).
An id-only entry the registry doesn't define is a startup error. Changes take
effect on restart.
Agents with modules.mcplAdmin can also deploy, restart and unload their own
servers; those are kept in mcpl-servers.agent.json and load regardless of
the recipe.
Per server, requestTimeoutMs raises the framework's JSON-RPC timeout (60 s
by default) for slow tools, and agent.retry passes a Membrane retry policy
through for flaky gateways.
Stdio servers do not inherit the host environment. A child gets agent-framework's allowlist (CHILD_ENV_ALLOWLIST: PATH, HOME, USER, LOGNAME, SHELL, TERM, LANG, LC_*, TZ, temp and XDG dirs, DISPLAY/WAYLAND_DISPLAY, TLS CA bundles, HTTP_PROXY/HTTPS_PROXY/NO_PROXY in either case, and the Windows system variables), then the entry's env, then the host's AGENT_TIMEZONE. agent-framework then adds its derived DISCORD_SUPPRESSED_REACTIONS_BASELINE when the child spawns, unless the entry's env sets one with tokens, or, for an inheritEnv entry, the host's own environment does. A server that needs a value from .env must declare it, e.g. "DISCORD_GUILD_ID": "${DISCORD_GUILD_ID}"; a variable left only in .env is unset for the server.
Operator-owned recipe/file entries can set inheritEnv: true to pass the full host environment, including credentials. Prefer explicit env entries for needed variables. Explicit recipe false overrides file true; omission preserves the file's policy. Agent-owned mcpl-servers.agent.json overlays strip inheritEnv, including when replacing an operator-defined server. Put a full-inheritance grant in the recipe or operator server file instead.
Check legacy configuration variables before enabling full inheritance. For Discord MCPL, an inherited DISCORD_SUPPRESS_REACTION_EMOJIS="" seeds an explicit empty suppression list when its configured filters file does not yet exist; that durable file then overrides the protective baseline. Remove an unintended stale variable before the first startup, or configure the intended suppression in the filters file. Likewise, a DISCORD_SUPPRESSED_REACTIONS_BASELINE with tokens in the host's environment becomes an inheriting child's whole baseline, in place of agent-framework's protective default. The framework logs when it does.
Feature sets and tool names #
An MCPL server declares feature sets, and each server entry chooses which of them to enable:
enabledFeatureSetsomitted: every set the server declares is enabled, subject to theusesrule below.enabledFeatureSets: []in a recipe or inmcpl-servers.json: no set is enabled (deny-all). Only the agent's ownmcpl-servers.agent.jsonreads an empty list as unset, since strict function calling makes some models send[]for "unspecified" (resolveOverlayEntryinsrc/mcpl-config.ts).- A
*in a pattern matches exactly one dot-separated segment (memory.*matchesmemory.retrieval, notmemory.a.b).disabledFeatureSetswins overenabledFeatureSets. - A set whose declaration has no
uses, an empty one or an unrecognized value stays disabled whatever the lists say, as does one whoseusesnames a capability the server was not granted. Each such set is logged on stderr as[mcpl] <server>/<set> disabled: ….
The model sees an MCPL server's tools as mcpl--<serverId>--<tool>:
that is agent-framework's default prefix, and the host sets none of its own.
An entry's toolPrefix replaces the mcpl--<serverId> part. Tool-name
patterns elsewhere in a recipe match that model-facing form, so
toolClassOverrides keys and toolLifecycle tools narrowings are written
mcpl--blender--*, not blender--*. Most host module tools use
<module>--<tool> (for example history--search); built-in ToolsModule
tools use bare names. enabledTools and disabledTools take bare server names.
Tool lifecycle and tool classes (MCPL RFC-007 / RFC-008) #
An MCPL server can follow the agent's calls to other tools: a desktop avatar picking up a prop while a shell command runs, or pointing where the agent clicks. It receives tools/lifecycle notifications (started, then completed / failed / aborted) and never tool results. Both permissions are off by default. A toolLifecycle block on the server's entry, in the recipe or in mcpl-servers.json, is the grant:
"mcpServers": {
"avatar": {
"command": "node",
"args": ["avatar-mcpl.mjs"],
"toolLifecycle": {
"observe": {},
"inputs": { "classes": "default" }
}
}
}
observesends metadata (tool, class, provider, phase, duration).{}means every call. Narrow it withtools(patterns over model-facing names such asmcpl--cua--*,*= any run),classes, orconversations(agent names).inputssends argument fields, but only the fields the server asks for withtools/observe. It needs atoolsorclassesterm to deliver anything ("default"= computer, shell, files, web, media, body). It never carriescommsor unclassed tools' arguments.maxInputBytesbounds the payload (default 16 KiB).
A tool's class comes from, in order:
- the recipe's
toolClassOverrides; - this host's table of its own module tools (
HOST_TOOL_CLASSESinsrc/tool-lifecycle-config.ts) or the framework's built-ins; - the server's own
_meta["mcpl/class"].
Third-party MCP servers never declare a class, so class them in the recipe:
"toolClassOverrides": {
"mcpl--cua--*": ["computer"],
"mcpl--blender--*": ["media"]
}
An unclassed tool is treated as the most restrictive class: observable that it ran, never what it was given.
To check what each tool ended up as, and which of the three sources decided it, run /tools [agent], open the web UI's MCP tab, or GET /debug/tool-classes. With mcplAdmin enabled, the agent's mcpl_list shows the same for each server's tools.
Servers an agent deploys for itself (mcpl-servers.agent.json) can never hold either permission. The overlay denies toolLifecycle and strips any toolLifecycle block, as it already does for context hooks and server-initiated inference. To let such a server observe, the operator moves it into the recipe. These settings take effect with an agent-framework that includes tool lifecycle (anima-research/agent-framework#199); older ones ignore them.
Included recipes #
| Recipe | What it is |
|---|---|
recipes/mock-test.json |
Offline smoke test: mock provider, no credentials, loopback web UI |
recipes/claude-export-revive.json |
Continue a conversation imported from claude.ai (guide) |
recipes/triumvirate.json |
Historical conductor example; Fleet orchestration is not exposed by the current host (archive) |
recipes/knowledge-miner.json |
Knowledge extraction from Zulip + GitLab + web search (guide) |
recipes/knowledge-reviewer.json |
Critic pass and SME checklists over mined documents |
recipes/clerk.json |
Frontdesk agent that answers from the mined library on a Zulip channel |
recipes/zulip-miner.json |
Zulip-only knowledge extraction |
recipes/mcpl-editor-test.json, webui-test.json, webui-fleet-test.json |
Development test recipes |
The production residents' recipes live in their own install directories, not
here; docs/AGENT-ONBOARDING.md shows the shape.
Claude subscription provider #
The default anthropic provider also runs on a Claude subscription (Pro/Max)
instead of an API key. Install Claude Code, generate a long-lived OAuth token
with claude setup-token, and export it as ANTHROPIC_AUTH_TOKEN:
export ANTHROPIC_AUTH_TOKEN=sk-ant-oat...
No recipe change is needed — any anthropic recipe works. When
ANTHROPIC_AUTH_TOKEN is set it takes precedence over ANTHROPIC_API_KEY
(requests never carry both). Connectome then sends the oauth-2025-04-20
beta header (merged with any agent.anthropicBetas) and prepends the Claude
Code identity block the subscription endpoint requires ahead of the recipe's
system prompt. Usage draws down the subscription's 5-hour and weekly windows
rather than per-token billing; the TUI status line and WebUI show them. When
the quota meter already has a reading that shows a spent window, a 429 parks
the agent until the window resets instead of retrying; without a reading
(e.g. the first 429 in a headless run with no viewer, or an unreadable usage
endpoint) it follows the normal retry path.
A claude setup-token credential cannot be rotated by the host: when it
expires or is rejected, the host raises an auth-rejected alert whose only
action is pasting a new token. A credentials file with a refresh token
(ANTHROPIC_OAUTH_CREDENTIALS_FILE) makes "Refresh token" available and warns
30 minutes before expiry. See the credential-alerts note under the ChatGPT
provider and docs/subscription-transport.md.
OpenAI reasoning #
For openai-responses and openai-codex, primary-agent reasoning is configured
under agent.responses. Omitted settings default to reasoningEffort: "high"
and reasoningContext: "all_turns". Add the following to an existing recipe to
request a higher effort:
{
"agent": {
"responses": {
"reasoningEffort": "xhigh",
"reasoningContext": "all_turns"
}
}
}
The host accepts none, low, medium, high, xhigh, or max for
reasoningEffort, and current_turn or all_turns for reasoningContext. The
selected model must support the requested effort. These settings apply to the
primary agent; modules.retrieval has its own reasoning setting described below.
Memory sizing and compression-model settings are covered in
docs/AGENT-ONBOARDING.md.
ChatGPT subscription provider #
Install the Codex CLI, sign in with codex login, then select the subscription
transport in a recipe:
{
"agent": {
"provider": "openai-codex",
"model": "gpt-5.4",
"codex": { "fastMode": false },
"systemPrompt": "You are a helpful assistant."
}
}
Connectome asks the Codex app-server to refresh the ChatGPT login and starts a
device-code flow if needed. No OPENAI_API_KEY is used for this provider. Use
/fast on or /fast off at runtime. Connectome requests Codex's Fast tier and
warns if the service reports that it fell back to Standard; Fast mode consumes
subscription credits at a higher rate when applied. Quota windows are shown
the same way as for the Claude subscription. See
docs/subscription-transport.md.
On any subscription credential (Anthropic OAuth or Codex) the host names what
is wrong with it — spent quota, unreadable quota, expiring/expired/rejected
token, pending login — as an ops alert with the actions it can run, shown on
the TUI status bar / /auth, the WebUI alert strip and Health tab, and the
fleet parent. Nothing rotates without an operator action. See
docs/subscription-transport.md.
OpenAI-compatible endpoints (Ollama, vLLM, Together, Groq, NanoGPT, ...) #
Any server speaking the OpenAI chat-completions API works through the generic
openai-compatible provider — the recipe names the endpoint and the model:
{
"agent": {
"provider": "openai-compatible",
"baseUrl": "http://localhost:11434/v1",
"model": "qwen3:32b",
"systemPrompt": "You are a helpful assistant."
}
}
The API key is read from OPENAI_COMPATIBLE_API_KEY only — deliberately no
OPENAI_API_KEY fallback: baseUrl is recipe-controlled, and a real OpenAI
credential must never be sent silently to an arbitrary endpoint. Local
servers usually need none. agent.model is required —
there is no default model for an arbitrary endpoint. Tool calls use the
standard tool_calls format, so the endpoint must support function calling
for tool-using recipes. Provider-side prompt caching and cache accounting
depend on what the endpoint reports.
Other providers #
agent.provider |
Credentials | Notes |
|---|---|---|
bedrock |
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION (default us-west-2) |
Claude on AWS — including models retired from the first-party API. BEDROCK_BASE_URL routes through an inference gateway. Prompt caching is enabled per model where Bedrock supports it; agent.promptCaching overrides. |
openai-responses |
OPENAI_API_KEY (OPENAI_BASE_URL optional) |
OpenAI Platform, Responses API. |
openrouter |
OPENROUTER_API_KEY |
|
mock |
none | Echoes the last user message by default; scripted replies and timing are set under agent.mock (below). Calls are still logged. |
Every provider's calls are logged to $DATA_DIR/llm-calls.<iso>.jsonl.
agent.formatter: "anthropic-xml" with agent.prefillUserMessage reproduces
classic prefill-style prompting for agents migrated from prefill-era bots.
Mock provider #
agent.provider: "mock" runs membrane's MockAdapter: the full host loop
with no credentials and no provider spend. agent.mock configures it:
| Key | Default | Meaning |
|---|---|---|
echoMode |
true |
Reply [Echo] <last user message>. |
defaultResponse |
membrane's canned text | Reply when not echoing and the queue is empty. A non-empty string. |
responseQueue |
none | Replies returned in order, one per provider call, before the echo or defaultResponse takes over. Every call through the adapter takes one, including auxiliary calls such as compression and session naming. Non-empty strings. |
completeDelayMs |
10 |
Delay before a non-streamed reply, in ms. Agent turns stream; auxiliary calls take this path. |
streamChunkDelayMs |
5 |
Delay between streamed chunks, in ms. There is none before the first chunk. |
streamChunkSize |
10 |
Characters per streamed chunk. A positive integer. |
A streamed reply of L characters takes about
(ceil(L / streamChunkSize) - 1) × streamChunkDelayMs, so a slow agent turn
is a long reply with small chunks. This one holds the first turn for about
2 s, which leaves time to send more events while it is in flight and see how
they coalesce:
"agent": {
"provider": "mock",
"mock": {
"echoMode": false,
"responseQueue": ["first reply", "second reply"],
"defaultResponse": "done",
"streamChunkSize": 1,
"streamChunkDelayMs": 200
}
}
Unknown keys under agent.mock are reported in the recipe's
unknown-key warning, like those at the other levels.
What it provides #
Keeping one agent alive
- Autobiographical memory — context-manager folds older history into
memories the agent writes in its own voice, fitted to a context budget the
agent and operator can change at runtime (
agent_settings, WebUI Settings); nothing is deleted from Chronicle - Prompt-cache economics — cache-stable folding, a 1-hour Anthropic cache
by default (
agent.cacheTtl), plus opt-inagent.cacheKeepalive: current-prefix Antigravity warming and validated Anthropic snapshot replay, with live Settings controls and distinct Health accounting. Implicit Google cache lifetime is not guaranteed. See transport safety. - Cost and quota — a billing-grade call ledger with a cache verdict per call; on subscriptions, quota windows instead of dollars and parking on a spent window
- Attention — the wake gate (
modules.wake, on) decides which events start a turn; agents edit their own rules.subscriptionGc(on) closes channels that flood context;channelMode(on) switches a channel between mentions-only and debounced ambient. Seedocs/ATTENTION-AND-GATING.md - Tools (
modules.tools, on) — direct filesystem, shell, web, browser, terminal, and persistentrepltools with bare names. ACP is an opt-in group. - History (opt-in,
modules.history) — the agent searches and extracts from its own uncompressed record. The object form{ "semantic": { "url": … } }addshistory--semantic_search: meaning-based search over raw messages and memories, through a shared embed service (startup fails if the installed agent-framework predates 0.20). The index namespace is always<prefix>/<session id>, the prefix defaulting to the agent name;tokentakes${VAR}; plainhttpis refused outside loopback and the tailnet unlessallowInsecureHttp: true; unknown keys fail the load. Sync sends the raw record — including the agent's private notes unlessincludePrivateTools: false— and/session deletedoes not remove the remote index - Shared instructions (opt-in,
modules.instructions) — see below - Identity (opt-in,
modules.identity) — the agent's own key-based identity, used to obtain access to services without credentials entering its context - Conversations (top-level
conversations— deprecated, not recommended) — per-channel conversation forks spawned from a dormant trunk agent. Being retired (agent-framework#235): its'mention'bind/trigger rule, the default for channels, readsmetadata.mentioned, which discord-mcpl does not set, so on Discord channels an @-mention never binds or triggers a fork. Still works; the host logs a[deprecated]line at startup - Subconscious (top-level
subconscious) — a secondary agent that cantune_outchannels; code execution (top-levelcodeExecution) — a Python tool runner - Activity (
modules.activity) typing indicators and a TTS relay (modules.ttsRelay) for voice clients - Extensions (top-level
extensions) — local modules that register custom context strategies or framework modules
Looking after it
- Web UI (
modules.webui) — browser operator console, below - TUI, readline and batch modes — OpenTUI interactive terminal,
--no-tuifor a plain prompt, or piped stdin for a one-shot batch run - Headless mode —
--headless: no terminal, JSONL over a Unix socket; how residents run under a supervisor - Time-travel — Chronicle-backed undo/redo, checkpoints, branch exploration; in the web UI, rolling back to a message and suppressing messages
- Session management — isolated sessions with auto-naming
- Ops alerts — compression quarantine, refusal streaks, inference exhaustion and more surface as
ops:alerttraces,logs/failures.logrecords, and an optional webhook (CONNECTOME_OPS_WEBHOOK) - Importers — claude.ai exports, Claude Code transcripts and Codex rollouts become sessions (guides above)
More agents
- Historical Fleet orchestration — archived in
docs/fleet-protocol.md;modules.fleetand Fleet agent tools are not exposed by the current host. - Subagent forking (opt-in,
modules.subagents) — spawn/fork parallel in-process agents, shown in the fleet tree (Tab) - Persistent lessons (opt-in,
modules.lessons) — knowledge store with confidence scores and tags. Automatic retrieval-injection of lessons into context (modules.retrieval) is a separate opt-in — it adds per-turn context churn and retrieval-model calls, so enable it only for agents that actually curate a lesson library
For openai-responses and openai-codex, an object-valued
modules.retrieval can set reasoningEffort (none, minimal, low,
medium, high, xhigh, or max) independently of the primary agent.
Retrieval calls are independent one-shot requests, so there is no separate
retrieval reasoning-context setting. When reasoningEffort is configured,
model must also be set explicitly: the historical retrieval default is a
Claude model and cannot be sent through an OpenAI adapter. Anthropic/Claude
uses different native thinking controls and does not accept this OpenAI-shaped
option.
For a single-pass local decision model, configure an HTTP decision endpoint:
{
"modules": {
"retrieval": {
"model": "LiquidAI/d1-2B",
"decisionUrl": "http://127.0.0.1:7345",
"decisionThreshold": 0.5,
"maxInjected": 5
}
}
}
The host POSTs the recent conversation and every eligible lesson to
<decisionUrl>/v1/decide; concept extraction and substring matching are bypassed.
The endpoint returns model, durationMs, relevantIds, and a probabilities
object containing a finite P(yes) in [0,1] for every submitted lesson ID.
Selection is inclusive at decisionThreshold (default 0.5), ordered by
probability descending, then lesson confidence descending, before maxInjected
is applied. Dynamic knowledge remains at afterUser, preserving the stable
system-prefix cache. Successful empty decisions and tool-only continuations
reuse the existing retrieval cache.
Use a trusted endpoint: requests contain conversation text and lesson content.
Local HTTP has a five-second timeout. Connection errors, HTTP errors, timeouts,
and malformed decisions warn and fall back to the existing three-step cloud
pipeline. On Google Antigravity, that fallback uses gemini-3.8-flash-low with
low reasoning, never the local model name; other providers use the configured
primary model. No provider reasoning setting is needed for local decisions.
The authenticated /debug/retrieval/view inspector distinguishes semantic
evaluation from mechanical matching and shows probabilities, selected IDs,
decision timing, and retained local errors when cloud fallback takes over.
Vault retrieval #
modules.vaultRetrieval independently retrieves knowledge from an existing
QMD collection. It is disabled by default and does not require Lessons:
{
"modules": {
"vaultRetrieval": {
"enabled": true,
"command": "qmd",
"collection": "notes",
"minScore": 0.70,
"maxChunks": 2,
"timeoutMs": 3000
}
}
}
The example shows all defaults except enabled. Index the collection with QMD
before enabling retrieval. Searches use structured lex: / vec: queries with
--no-rerank --json, with CI removed from the child environment. QMD's
no-rerank scores are rank-based, not calibrated similarity probabilities:
on QMD 2.8.3, the default minScore: 0.70 admits only the top-ranked hit;
maxChunks is an upper bound.
Set decisionUrl to a trusted d1-compatible service to semantically select the
snippets instead of treating RRF rank as relevance:
{
"decisionUrl": "http://127.0.0.1:7345",
"decisionThreshold": 0.5,
"candidateLimit": 4
}
These fields belong inside modules.vaultRetrieval. candidateLimit defaults
to four and its effective minimum is maxChunks. The module fetches that many
candidates without applying minScore, then POSTs them and the user message
to /v1/decide. It selects inclusive P(yes) >= decisionThreshold (default
0.5), orders by probability, and injects at most maxChunks. Headers report
these calibrated probabilities, so a low-RRF second-ranked note can be retained
and a high-RRF but irrelevant first-ranked note can be rejected.
Set queryUrl (for example, http://127.0.0.1:7346) to use an independently
managed, resident QMD SDK worker instead of loading the embedding model in a
new CLI process per search. The worker accepts POST /v1/query with
{ query, collection, limit } and returns the same JSON result array as the
CLI: [{ file, title, snippet, score }]. file uses a canonical
qmd://collection/path URI. A failed, timed-out, or malformed worker response
warns and falls back to the configured CLI. The host does not start or restart
this optional service. Use a trusted endpoint: queries contain user text and
results contain private vault knowledge.
The WebUI Lessons → Last Turn Retrieval section shows both modules' latest primary-agent decisions: evaluated counts, calibrated match probabilities, query/decision/total latency, cache reuse, and fallback/error states. Vault matches link to Obsidian. Legacy scores are explicitly labeled as rankings, not probabilities. The visible inspector refreshes after parallel context gathering and inference boundaries without polling; local/fleet snapshots remain scoped. Cached query/decision timings describe the original work, while total latency describes the latest gather.
Results are injected only at afterUser, never into the system prompt or
stored conversation, preserving the stable prompt prefix. Searches run
concurrently with other module context hooks, including Lessons; turn assembly
still waits for the slowest hook. Results, including empty searches, are cached
per agent and user message across tool continuations. New/edited messages or
replacement contexts select a new cache entry; errors remain retryable.
QMD failures and timeouts log a warning and omit vault context. A failed,
timed-out, or malformed decision response warns and falls back to the original
minScore filtering; fallback scores remain uncalibrated QMD ranks. timeoutMs
bounds each worker HTTP, CLI, and decision attempt; a worker failure followed
by CLI fallback can therefore take two query timeouts. Shutdown cancels
in-flight requests without stale injections. On POSIX, CLI cancellation
terminates the process group, including its model worker.
Shared instructions #
modules.instructions reads living instruction documents directly from disk
and injects them into every agent's context on every turn, including ephemeral
subagents. Edits take effect on the next turn; nothing is persisted to history.
The existing single-file config remains supported: path instructions/AGENTS.md,
position: "system", 32 KiB cap by default. Alternatively, configure an ordered
files array (not combined with single-file options):
{
"agent": { "systemPrompt": "" },
"modules": {
"instructions": {
"files": [
{ "path": "orientation.md", "header": "", "includeInCompression": true },
{ "path": "instructions/AGENTS.md", "maxBytes": 32768, "includeInCompression": false }
]
}
}
}
Each entry requires path and independently supports header, maxBytes,
position, and includeInCompression. header: "" preserves exact file
text with no heading or leading separator. System files follow the base system
prompt in list order; an empty base adds no leading newline. Relative paths use
the configured workspace root (or recipe directory); absolute paths work too.
Each file independently uses a bounded handle-based read and
(realpath, mtimeMs, size) cache: unchanged files incur no content rereads.
A missing/unreadable file fails open without hiding the other files, warning
once per distinct error until recovery. No workspace mount, materialization,
or synchronization tool is required: edit the disk source directly.
Compression identity: only position: "system" files are eligible. If any
file explicitly sets includeInCompression, only entries set to true are
included; an explicit false disables automatic inference for the list. With
no explicit flags, files with header: "" or basename WAKE.md are selected.
This includes a compact orientation without automatically adding the tool
reference. The base agent.systemPrompt is retained. Empty/missing files clear
their old contribution.
The host awaits InstructionsModule.bindAgentContext before publishing or
seeding each resident, ephemeral or conversation context. Generic framework
embedders can supply FrameworkConfig.bindAgentContext; the returned disposer
runs when that context retires. ContextManager.setSystemPromptRefreshHook
refreshes stat-cached identity before memory operations, so idle edits apply
without requiring a primary turn. Compression and merges append the configured
summarySystemPrompt; this separate memory-writing slot does not duplicate
the primary instruction overlay or persist instructions to Chronicle.
Cache note: at position: "system" the block lives in every agent's
prompt-cache prefix, so edits change that prefix. An enabled current-prefix
warmer may seed it before the next turn, but retention/quota savings are not
guaranteed. Curate in batches, or use afterUser for cache-cheap, lower-salience
injection. Compared to lessons (modules.lessons): lessons are a
structured, confidence-scored store with model-driven retrieval; instructions
are ordered free-form curated documents, present verbatim up to their byte caps.
Prerequisites #
- Bun runs the host; Node.js 20+ and npm build the web UI
- No Rust toolchain:
@animalabs/chronicleships prebuilt native binaries - Credentials for one provider: an Anthropic API key, a Claude subscription
OAuth token (
claude setup-token), an OpenAI or OpenRouter key, AWS credentials for Bedrock, the Codex CLI signed in with ChatGPT — or none, formockand keyless local endpoints
Install #
bun install # or npm install; the postinstall step builds the web UI
Environment variables #
| Variable | Default | Description |
|---|---|---|
ANTHROPIC_API_KEY |
(required for anthropic unless ANTHROPIC_AUTH_TOKEN is set) |
Anthropic API key |
ANTHROPIC_AUTH_TOKEN |
— | Claude subscription OAuth token (claude setup-token); takes precedence over ANTHROPIC_API_KEY |
ANTHROPIC_OAUTH_CREDENTIALS_FILE |
— | JSON credentials file (accessToken + refreshToken + expiresAt, Claude Code's shape or flat); a refresh token makes the credential host-rotatable. Use a COPY, never ~/.claude/.credentials.json itself |
ANTHROPIC_OAUTH_AUTO_REFRESH |
off | 1/true: rotate on a 401 without an operator action |
ANTHROPIC_BASE_URL |
Anthropic API | Route Anthropic calls through a gateway |
OPENAI_API_KEY, OPENAI_BASE_URL |
— | openai-responses recipes |
OPENAI_COMPATIBLE_API_KEY |
— | Key for openai-compatible recipes (no OPENAI_API_KEY fallback by design); omit for local servers |
OPENROUTER_API_KEY |
— | openrouter recipes |
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION |
region us-west-2 |
bedrock recipes |
BEDROCK_BASE_URL |
— | Route Bedrock calls through a gateway |
CODEX_BINARY |
codex |
Codex CLI executable for openai-codex subscription auth |
CODEX_HOME |
~/.codex |
Codex credential/config directory |
CODEX_BASE_URL |
ChatGPT Codex backend | Optional subscription transport override |
CODEX_GATE_TOKEN |
— | openai-codex through an inference gate that holds the ChatGPT logins (gate's /codex leg): no Codex CLI or login on this host. Requires CODEX_BASE_URL pointing at the gate |
MODEL |
from recipe, else claude-opus-4-6 (gpt-5.4 for openai-codex) |
Override model (wins over the recipe) |
DATA_DIR |
./data |
Session, recipe and log storage |
AGENT_TIMEZONE |
process timezone | Agent-facing clock when the recipe sets no agent.timezone |
SLEEP_PRIVILEGED_FILE |
./sleep-privileged.json |
Who may wake the agent during sleep |
OBSERVERS_FILE |
$DATA_DIR/observers.json |
Web UI observer grants |
IDENTITY_KEY_FILE, IDENTITY_HOME |
$DATA_DIR/identity-key.pem, id.animalabs.ai |
modules.identity |
CONNECTOME_OPS_WEBHOOK |
— | Webhook for ops alerts |
COUNT_TOKENS_MODEL |
the agent's model | Override the model used for exact token counts in the context-makeup panel |
LLM_CALLS_FULL_PAYLOADS |
off | 1/true: keep the full request on every llm-calls log entry (large) |
GATE_TELEMETRY |
off | 1: send x-gate-* telemetry headers — only when ANTHROPIC_BASE_URL is also set |
Recipes may reference any further variables they need as ${VAR} (for
example WEBUI_USERNAME / WEBUI_PASSWORD, ZULIP_CHANNEL, GITLAB_TOKEN
in the shipped recipes).
Running #
bun src/index.ts # Interactive TUI
bun src/index.ts --no-tui # Readline mode
echo "Hello" | bun src/index.ts # Batch mode: run each line, then stop
bun src/index.ts <recipe> --headless # Daemon: JSONL IPC over $DATA_DIR/ipc.sock, no terminal
bun src/index.ts <recipe> --headless --exit-when-idle # One-shot: exit once the agent goes idle
bun src/index.ts <recipe> --headless --socket-path <p> # Custom socket path
bun --watch src/index.ts # Dev mode
Put the recipe before other arguments: the first argument that doesn't start
with -- is taken as the recipe. Headless mode, its files and its protocol
are described in docs/fleet-protocol.md.
Whenever stdin is not a TTY and --headless is absent, the host runs in
batch mode, with or without --no-tui: it reads stdin to EOF, runs each
line, then stops the agent and closes its MCPL servers. reconnect: true does
not bring them back, because an explicit close is not a failure. That
includes a host started by a supervisor or nohup with stdin redirected, so
anything meant to keep serving needs --headless. The host prints a
[batch] line at the start and before the teardown. If the recipe enables
the web UI, its server outlives the agent by design, so the process keeps
running, with no agent data behind the page, until interrupted.
Web UI #
Enable it in the recipe. The host serves the SPA and its WebSocket protocol on
port 7340 and binds 0.0.0.0 by default; a non-loopback bind refuses to
start without basic-auth credentials, so either bind loopback for local use:
"modules": { "webui": { "host": "127.0.0.1" } }
or supply credentials for a remote deployment:
"modules": { "webui": { "basicAuth": { "username": "${WEBUI_USERNAME}", "password": "${WEBUI_PASSWORD}" } } }
Build the SPA bundle once with bun run build:web (also runs on install via
postinstall). Deployment behind a reverse proxy, observer access and all
endpoints are covered in docs/webui-deployment.md.
- Chat with full interiority: thinking blocks, tool calls + results, live streaming, inline images
- Sidebar: agent/fleet tree, lessons, MCPL servers (live status, tool classes and registry), workspace files, context makeup + compression coverage, Settings (live context budget with dry runs), Pins (protected ranges), Health (alerts, per-call stats, compression debt) — each can inspect any fleet child via the "inspecting:" selector
- Live surgery: roll back to a message, suppress messages, quiesce/resume the host; every action is recorded in an operator log (
docs/webui-live-surgery.md) - Header branch chip opens the Chronicle branch lineage tree (checkout from the UI)
- Ops alerts (compression quarantine, refusal streaks, inference-exhausted) render as persistent banner rows
- Usage panel: per-agent costs and a billing-grade call ledger with cache verdicts; quota windows on subscriptions
- Health: linked seven-day Antigravity quota and call timelines, selectable interval breakdowns by workload or model/thinking, optional cached-token stacks and paged individual calls; observed quota deltas remain distinct from per-call charges
/healthz(health JSON for doctor/fleet tooling),/quota,/antigravity-history(health-scoped seven-day diagnostics),/curve(compression-curve visualization),/debug/context/*(docs/debug-context-api.md),/debug/tool-classes— authenticated like the rest of the surface, and answerable by a fleet child with?scope=<child>/debug/retrieval/view— operator-only per-run lesson selection viewer (seedocs/retrieval-traces.md)- Read-only observer access via Ed25519 device keys with per-grant scopes; the agent can grant and revoke observers itself
For SPA development: cd web && bun run dev proxies the Vite dev server onto a
locally running host.
Slash commands #
The same commands work in the TUI, readline mode, the web UI and over the headless socket.
| Command | Effect |
|---|---|
/help |
List all commands |
/quit, /q |
Exit (exports lessons if the lessons module is loaded; asks first if fleet children are running) |
/recipe |
Show current recipe info |
/status |
Show agent state, branch, queue depth |
/usage |
Show session token usage and costs |
/clear |
Clear this client's display (history and context are kept) |
/lessons |
Show lesson library sorted by confidence |
/export |
Export lessons to ./output/ (JSON + markdown) |
/newtopic [context] |
Reset the head window for a new topic |
/nudge [agent] |
Run inference on the current context without a new event |
/puppet <tool> [json] |
Admin: execute a tool as the agent and store the call/result pair |
/undo |
Revert to state before last agent turn |
/redo |
Re-apply undone action |
/checkpoint [name] |
Save current state (no name: list checkpoints; held for this session) |
/restore [name] |
Restore to checkpoint |
/branches |
List Chronicle branches and checkpoints |
/checkout <name> |
Switch to branch |
/branchto <msgId> |
Branch from a specific message |
/history [n] |
Show recent state transitions |
/find <text> |
Search messages for text |
/mcp list |
List servers in mcpl-servers.json |
/mcp add <id> <cmd> [args...] |
Add or overwrite a registry server (keeps its env) |
/mcp remove <id> |
Remove a registry server |
/mcp env <id> KEY=VALUE [...] |
Set env vars on a registry server |
/tools [agent] |
Each tool's effective MCPL class and where it came from (recipe override, host table, server _meta, or unclassed) |
/budget [tokens] |
Show/set stream token budget |
/fast [on|off|status] |
Toggle Codex subscription Fast mode |
/auth [status|refresh|login|recheck|token <tok>] |
Subscription credential state and operator actions (see docs/subscription-transport.md) |
/session [list|new|switch|rename|delete] |
Session management; delete requires --confirm |
/fleet [list|status|view|peek|stop|restart] |
Fleet children (see docs/fleet-protocol.md) |
Commands that move the head (/undo, /redo, /checkout, /restore,
/branchto, /newtopic) are refused while a turn is in flight.
TUI controls #
| Key | Action |
|---|---|
Enter |
Send message or command |
Alt+Enter / Ctrl+J |
Newline |
Esc |
Interrupt agent (chat) / back (fleet/peek) |
Tab / Ctrl+F |
Toggle fleet view (when subagents or a fleet are enabled) |
Ctrl+B |
Send the running stream and sync subagents to the background |
Ctrl+V |
Toggle verbose mode |
Ctrl+C |
Exit (asks first if fleet children are running; press again to force) |
@child message |
Send a line straight to a fleet child, bypassing the conductor |
Fleet view (Tab):
| Key | Action |
|---|---|
| Up/Down | Navigate tree |
| Enter/Right | Expand/collapse |
| Left | Collapse |
p |
Peek the selected node's live stream — local subagents, fleet children, or a single agent/subagent inside a fleet child |
Delete / Backspace |
Stop the selected subagent or fleet child |
r |
Restart the selected fleet child |
Architecture #
See ARCHITECTURE.md for how the host is built, and
docs/README.md for every guide.
Dependencies #
| Package | Source | Role |
|---|---|---|
@animalabs/agent-framework |
npm | Event-driven agent orchestration |
@animalabs/context-manager |
npm | Context window management and compression |
@animalabs/chronicle |
npm | Branchable event store (Rust + N-API, prebuilt) |
@animalabs/membrane |
npm | LLM provider abstraction |
@opentui/core |
npm | Terminal UI (Zig native core) |