Skip to content

Instantly share code, notes, and snippets.

@johan--
Forked from csnyder256/PLATFORM-SNAPSHOT.md
Created July 22, 2026 05:36
Show Gist options
  • Select an option

  • Save johan--/9408e6d46b9d679a51ab306164953ee0 to your computer and use it in GitHub Desktop.

Select an option

Save johan--/9408e6d46b9d679a51ab306164953ee0 to your computer and use it in GitHub Desktop.
RAG-OS in practice: an anonymized reference build snapshot

RAG-OS in practice: a reference build snapshot

About this document

This is a point-in-time, anonymized snapshot of one working build of the architecture described in BUILD-GUIDE.md. It is condensed from the maintainer's internal, code-verified platform reference, which documents the system as the code on disk actually behaves and flags where older prose disagrees with the code.

It is scrubbed of anything machine-specific or personal: names, absolute paths, private repository names, credentials, and the identifying specifics of a separate high-stakes always-on workload that shares the same machine (in this build, an automated trading system). Module and file names under system/ are kept, because they are the useful part of a reference.

Read it as a worked example, not a specification. The build guide asks you a question at every design fork, so a system you build from it will differ. This snapshot shows what one set of answers looks like after the system has been running for a while: which modules exist, how the security model is actually enforced, where the hard invariants sit, and which ideas were tried and reversed. Nothing here is required for your own build.

The system it describes is an always-on Python daemon on Windows (Python 3.14) that orchestrates Claude Agent SDK sessions to do real work across a set of adopted repositories. It is reachable over a chat transport (Discord in this build) and a local CLI, but stays transport-agnostic, with adapters swappable by contract. The entry point is system/kernel.py.

Table of contents

  1. Vision, principles, and history
  2. Runtime architecture and kernel lifecycle
  3. Data model, contracts, and persistence
  4. Security: gating, tiers, fences, and injection defense
  5. Sessions, routing, and the model registry
  6. Orchestrator, workers, and context assembly
  7. Providers and adapters
  8. Budget, usage accounting, and evaluation
  9. Memory and knowledge (the llm-wiki system)
  10. Operations, configuration, and scheduling
  • Appendix: quick reference

1. Vision, principles, and history

What the platform is

An always-on Python daemon that orchestrates Claude Agent SDK sessions to do real work across a handful of adopted repositories. It is described as an AI operating system rather than a chatbot. The orchestrator does not answer questions itself by default. It decides what the operator wants, which project owns the request, which context and memories to load, which model runs the work (local or cloud), and whether information becomes permanent knowledge.

Founding decision: durable state, ephemeral brains, boring kernel. No LLM context outlives a session, so persistence lives in files and SQLite rows, and a crash or a model downgrade is a non-event.

How it works in one paragraph

A single supervised daemon holds zero LLM context and is the single writer of one SQLite database (state/os.db, WAL). Two Windows Task Scheduler tasks keep it alive: a restart-loop wrapper plus an independent watchdog. A 2-second asyncio tick fires an internal SQLite cron, claims workers from a pool (concurrency capped at 2), supervises adapters, and writes a heartbeat. An adapter does pure I/O translation and drops non-operator senders at the edge. The kernel re-checks identity at the gate, routes to a project via a token-free channel-to-project lookup, resolves a model by capability rather than name, authorizes spend against a governor, and runs one turn of a durable per-project orchestrator session. The orchestrator answers inline or emits a fenced dispatch directive that the kernel parses into a headless worker, a fresh SDK session that edits files and runs commands in place. A worker's result is never shown raw. It is relayed back into an orchestrator turn, and only the orchestrator's own prose reaches the human.

Six design principles

Each survived adversarial challenge from a three-perspective design panel. (1) Durable state, ephemeral brains, boring kernel: the kernel holds zero context and targets under about 2,000 LOC. (2) Security enforced outside prompts, from code rather than from instructions a prompt injection could override. (3) Frugality is architecture: cheap models by default, expensive by exception. (4) Integrate, do not duplicate: existing skills, memory, and the offload stack are adopted in place, never migrated. (5) Compiled knowledge over retrieval-from-scratch. (6) Downgrade insurance: frozen contracts, per-phase proofs, and this reference let a weaker model continue the build.

Machine substrate

A single 24 GB GPU is time-shared with the separate high-stakes workload during a reserved GPU window, which forces the local-model inference gate. Local models run only through wrapper scripts (offload.cmd / ask_local.cmd) that self-gate during that window with no override. Exit 3 means gated, and the rule is to do the work in-session rather than route around it. Two local models are served via llama-swap at 127.0.0.1:8080. The llama-swap runtime belongs to the operator, not the sibling workload, so the OS may start or restart it at any time (scoped to the llama-swap and llama-server processes). The only caveat on a restart inside the reserved window is VRAM contention, which is advisory, not a fence. Python is always invoked as py -3, since bare python on this box is msys2 and mishandles Windows paths.

Locked decisions

Two sets were frozen as non-relitigable. The first (twelve decisions) fixed the foundational shape: subscription auth via claude-agent-sdk with no API key (an ANTHROPIC_API_KEY would silently switch to per-token billing), Python and discord.py, one on-disk root, channel-per-project chat with swappable adapters, governed background work, Task Scheduler persistence, an Obsidian-native vault UX, a conversational-core-first MVP, a private GitHub remote, and a nonce-retype approval flow reused across all danger capabilities. The second set (eight decisions, from the orchestrator re-architecture) opened workers to real in-place work behind a redrawn boundary: commands moved from an allowlist to a denylist with a nonce-gated danger list (permanent deletions, security or OS-self-source edits, live trades), auto-decide dispatch, completion notifications, a spawnable headless CLI worker harness, and a hard worker concurrency cap of 2.

Evolution arc

The system reached its shape through documented reversals. The founding pivot replaced a single long-running top-model instance (rejected unanimously by the panel as unrecoverable, decaying, cap-burning state) with the zero-context kernel over durable state. A later re-architecture converted a drifted sandboxed assistant, which ran one tier-locked session inline, into an orchestrator-plus-worker split, moved security enforcement to the PreToolUse hook, and inverted command control to a denylist. Subsequent phases added a conversational reply loop (workers run in a bypass-permissions mode while the PreToolUse hook still fires, verified live by a denied local-port call in the audit table), read-only inspection of the operator's own transcripts, file attachments, and a cautionary context-degradation episode.

That episode is worth stating plainly, because it is the kind of thing a showcase usually hides. Session rotation was calibrated against an assumed context window that was far smaller than the real one, so it fired on every turn and cold-restarted sessions out from under in-flight workers. The resolution was to leave rotation off permanently while keeping the telemetry and a memory-directory fix. The distilled lesson: ship telemetry and measure before enabling any threshold-driven mechanism.

The most recent work closed the compounding-memory loop, mirroring every project's in-place memory into the vault and injecting a stored-knowledge digest into orchestrator context, proven live when the orchestrator answered a question about the sibling workload from stored memory citing a real file. That sibling workload is deliberately held at a read-and-edit-code tier that cannot run it, one step below its architected tier, because its own external connection is unfenced. This is a live case of the code being more conservative than a locked decision, which is why tiers are read from the DB rather than static YAML.


2. Runtime architecture and kernel lifecycle

The OS is a single always-on Python process (system/kernel.py) supervised by two Windows Task Scheduler tasks. The kernel holds zero LLM context. It is a pure supervisor and the single writer of state/os.db. All intelligence (routing, orchestration, worker synthesis) lives elsewhere. This layer keeps the process alive, ticking, and consistent.

Three supervision rings. Task Scheduler launches run_daemon.cmd, a restart-loop wrapper that relaunches kernel.py on crash. An independent watchdog task runs every 5 minutes to revive a dead or wedged daemon out of band (watchdog.py). The kernel's own asyncio loop ticks every 2 seconds to fire schedules, claim workers, supervise adapters, and write a heartbeat. Both .cmd wrappers pin the interpreter to the repo venv rather than an ambient launcher, which resolves user-site packages differently under Task Scheduler. The wrapper forces UTF-8 (PYTHONUTF8=1, PYTHONIOENCODING=utf-8); the Windows cp1252 default otherwise crashes the SDK subprocess message reader mid-task. Exit code 3 means another instance holds the PID lock, so the wrapper exits without looping; a clean-stop sentinel exits 0; any other exit relaunches after roughly 5 seconds.

PID lock. procutil.pid_alive() uses OpenProcess plus GetExitCodeProcess (treating STILL_ACTIVE == 259 as alive), never os.kill(pid, 0), which on Windows would terminate the process. acquire_pid_lock() fails to exit 3 on a live foreign pid and replaces stale or garbage pids; release_pid_lock() unlinks only when the file still holds our own pid.

Boot sequence (main()). Ordered and asyncio-driven: init and migrate the DB, acquire the PID lock, open the sole WAL writer connection, record uptime and the boot git head, reconcile crash state, seed schedules, resurrect interrupted workers, spawn one asyncio task per adapter, seed the heartbeat, then enter the tick loop. Crash recovery flips every RUNNING job to INTERRUPTED, fails AWAITING_APPROVAL jobs whose approval nonce lapsed, and notifies but never auto-reruns interrupted workers, which mutate real files and are not guaranteed idempotent.

Tick loop. Each 2-second iteration checks stop/restart/kill sentinels, honors a KILLED file that halts autonomous work while keeping the daemon heartbeating, scans the scheduler at most once per wall-clock minute, claims queued workers up to a concurrency of 2 (deliberately small, since a separate high-stakes always-on workload shares the machine), and supervises adapters, where a dead task retries after a cooldown and self-heals. Every message, scheduled job, and worker runs as its own tracked asyncio.Task, so a long LLM turn never stalls the heartbeat. The heartbeat dual-writes to a state/heartbeat file that the watchdog reads with no DB dependency and to kernel_state.

Sentinels. Three files under state/ gate the daemon. KILLED halts dispatch only, daemon.stop is left in place for a clean shutdown (the watchdog treats it as intentional), and daemon.restart is deleted before a clean self-restart so the wrapper loops. Operators drive the daemon via osctl.py, which opens the DB read-only.

Internal scheduler. scheduler.py is the daemon's own SQLite cron, polled each tick and deliberately not registered with Windows Task Scheduler so it cannot collide with the sibling workload's tasks. It supports standard 5-field cron. SCHEDULE_DEFAULTS is code-owned and runs during an off-hours window with a strict intra-run dependency order (extract before consolidate, mirror-memory before the wiki-index reindex, git-push last). seed() is reconciling, inserting missing rows and updating changed cron or kind, so editing the defaults takes effect at the next boot; operator-created schedules under other names stay untouched. claim() uses a UNIQUE idempotency_key bound to a minute-window, guaranteeing at most one fire per minute-window across restarts.

Job FSM (jobs.py). States are QUEUED, AWAITING_APPROVAL, RUNNING, DONE, FAILED, PARKED_CAP (usage cap hit mid-run, no auto-resume, awaits a manual !redispatch), PARKED_GATED (local model gated during a reserved window, retry later), and INTERRUPTED. claim_worker is the atomic race winner (UPDATE ... WHERE state='QUEUED', rowcount 1). Workers cold-restart from spec_json alone; finish() additively records the Claude session id via COALESCE, so later transcript extraction can locate the run.

Single-writer invariant. The kernel is the only writer of state/os.db, opened WAL, while osctl opens read-only. Subprocess LLM sessions never touch the DB; they write result files under state/results/<job_id>/result.md, a directory deny-all fenced from every session, so one worker cannot read another's result. Sharing one process and one connection serializes DB access through the event loop; a per-project in-process lock further serializes a channel's converse turns.

!health. _runtime_health answers whether on-disk code is ahead of the booted daemon with zero tokens, diffing the system/ tree across boot_head..HEAD rather than the bare SHA, because nightly content-sync jobs advance HEAD without touching code.


3. Data model, contracts, and persistence

The OS keeps no long-lived LLM context. Anything that must survive a session, crash, or model downgrade lives in one of four persistence layers, and every subsystem communicates through the frozen value types in system/contracts.py.

The contract freeze

system/contracts.py is frozen as of Phase 0. It is a deliberate model-downgrade handoff artifact: a weaker model resuming the build codes against these types plus the design doc, not against memory of a conversation. Fields, enum members, and protocol methods cannot change without first writing a decision page under knowledge/pages/decisions/. Conventions: all dataclasses are frozen=True; time is Unix epoch seconds as UTC float; paths are absolute strings.

Enums

Five str, Enum vocabularies. Tier (T0 read/answer only, T1 adds writes inside the repo root, T2 adds allowlisted commands, T3 adds external side effects requiring nonce approval) drives the security gate. RouteAction is ANSWER | CONVERSE | JOB. JobState is the persisted job FSM; the DB jobs.state column is free-text, so the enum is authoritative but unenforced. ResultStatus encodes typed provider outcomes as return values, never exceptions, including GATED (local model refused during the reserved GPU window) and DOWN (server down). GovernorVerdict is OK | DEGRADED | PARKED.

Dataclasses

Frozen value objects grouped by subsystem boundary. InboundMessage/OutboundMessage cross the adapter boundary. RouteDecision carries slug, action, tier, confidence, and optional answer text; low confidence surfaces to the operator. CapabilityRequest/ModelHandle express work in capability terms and resolve to a registry alias (route, work, deep, reserve, bulk-local); the provider-native model id stays opaque outside the registry to keep model names out of the codebase. ToolPolicy is the capability envelope a spawned session runs inside: tier, allowed tools, read/write roots, advisory command allowlist, MCP mounts (empty by default), and granted danger caps.

Four danger caps are nonce-gated classes of effect, not tools: CAP_DELETE, CAP_EDIT_SECURITY (writes under system/**, config/**, .claude/**), a trading-execution capability that is never granted to any session and is inert because no trading tool is ever mounted, and CAP_GUI (drive the desktop, inherently unfenceable). The gate routes any ungranted danger op to the out-of-band nonce flow, since the PreToolUse hook cannot block on a human.

JobSpec is the self-sufficient, cold-restartable unit of work: kind, slug, instructions, cwd, tier, capability, context refs, turn/token caps, idempotency key, optional resume session, and an SDK-native reasoning-effort knob. The whole spec serializes to jobs.spec_json, which is the cold-restart artifact. UsageRecord and JobResult cover metering and provider returns; result files land under state/results/{job_id}/ (workers never write the DB). Gate verdicts are an Allow | Deny union; Authorization carries the governor verdict and a possibly-degraded alias.

Protocols

Subsystems talk via direct async calls with no message bus: Adapter (pure I/O translation), GateProtocol, RouterProtocol, RegistryProtocol, Provider (typed refusals, never raises), and GovernorProtocol (authorize plus charge).

The operational database (state/os.db)

SQLite in WAL mode with a single-writer invariant: the kernel is the only writer, subprocess sessions only emit result files it ingests, and the CLI opens the DB read-only. Base tables use CREATE TABLE IF NOT EXISTS; new columns come from a hand-written migration in db.py, each guarded by a PRAGMA table_info check so migration is safe to re-run on every boot. Key tables:

  • projects registry (slug, cwd, bound channel, durable conversational session id, per-project turn and context-occupancy counters feeding session-rotation telemetry, default tier, budget weight).
  • messages audit/dedupe log storing a content hash rather than raw text.
  • conv_window is the kernel-owned conversation window that replaced the SDK resume= transcript; the kernel appends one row per exchange and prunes to the last N per project, so stateless converse turns reassemble context from rows.
  • jobs holds the FSM with spec_json as the cold-restart artifact and a UNIQUE idempotency_key that stops the scheduler double-firing a cron window.
  • schedule seeds cron-style recurring jobs; usage_ledger is append-only metering the governor reads for the weekly cap; approvals is the T3 nonce ledger with a short TTL cleaned by a boot reconciler; audit records every gate allow/deny.
  • kernel_state is a key/value liveness scratchpad; eval_runs stores nightly, zero-token, deterministic golden-task pass rates that !health reads as a degradation trend; plans backs plan-mode dispatch, where a plan is delivered as a private GitHub issue and later accepted to dispatch a worker.

The four persistence layers, in order of authority

(1) state/os.db (SQLite, WAL) is transactional truth for jobs, sessions, budgets, schedule, and audit; the kernel is the single writer and subprocess sessions write result files it ingests. (2) The git-tracked wikis (Markdown) hold compiled, versioned knowledge. (3) Mission ledgers under .claude/ledger/ carry mission progress with proofs, the deep-recovery layer, so a cheaper model can resume from the first unproven milestone. (4) SDK transcripts, referenced by a stored session_id, are a best-effort cache and never canon. Crash recovery is therefore a non-event, since a fresh session rebuilt from a stored job spec plus the wikis can redo any work, and no ever-growing context exists anywhere.

The technology split is deliberate. SQLite (stdlib, WAL) was chosen for operational state over Postgres: single writer, one machine, zero ops, backup is a file copy, no new service on a shared box. Markdown-plus-git was chosen for knowledge over a database: human-readable, Obsidian-browsable, LLM-native, versioned, and merge-reviewable. Backup follows the same split: knowledge/ and projects/ are git-pushed nightly to a private remote; state/ is gitignored with a nightly SQLite .backup to a separate drive.

system/mirror_memory.py bridges layers 1 and 2. Canonical per-project memory lives outside the vault and outside git, so distilled knowledge was invisible in the vault and unpushed. The nightly job deterministically mirrors each project's memory into projects/{slug}/wiki/memory/ without migrating the originals (the adopt-in-place invariant: sessions and skills keep reading and writing the canonical stores). It is zero-token, write-if-changed, reads-only outside the vault, and destination-authoritative: only files carrying a mirror marker are deleted as orphans, while hand-authored notes are preserved and reported as foreign.


4. Security: gating, tiers, fences, and injection defense

The OS runs untrusted LLM sessions with real filesystem, shell, network, and optional desktop access on a personal machine that also hosts a separate high-stakes always-on workload and the operator's crown-jewel credentials. The governing principle sits at the top of gate.py: prompts are advisory, code is enforcement. Safety never depends on what a model was told, only on what the code permits, re-checked on every tool call, fail-closed, and audited.

Two layers split the work. system/gate.py is the pure decision core: given (actor, action, target, tier, granted_caps) it returns Allow or Deny, knowing the hard fences, the tier ladder, and the danger-cap classifier. It writes nothing. system/sessions.py is the single choke point over claude-agent-sdk, building each session with a per-tier tool list, a per-session sandbox, a PreToolUse hook that re-invokes the gate on every call, and a PostToolUse hook that scrubs fence leakage from search results.

Identity edge and tier ladder

Every inbound message clears an identity gate first, before tier, routing, or the LLM sees text. A CLI sender must be the local operator; a chat sender must be in the operator allowlist loaded from config.yaml; unknown adapters are denied. Because the allowlist is config living inside a write-fenced directory, no channel content can enroll itself.

Tiers are a monotonic capability ladder (T0 to T3) fixing both the SDK tool list and the gate verdicts a session can earn.

  • T0: Read, Grep, Glob, WebSearch, WebFetch, Skill, TodoWrite; no writes, no exec.
  • T1: adds Write, Edit; path.write to any non-fenced path inside the sandbox.
  • T2: adds Bash; command.exec for any command not on the danger-list.
  • T3: same tools as T2 plus external side effects, but only through the operator retyped-nonce approval flow. A session can never perform an external.effect directly.

Writes are allow-by-default within the sandbox and deny-by-fence, not an allowlist. Commands use a denylist: anything not danger-classified runs at T2 or above. mcp.mount is denied at every tier, reinforced by strict_mcp_config=True and empty mcp_servers in the SDK options. Unknown actions fail closed.

The deny model: fences

Fences are config-driven (system/config/fences.yaml), fail-closed (a missing or malformed file aborts kernel start), and self-protecting (the fences file is itself write-fenced). Three path classes plus a net fence:

  • deny_all_access (read and write denied, every tier, no bypass): <repo>/state (the kernel control plane: SQLite DB, KILLED file, CLI bridge, logs, secrets) and ~/.claude.json (the SDK root-of-trust and credentials).
  • deny_write_hard (read allowed, write never): the sibling workload's runtime state (diagnose, never touch), fences.yaml itself, the generated .claude/settings*.json, .claude/hooks, the machine-wide agent-rules file and its template, and the OS directories.
  • security_write_prefixes (write allowed only with the EDIT_SECURITY capability): the OS's own <repo>/system and <repo>/.claude. deny_write_hard wins where they overlap.
  • forbidden_net: currently empty; the local model port is reachable.

Path normalization is where correctness lives. _norm strips extended-length prefixes, resolves .., mixed separators, and symlink/junction targets, then casefolds; an unresolvable path yields a sentinel that matches no root, failing closed. Command checks expand every home-dir spelling, extract each path token, and test both fence sets, so a command that merely references a fenced path is denied. The code documents the honest limit: the command check is a speed-bump, not a sandbox (an interpreter can still reach a file via a string arg), with the stated future control being running workers as a restricted OS user.

Nonce approval and danger caps

Fences say never; danger caps say not without live out-of-band consent. Because the PreToolUse hook cannot block for a human, an ungranted danger op is denied with a machine-readable NONCE_REQUIRED:<CAP>:<target> marker, and the kernel runs the approval loop separately. Classes: CAP_DELETE (destructive commands, including history-rewriting git), CAP_EDIT_SECURITY (writes or mutating commands under a security prefix), CAP_GUI (desktop-control tools), and a trading-execution capability that is never granted to any session.

Nonces are 128-bit single-use (secrets.token_hex(16)), TTL 30 minutes, cleared across all pending approvals after five wrong guesses. A lapsed-but-real code auto-reissues a fresh nonce, since intent was already proven; a re-dispatched brief inherits an earlier grant. A boot-time reconciler fails any AWAITING_APPROVAL job whose newest nonce expired unapproved. A tool-less-cap guard force-upgrades a danger-cap dispatch off a tool-less local completion engine to a cloud worker, since such an engine cannot touch disk and would otherwise fabricate the edits.

Config-to-settings drift closure

fences.yaml is one source of truth feeding two layers: the in-code gate and the SDK-layer denylist at .claude/settings.json, the latter generated deterministically by settings_gen.py. This closes drift that once left settings.json denying reads the gate had come to allow. The generator intentionally omits security_write_prefixes denies so a nonce-approved edit is not double-blocked.

The shadow finding

claude-agent-sdk exposes a can_use_tool permission callback that looks like the enforcement point but does not fire. In SDK 0.2.116 it is shadowed: entries in allowed_tools auto-approve before the callback, and the default permission mode auto-approves read-only tools. Verified empirically, the callback fired zero times and a fenced Read leaked. Enforcement is therefore a PreToolUse hook, which fires under every permission mode. Consequently allowed_tools must stay empty (a regression test asserts this), and workers run permission_mode="bypassPermissions" to avoid a headless prompt stalling forever, which suppresses only the harness prompt while the hook still enforces every fence. A PostToolUse scrub strips references to all-access-fence paths out of legitimate repo-root Grep/Glob results before they reach the model.

GUI, injection defense, and secrets

CAP_GUI is the honest edge case: a desktop-control MCP server driving screenshot/click/type/key cannot be path-fenced, because pointer primitives operate outside the gate's view. It is nonce-gated; open_app is fence-checked twice and launched without a shell to close the shell-injection path, but a GUI worker could still navigate a browser to a sensitive service, so GUI safety is only as strong as the approved task. The full fix remains a restricted OS user.

Injection defense rests on the invariant that all enforcement is identity plus tier plus sandbox, none of it prompt-dependent. A four-payload red-team corpus is a permanent regression fixture: a non-operator command dies at the identity gate; a guessed approval is a 128-bit unknown; a poisoned ingested document is read as data and write-confined to .staging/knowledge, reaching canonical knowledge/ only through an explicit human-diffed promote; and an operator instruction to read a fenced path is denied by the sandbox regardless of the text. The ingest firewall also refuses reparse points so a junction cannot redirect a copy into a fenced directory. Dispatch directives are extracted by a string-aware brace scanner with a deterministic repair pass, then validated against fixed allowlists, so the model proposes and the code disposes.

Secrets never transit the LLM and have no in-tree store. Credentials and the kernel control plane sit under the deny_all_access state/ fence; SDK credentials sit under the ~/.claude.json fence; the sibling workload's runtime state is read-only. The one acknowledged residual is exfiltration by a trusted T2 shell or an approved GUI worker, contained today by trusted-task assignment, nonce gating, and full audit, and fully closable only by a restricted OS user.


5. Sessions, routing, and the model registry

Three modules sit between an inbound message and a running Claude Agent SDK session: router.py (which project and security tier), registry.py plus config/registry.yaml (which model, chosen by capability and never by name), and sessions.py (the single security-enforcing choke point over the SDK).

Router

router.py is deliberately tiny and token-free. decide(msg) does a free table lookup: a channel bound to a project routes there, otherwise a designated fallback project row. The security tier is a column on the project row, not something the router computes. The LLM route-classifier for multi-project disambiguation is seamed but unimplemented, so decide() always returns CONVERSE at confidence=1.0. Its one real classification is reserve-word detection: a case-insensitive whole-word regex compiled from reserve_trigger_words in config (trigger words are config, never code).

Registry

config/registry.yaml is by hard invariant the only file where model names may appear; a grep for model-family words across system/ excluding config must return nothing. Orchestration code requests capabilities and receives an opaque ModelHandle, so model choices change with a YAML edit. Six aliases (route, work, deep, reserve, bulk-local, codex) carry provider, capability set, tags, and a cost_order. work deliberately covers the bulk capabilities plus the cloud set, making it the cloud fallback. bulk-local is operator-pinned to a specific local model verbatim and fails loudly (exit 5) rather than silently substituting; it is tagged as gated to the reserved GPU window it shares with the sibling workload.

Registry.resolve(req): a preferred_alias short-circuits everything (unknown alias raises); no-<tag> constraints subtract tags (no-local skips the local engine); explicit-only aliases (reserve, codex) are never auto-picked; otherwise the first alias in cheapest-first cost_order whose capability set is a superset of the request wins, else LookupError (fail loud, never a silent default).

Tiers to tool allowlists, and policies

Tier is the security tier, distinct from the model alias. TIER_TOOLS maps T0 (Read/Grep/Glob + net + meta) up through T3, adding Write/Edit at T1 and Bash at T2. T2 and T3 share an identical tool list; T3's external-side-effect escalation is enforced by the gate via nonce-gated danger caps, not extra SDK tools. Three families are appended conditionally: computer-use tools (only when a worker dispatch carries gui=true, itself derived from a granted CAP_GUI), local-dispatch tools, and a single read-only hybrid-wiki-retrieval tool (mcp__memory_search__search, no path arg, reads a fixed prebuilt index).

ToolPolicy is an immutable per-session record. Four builders differ by sandbox scope. policy_for_tier (conversational) reads ROOT plus cwd plus adopted memory dirs but confines writes to the repo ROOT only, never an adopted repo's real cwd. worker_policy (dispatched workers) reads and writes in place, with per-path fence enforcement in the gate; scope='repo' narrows back to ROOT plus cwd for untrusted tasks. no_tools_policy grants no tools and no MCP at all, so the end-of-turn distiller cannot wander off globbing files. distill_policy is tightest: T1, writes only inside the memory dir.

Request to running session

Route, pick preferred alias (reserve on a trigger word, else the configured converse alias, which resolves the whole conversation on the top reasoning model), resolve to a handle, then governor.authorize. The governor returns PARKED (no session), DEGRADED (caller re-resolves one rung cheaper down reserve -> deep -> work -> route past a spend threshold), or OK. Reserve is re-checked kernel-side for dispatched workers: model=reserve is honored only for an operator-typed command or an origin message containing a trigger word, else silently downgraded. After a terminal worker result, _maybe_local_fallback re-dispatches a tool-less local worker that did not finish once on cloud, adding a no-local constraint so it cannot resolve back to the engine that just failed.

The SDK choke point

Every session goes through Sessions.converse -> _options -> claude_agent_sdk.query. _options builds ClaudeAgentOptions with an opaque model id, SDK-native effort, policy tool availability, bypassPermissions only for headless workers, hooks, and strict_mcp_config=True so nothing ambient can mount. _decide_tool checks in order: tier allowlist, meta-tool passthrough, per-MCP rules, ..-segment rejection, action mapping (unmappable denies, fail-closed), read/write root containment, then the gate. Four hooks assemble via _hooks_for: PreToolUse, PostToolUse (scrubs fenced paths from Grep/Glob output while preserving shape), and PreCompact (side-effect-only telemetry plus a wiki breadcrumb, never raises) apply to every session; UserPromptSubmit is converse-only and performs per-turn push retrieval, injecting up to ~1500 chars of freshly retrieved wiki snippets as additionalContext. This push half plus the pull memory_search tool are how retrieval actually reaches the model.

Stored-context inlining

converse_context.build_stored_context addresses a retrieval-audit finding: the converse path previously handed the model only path pointers to its memory. With embedding-based retrieval deferred, the fix at a small repo count is deterministic inlining of a zero-token, byte-capped digest (memory head, wiki learnings, context snapshot, plus an all-repos roster). It is seeded only at context-loss moments (cold start or just-rotated session, resume_id is None), since a resumed session already holds it.


6. Orchestrator, workers, and context assembly

The intelligence layer turns an inbound message into either a spoken answer or real work in a repo. It lives almost entirely in system/kernel.py, with three helpers: system/context.py (the worker brief), system/converse_context.py (the stored-knowledge digest), and system/plans.py (!plan/!acceptplan).

The orchestrator/worker split

The layer keeps two kinds of Claude session distinct.

  • Orchestrator (the brains): one durable per-project session, resumed every turn. It talks with the operator, decides answer-versus-dispatch, composes briefs, and synthesizes worker results. It runs on the deep/converse tier at max_turns=20, and its prose is shown verbatim.
  • Worker: a fresh or resumed headless session per job, run via the worker pool. It does real work in one repo (edit files, run commands) at whatever tier the directive names, up to max_turns=150. Its report is never shown raw.

The invariant is that the orchestrator never does repo work and the worker never talks to the human. A worker result is fed back as an inbound turn to the orchestrator (_relay_worker_result_to_orchestrator), and only the synthesized reply reaches the channel. Orchestrator behavior is steered by a text protocol prepended to every turn (ORCHESTRATOR_PROTOCOL), not an in-process SDK tool. The dispatch decision is a fenced ```dispatch block the kernel parses, chosen over an MCP tool because it is fully testable and independent of SDK MCP internals that cannot be live-verified.

The dispatch directive

respond runs a non-! message through router.decide, selects an alias, authorizes against the governor, builds per-turn context instructions, runs the shared _converse_turn, then parses a dispatch directive from the pristine reply. The directive is a one-line JSON header plus a plain-text brief, since plain text needs no JSON escaping and Windows paths survive. Header keys include tier, project (registered slug to its cwd), model (registry alias), effort (independent of model), max_turns (clamped to 150), resume (continue an existing session, including the operator's own desktop/CLI sessions, which works because the daemon is not a nested claude), danger_caps, gui, and engine. Two out-of-band single-line markers, LEARNING: and ATTACH:, are stripped before the human sees the reply.

The fence parser is escaping-hardened. Plain json.loads choked on LLM-composed Windows paths (C:\Users becomes an invalid \U escape), which silently posted the raw block instead of dispatching. DISPATCH_RE captures the fence body, _scan_json_object finds the first balanced {...} with a string/escape-aware scan, and parse_dispatch_directive tries json.loads(strict=False) then one _repair_json pass. A fence that existed but failed to parse is surfaced as an explicit warning, never rendered as raw markdown.

Directive to queued worker

_dispatch_from_directive turns a parsed directive into a jobs row: resolve the target project, build the brief with pack_context (paths, not bodies), filter requested danger caps to the grantable AUTO_GRANT_CAPS set, resolve model/effort, apply the tool-less danger-cap guard, resolve resume, then enqueue. A trading-execution capability is never grantable and is dropped here. No danger caps enqueues QUEUED immediately; danger caps enqueue AWAITING_APPROVAL with a 128-bit nonce and a 30-minute TTL. Reserve is enforced in kernel code, never in prompt text: a model that asks for reserve on its own gets a silent downgrade with a surfaced note.

The tool-less-provider guard

TOOLLESS_PROVIDERS = ("local_offload",) are single completion calls with no file tools, no shell, and no MCP, so a ToolPolicy or a granted danger cap is physically meaningless on them. After an incident where such a worker was granted EDIT_SECURITY, could only role-play the edits, and fabricated file paths and before/after quotes, three defenses were added: refuse danger caps on tool-less aliases (auto-upgrade to default cloud, keyed by provider so future completion providers inherit the guard), a deterministic disk-verify stamp, and auto-fallback to cloud.

The stamp uses _worktree_fingerprint, a sha256 over git rev-parse HEAD plus git status --porcelain plus git diff HEAD plus (mtime_ns, size) of each untracked file. An identical value before and after means nothing changed on disk. HEAD is included so an edit-and-commit worker is not misread as "did not land." _disk_verify_stamp prepends a [kernel disk-verify: ...] line at the top of the report so relay truncation cannot cut it. _maybe_local_fallback re-dispatches a non-DONE tool-less run once on cloud; _maybe_auto_continue resumes the same session once after a [TURN-CAP] failure. At most one successor fires per terminal result.

The worker pool

WORKER_CONCURRENCY = 2 is deliberately small because the machine also runs a separate high-stakes always-on workload that shares finite resources. The main loop claims QUEUED workers up to the cap each tick via jobstore.claim_worker (the atomic QUEUED-to-RUNNING transition), each becoming an asyncio.Task. run_worker runs one worker end to end and never raises to the caller: rebuild the JobSpec, resolve granted danger caps and a provider handle, build worker_policy, fingerprint before, run the engine, fingerprint after, write the result file, charge usage to the governor, call jobstore.finish, then run the successor gate. Cancellation marks INTERRUPTED and re-raises; any other exception marks FAILED; in all cases the done-notifier runs.

The worker brief (system/context.py)

pack_context(project, task, hints) returns (instructions, context_refs). It emits PATHS, not inlined bodies (prompt-cache-friendly; the worker Reads what it needs inside its own budget), capped at MAX_REFS = 8 in priority order: adopted MEMORY.md, .claude/repo-map.md, wiki/index.md, wiki/pages/learnings.md, a context-snapshot digest, handoff.md, a machine-wide os-learnings.md, and a staged-skills index. Instructions also carry WORKER_RULES that override the machine default of reaching for heavy skills first, which is right for interactive sessions but kills turn-capped workers (observed workers burning whole budgets standing up mission-ledger protocols). The rules: do the task directly, decide-and-document since they cannot ask mid-run, py -3 never bare python, local LLMs only via the offload wrapper, exclude .claude/ via .git/info/exclude.

Stored-context assembly (system/converse_context.py)

This answers the requirement that the orchestrator be a centralized model that knows all repos and compensates for context loss with stored information. The old converse path handed the model only path pointers and hoped it would Read them within the turn cap, and each turn saw only the single channel-mapped project. There is no embeddings/BM25 retrieval on this path (deferred to an observed-miss trigger), so at roughly a dozen small repos the fix is deterministic inlining. build_stored_context returns a compact byte-capped digest, zero-token, never raising: a [STORED CONTEXT] header telling the model the OS auto-loads this every turn; for the current non-home project, the MEMORY.md head, wiki learnings.md, and .claude snapshot each cut to a per-section byte cap; and an all-repos roster (one line per active project mapping slug to cwd, wiki, and memory path).

Per-section caps are tight because this rides every converse turn (MEM_BUDGET=1500, LEARN_BUDGET=1200, SNAP_BUDGET=800, ROSTER_MAX=40). Truncation reuses a single head+tail cut helper with an explicit [...TRUNCATED n chars...] marker so both the opening summary and a trailing section survive. The digest is injected in _converse_turn only when resume_id is None, the two context-loss moments, since re-sending on every resumed turn had stacked about 1.3K tokens per turn and accelerated rotation.

Underpinning this, resolve_memory_dir honors a project.yaml memory_dir override (registration truth) before the cwd munge, then the parent munge for nested repos, but never falls through to home memory. This fixed a split-brain in a sibling repo where a worker natively creating the exact-cwd munge dir silently flipped resolution off the rich parent-munge store onto a fresh near-empty silo, and it is guarded against binding a non-home project to home memory.

Durable-session rotation, then a kernel-owned window (system/conv_window.py)

A per-channel session resumed every turn grows unbounded until the CLI auto-compacts lossily (context-editing betas are not plumbed in the SDK version in use). One phase rolled the session via carry-over summaries; a later phase dropped resume= entirely, moving turn history into the kernel's own DB so the SDK transcript is disposable. Both paths run inside _converse_turn under a per-project lock serializing read -> converse -> persist so two turns on one channel cannot double-rotate or race.

Stateless path (current for every project): resume_session_id is forced None every turn, so nothing rotates. conv_window.recent_turns reads the last MAX_TURNS=8 exchanges from the conv_window table (per-turn-capped, total-bounded, dropping oldest first), rendered as a [RECENT CONVERSATION] block spliced in right after the byte-identical ORCHESTRATOR_PROTOCOL prefix (cache-friendly). After each DONE non-empty turn, append_turn records the exchange and prunes to KEEP_ROWS=24.

The legacy resumed path remains as a fallback: _should_rotate is true when conv_turns >= 40 OR conv_ctx_tokens >= 120_000, where the token metric is the last turn's occupancy rather than a cumulative sum that would overcount resumed history. _make_carryover runs one cheap turn asking the old session to summarize itself for a successor, trimmed and mirrored to wiki/pages/handoff.md.

Plan mode (system/plans.py)

!plan <slug> | <task> dispatches a read-only T0 planning session (the SDK's headless plan mode is undocumented, so a T0 session is the reliable equivalent). It formats a prompt mandating Goal / Approach / Steps / Files-touched table / Risks / Definition-of-Done, runs a Tier.T0, max_turns=20 job, and delivers the plan as a private GitHub issue on the OS repo via the authenticated gh CLI (GitHub renders the markdown tables, an issue opens as a phone web page, and private keeps it private). !acceptplan approves the latest pending plan and dispatches a worker to execute it, the plan text becoming the brief. Only the operator triggers either command.


7. Providers and adapters

The OS has two pluggable seams at its edges. Adapters (system/adapters/) are the inbound/outbound transport boundary. They translate a chat transport into InboundMessage / OutboundMessage and enforce the sender allowlist at the very edge. Providers (system/providers/) are the execution boundary. Each runs one JobSpec against one model on some engine (the Claude Agent SDK, the local llama-swap models via offload.cmd, or a foreign headless CLI such as Codex) and always returns a typed JobResult rather than raising. A third piece, system/local_dispatch.py plus system/mcp_servers/, exposes local-model dispatch into a live orchestrator session as MCP tools and adds nonce-gated GUI desktop control. Both seams share one philosophy: the boundary object is a value. An edge drop is a reported audit fact, not an exception; a gated local model is a ResultStatus.GATED value, not a crash.

Adapter contract

contracts.Adapter is the Protocol; BaseAdapter is the ABC. A concrete adapter implements name, run(on_inbound), and send(msg). The kernel injects is_allowed_sender and on_edge_drop. The shared edge_allows silently drops a denied sender (refusing to reply is itself the anti-abuse behavior) while reporting it through on_edge_drop, so the adapter never touches the DB. The gate re-checks identity in-core, so the edge allowlist is a redundant first cut, not the security boundary.

Chat adapter (Discord in this build). A discord.py bot whose token is read by the kernel process from state/secrets/ and never enters any LLM context. It is a lazy import; if the token or the package is missing, the daemon still boots on the CLI adapter alone. Its edge policy is a four-layer drop ladder (bot authors ignored, guild allowlist, sender allowlist, then accept). Operator attachments download into an inbox as untrusted data with sanitized filenames. Outbound uses post-and-edit with no token streaming, line-aware chunking against the platform's per-message cap so nothing is truncated.

CLI adapter. Both the conformance reference and the always-mounted out-of-band ops channel that works with the chat transport down. Allowlist defaults to the local operator only. Two inbound sources: in-process submit for tests, and a filesystem bridge under state/cli/ where osctl say drops in/{id}.json and the daemon writes out/{id}.txt.

Provider contract

providers/base.py re-exports contracts.Provider: a name plus async run(spec, handle, policy) -> JobResult that NEVER raises for GATED/DOWN/CAP_EXHAUSTED. This typed-refusal invariant is what the fallback machinery depends on. ResultStatus is DONE | FAILED | GATED | DOWN | CAP_EXHAUSTED | INTERRUPTED. JobResult carries status, session_id (Claude only), usage, retry_after_ts, and an error slot that by convention carries the actual reply text even on DONE, so the kernel reads .error uniformly.

ClaudeSdkEngine is the thinnest engine, a wrapper around Sessions.converse. All real SDK options (preset prompt, the PreToolUse gate hook, setting_sources, mcp_servers, effort) stay in sessions.py, so behavior is byte-identical to calling converse. It is the only engine that can produce CAP_EXHAUSTED from a live subscription cap and a resumable session_id.

LocalOffloadProvider runs the local llama-swap models exclusively through offload.cmd, the machine-level hard rule (never a raw port call, never a force flag, never a reimplemented gate). Every dispatch passes an explicit --model pin (for example qwen3.6-27b) rather than a capability-to-role mapping, because the role/auto layer silently remaps to whatever is served, which once produced a fake benchmark that ran on the wrong model. An explicit pin either runs the named model or fails loudly (exit 5). The subprocess uses list-form argv (no shell=True) and pipes the prompt over STDIN. That last point is a real fix: a .cmd runs through cmd.exe even with shell=False, which truncates a positional argument at its first embedded newline, so multi-line prompts must ride stdin. Because this engine is a single stateless, tool-less text completion, three mechanisms keep a dispatched worker honest: a preamble telling the model it has no tools and must reply CANNOT-EXECUTE: <reason> rather than fabricate evidence, a notice warning the relay that any file-operation claim is fabricated, and mapping CANNOT-EXECUTE to FAILED to trigger cloud fallback. The exit-code map: 0+text is DONE; 0+truncated is FAILED [LOCAL-TRUNCATED]; 0+blank or 1 is FAILED [LOCAL-EMPTY]; 2 is DOWN; 3 is GATED with a next-reopen hint; 4 is DOWN.

Foreign-CLI engines. CodexCliProvider spawns the Codex CLI headlessly and states its trust boundary honestly: Codex has its own sandbox model, so the PreToolUse hook cannot run inside it, making it strictly weaker than a Claude session. It refuses T3 (external effects) outright. The generic HeadlessCliProvider abstracts that shape so a fourth AI brand is a BrandSpec registration (a build_args and a parse_output) rather than a new provider file. It enforces the trust boundary in code: T3 refused, and _forbidden_flag scans the built argv for bypass tokens (bypass, dangerously, no-sandbox, --yolo, etc.) and refuses before spawning.

Dispatch and fallback

ProviderDispatch is a callable map keyed on handle.provider. An unknown provider degrades to a typed DOWN, not a raise, so it flows through the same fallback path. Two fallback mechanisms key off the returned JobResult. Local-to-cloud fallback fires when the provider is tool-less, the result is not DONE, and it is not already a fallback (chain capped at one hop); it re-enqueues the same brief with a no-local constraint so it cannot resolve straight back to the failed engine. Turn-cap auto-continuation resumes the same SDK session for a FAILED result carrying a session_id and a [TURN-CAP] marker.

In-session local dispatch and MCP servers

local_dispatch.py is the in-session orchestrator view of the local models: independently testable functions plus runtime lifecycle, none raising. assemble_local_context inlines the actual trimmed text (not paths, since the model has no filesystem), greedily filling a char budget from prompt, then MEMORY.md, then repo-map.md, then the project wiki index.md. should_dispatch_local is tuned from a real benchmark: the cliff is required output length and open-endedness, not difficulty, so it refuses a danger-surface keyword set (trading, credentials, kernel.py, fences.yaml, delete/drop-table/irreversible), long-output keywords, and anything over 150 LOC or one file, while allowing mechanical and bug-finding tasks that benchmarked at cloud parity. The bias, stated plainly: the local model fails legibly on drafts (empty or truncated) but role-plays success on action-shaped tasks, so this seam is only for drafts the calling session verifies.

Server lifecycle lives here, not in the inference-only provider. The llama-swap runtime is the operator's own shared server, so the OS may start and restart it at any time (for example to reload a config change). stop_local_runtime is scoped to two process names (llama-swap, llama-server) so it can never reach the sibling workload's own runtime processes. The single caveat is physical: the GPU is time-shared during a reserved window, so a restart then competes for VRAM.

mcp_servers/ wires these seams into a live SDK session, mounted only when a policy asks, under strict_mcp_config=True so nothing else can mount. local_dispatch_tools.py exposes six mcp__local_dispatch__* tools whose context-bearing tools close over cwd/project_slug captured at mount time from the session's own JobSpec, never accepted as a tool argument; binding at mount is what keeps the read-root fence meaningful. computer_use.py is a nonce-gated CAP_GUI desktop-control server (ctypes/user32 input, Pillow screenshots), gated like DELETE precisely because it cannot be fully path-fenced. memory_search_tools.py exposes hybrid BM25-plus-semantic wiki retrieval as one tool taking only query and k, reading a single fixed index (state/wiki_index.db) in-process, read-only, on a thread to avoid blocking the loop during ONNX embedder cold-load.


8. Budget, usage accounting, and evaluation

Frugality is an architectural invariant enforced in plain Python before any Claude session spawns. Every cloud token is admitted through a governor that consults a rolling weekly-spend window, charged into an append-only ledger, and reconciled against a machine-wide, ToS-safe estimate of the operator's account-level subscription usage. A nightly zero-token regression eval proves the safety fences and cost-routing have not drifted.

Three-layer accounting

The operator's Claude subscription is one pool shared across the whole machine. Interactive sessions and the OS's autonomous work draw the same limits, so the OS cannot answer "how close am I to my limit?" from its own ledger alone. Three layers cooperate. The ledger (governor.py + usage_ledger table) charges from SDK-reported usage and sees only tokens the OS routed, driving admission control and per-alias/provider attribution. The machine-wide layer (usage_local.py) parses the operator's own ~/.claude/projects/*/*.jsonl transcripts to see all Claude Code usage on the box, giving the numerator for a true percent-of-limit answer. The cap denominator (governor.effective_cap) is config weekly_cap_tokens, else a value learned from a circuit-breaker hit. Guiding rule: read your own local logs, never impersonate the client to a server. No network call, no usage endpoint, no token replay, no credential access.

The governor

The governor is the admission oracle, but the kernel is the sole DB writer. charge() is the only INSERT and runs only in the kernel process. Thresholds apply to projected spend (_spent() + est_tokens) / cap. Below 75 percent, OK. At or above 75 percent (degrade_threshold), DEGRADED one rung down the ladder. At or above 80 percent (interactive_only_threshold), background work parks while interactive keeps degrading. Background-only spend at or above 25 percent (autonomous_fraction) also parks background. At or above 85 percent (os_cap_fraction), the OS parks everything, reserving the rest for the operator's direct use.

LADDER = {"reserve":"deep","deep":"work","work":"route"}; route is the floor. Uncapped aliases (bulk-local, the free local model, and codex, a separate subscription) never ladder and are excluded from the cap denominator, so a local or codex run never wrongly parks Claude work. Admission uses the capped _spent(); reporting uses the all-inclusive total_week().

Circuit breaker and cap discovery

The ladder is a soft guard against an estimated cap; the reactive circuit breaker is the hard backstop for the true subscription ceiling the OS cannot see. In sessions.py, any SDK exception whose text contains "limit" and ("usage" or "rate") maps to CAP_EXHAUSTED; jobs.finish() maps that to JobState.PARKED_CAP. On a CAP_EXHAUSTED converse turn the kernel calls record_observed_cap with the machine-wide 7-day total, which at the instant the limit trips approximates the real cap. That value is written monotonically (only ever raises), so noise never shrinks a learned cap. A PARKED_CAP job waits for operator re-dispatch (documented auto-resume at window reset is not yet implemented, an honest gap).

Nightly degradation eval (evals.py)

Suite core-v1, deterministic and zero-token by design, catching code/config regressions. check_fence_refusal is the safety canary proving the crown-jewel fences still DENY a worker. check_usage_classification proves a usage question is still caught while work passes. check_routing_tiers proves classify routes cheap and architecture routes to deep/reserve. check_local_model_judge grades a correct answer PASS and a wrong one FAIL as a zero-Claude-token model-drift signal.

The fence canary is hardened against false passes. Deny probes must all be denied: path.read of state/os.db and .claude.json (deny_all), and a path.write into the sibling workload's runtime state (deny_write_hard). Each denial reason must contain "fence", so a typo'd action does not falsely pass. A positive control requires that a READ of the sibling workload's state is ALLOWED, proving the gate is in normal operation rather than a blanket deny. run_suite excludes skips from the denominator, coerces unknown statuses to fail, and treats a raising check as fail without crashing the job. format_health powers zero-token !health: latest pass-rate, current failing checks, and a trend across recent runs.


9. Memory and knowledge (the llm-wiki system)

The knowledge model is compiled state rather than retrieval. An LLM reads each source once, integrates it into a persistent cross-linked Markdown wiki, and everything downstream reads that compiled wiki. The recorded rationale is that standard RAG re-derives synthesis from raw chunks on every query while a wiki compounds. Obsidian ([[wikilinks]] plus YAML frontmatter) is the human browse surface over the same files. A rebuildable BM25-plus-optional-vector cache (system/wiki_index.py), exposed through a memory_search MCP tool, sits over the compiled wiki as a searchable fallback.

Two wiki scopes plus a mirrored layer

All are plain Markdown under the vault root, git-tracked and Obsidian-browsable. System knowledge lives in knowledge/ (durable facts about the operator, the machine, tools, preferences, OS design decisions), written by confined ingest, lint-fix, and home-learning appends. A per-repo project wiki lives in projects/{slug}/wiki/ holding a .claude snapshot, accumulated learnings, and a memory mirror. A nightly read-only mirror under projects/{slug}/wiki/memory/ copies each repo's canonical in-place Claude Code memory. The adopt-in-place invariant is central: an adopted repo's real .claude memory and repo-map stay where they are and keep working; the wiki holds only snapshots and mirrors, never the master copy. knowledge/ carries index.md (read first on any query), append-only log.md, lint-findings.md, an operator-curated lint-accepted.md mute list, pages/, immutable raw/, and staged skills/.

The librarian

system/librarian.py runs each wiki operation as a confined Claude session and never writes os.db, returning usage for the kernel to charge. Operations are ingest (T1, write-confined to staging), promote (a synchronous, git-committed, diff-reviewed mirror of staging into knowledge/), query (T0 read-only, index-first), lint (T0 read-only), and lint-fix (T1). The injection firewall is enforced in code rather than by prompt. An ingest session may write only to .staging/knowledge, deliberately outside the fenced state/, so a malicious document lands its edits in staging and they reach knowledge/ only through the separate promote step. Ingest first refuses any source tree containing a Windows reparse point, then stages a fresh copy and runs the write-confined session under instructions to treat contents strictly as data.

The lint ledger

Lint was previously stateless and re-reported the same findings nightly. lint-findings.md (machine-rewritten each run) holds open findings; lint-accepted.md (operator-edited, never auto-touched) suppresses settled items. Each lint session ends with one fenced ```findings block, one line per finding keyed by a stable kebab key and categorized mech (auto-fixable the same night) or judge (waits for the operator). Reused stable keys mean a fixed finding drops off because it is gone, not because the fix was assumed to work.

The curator

system/curator.py is deterministic and pure (zero LLM tokens), so it is reliable on a live box and unit-testable. It dedups and caps the otherwise append-only learnings.md pages. A line-anchored header regex and a prefix regex strip date and worker stamps so the same lesson on different dates shares one key. curate_learnings keeps the most-recent occurrence per key, caps to the most-recent N bullets, preserves non-bullet prose, and returns the original bytes unchanged when nothing was pruned. The kernel runs it before the LLM distill sessions so consolidation happens even when distill is budget-parked.

Reparse-point defense

find_reparse guards against a Windows directory junction (which os.path.islink misses) or a symlink smuggling fenced bytes into staging. It checks the reparse tag or symlink flag, fails closed on any OSError, walks without descending junctions, and is called by ingest, promote, and the folder-backup path.

wiki_index: hybrid retrieval

system/wiki_index.py builds a rebuildable, kernel-owned, gitignored cache (state/wiki_index.db) so sessions search compiled knowledge instead of guessing files. Two lanes fuse: BM25 (stdlib SQLite FTS5, porter tokenizer, the zero-dependency floor) and an optional vector lane (sqlite-vec KNN over fastembed/bge-small-en-v1.5 CPU ONNX embeddings, no GPU, avoiding the sibling workload's GPU fence), gated by with_vectors= and degrading transparently to BM25-only. Ranked lists blend via Reciprocal Rank Fusion (K=60). Pages chunk by Markdown heading. A recency lane reorders near-tied results newest-first by dated bullet, with a frontmatter opt-out; headings starting "superseded" are excluded at build time. An incremental embed cache keyed by sha1(model + chunk) re-embeds only changed chunks. Exposed to orchestrator and worker policies as read-only mcp__memory_search__search, enabling cross-repo retrieval.

consolidate: bi-temporal supersedence

system/consolidate.py catches semantic supersedence that exact-key dedup misses. It invalidates rather than deletes: an older bullet moves to a ## Superseded section tagged with the newer date, staying git-tracked but excluded from retrieval. Candidate pairs are found deterministically (strictly-newer dates, token-Jaccard >= 0.34, distinct text, capped per page), then a bounded local-model yes/no judges each pair, gate-aware and zero cloud tokens.

extract: active extraction from worker transcripts

system/extract.py captures repo-specific insight from completed worker sessions. Workers are identified reliably from kind='worker' rows carrying session_id (plumbed at dispatch-result time), and transcript_path locates the .jsonl via the cwd dir then a store-wide UUID glob so resumed transcripts under another repo's dir are still found. assistant_digest concatenates assistant text (byte-capped) into a bounded local-model distill emitting 0-3 durable facts or NONE, appended through the curator. A composite (finished_at, job_id) watermark prevents re-mining; gated or failed runs pause without advancing.

skills_install: guarded auto-install

system/skills_install.py auto-installs minted skill playbooks into ~/.claude/skills/. Guards: well_formed requires a substantive body; stamp_provenance writes provenance: auto-installed plus status: probation; _safe_name requires a single path component; auto_install_staged never shadows a hand-installed skill and records each install in a git-tracked manifest so revert() removes exactly what was auto-added.

The self-improvement loop

The loop rides the synthesis turn that already runs. A worker's lesson becomes a LEARNING: line, appended to in-place memory and the wiki, deduped by the deterministic curator, consolidated by the weekly distill, and mined into staged skill playbooks. The nightly extract job separately mines each completed worker's full transcript through a bounded local-model distill into the same project wiki, catching mid-task process insight the single self-reported LEARNING: line misses.


10. Operations, configuration, and scheduling

How the platform is operated: the local control CLI, deterministic background jobs, project lifecycle, core config, the internal scheduler, process supervision, and the test suite.

osctl (local operator CLI)

system/osctl.py is the always-available local control surface, usable even when the chat transport is down. It is the only surface allowed to mutate access configuration, never channel content. It reconfigures stdout to UTF-8 because Windows consoles default to cp1252 while model replies carry arrows and dashes. A file bridge under state/cli lets a human drive the live daemon's orchestrator conversation from a terminal. Commands: status (read-only DB view of active projects, jobs-by-state, schedules, kernel PID, heartbeat age, KILLED flag), init (apply schema.sql), kill/unkill (write/remove the state/KILLED sentinel that halts dispatch while leaving the daemon alive), say/ask (post a JSON message into the bridge and poll for a reply), and stop-daemon/start-daemon.

Deterministic background jobs

system/background.py holds zero-token, kernel-side helpers. The morning digest is built entirely from the DB (projects, 24h message and job counts, 7-day token sum). A consistent online sqlite3 .backup() copies state/os.db to a backup drive with 30-day pruning, safe while the daemon is live. An arbitrary-folder copy backs the onboarding flow; the caller must pre-check the source is reparse-free so copytree cannot dereference a Windows junction. Nightly git_push commits knowledge/ and projects/ through a pinned full-path git.exe (msys2 git mishandles C:\) under a fixed machine identity, distinguishing a real push from a no-op after a no-op once overclaimed and made the report unreliable.

workspaces (project lifecycle)

system/workspaces.py owns CREATE (a new OS-native project) and ADOPT (an existing codebase adopted in place, cwd pointing at the real location, its .claude memory read in place and never migrated). resolve_memory_dir(cwd) precedence: (1) an explicit memory_dir override recorded in project.yaml at registration, unless it would bind machine/home memory to a non-home project; then (2) the cwd "munge" (Claude Code's per-project dir name, every non-alphanumeric becomes -); then (3) the parent dir for nested-repo layouts, but never falling through to home. A home-guard drops the parent candidate when it munges to the home directory. Registration-time truth beats munge-guessing, a fix for a split-brain where a native SDK session created the exact-cwd munge dir and flipped resolution to a near-empty silo. project.yaml is the disk-durable git-tracked record; the DB row is live routing truth; the two are kept in sync so a cold restart can rebuild routing from disk. new_repo runs all refusal checks before any FS mutation, then git-inits and writes .claude/ into .git/info/exclude (never .gitignore).

claude_md_sync

The kernel keeps the machine CLAUDE.md current with an owner-identity canary and a skills catalog. The harness hard-refuses any session write to a file named CLAUDE.md, but the kernel is plain Python, not a session, so it is exempt; the target is also in fences.yaml deny_write_hard, so no spawned session can write it. Managed content sits between markers, is idempotent write-if-changed, and lives in a reviewable git-tracked template rather than the .py.

config.yaml and the scheduler

system/config/config.yaml is mutable only from the local machine, never channel content. Keys cover routing (the reserve tier is never auto-selected, only on trigger words; the orchestrator runs on the top-tier reasoning alias every turn), an adoptable map of a handful of codebases, the operator allowlist, the budget ladder, and context settings. Turn rotation is effectively OFF after the live incident where a mis-calibrated 120k-token threshold fired on every turn; the stateless-turn window is set so the kernel owns the conversation window and the SDK resume= is dropped.

system/scheduler.py is the daemon's own SQLite cron, polled each tick. It deliberately creates no Windows Task Scheduler entries so it cannot collide with the sibling workload's tasks. It is reboot-safe via a UNIQUE idempotency key {prefix}:{minute-window} with INSERT OR IGNORE. Standard 5-field cron. seed() is idempotent by name and reconciles cron/kind for default-named rows. The catalog runs off-hours on local wall-clock, a nightly block then a weekly block, ending with daily mirror-memory and git-push last, before the reserved GPU window: eval (deterministic golden checks plus a local-model judge), wiki-lint (LLM, auto-fixes mechanical findings only), digest/backup (deterministic), auth-check (a 1-turn classify, quiet unless failed), distill and skill-mint (weekly), sync-claude-context/sync-claude-md, and platform-doc (regenerates only stale sections). Deterministic kinds cost zero tokens; LLM kinds request their own governor tier. The KILLED sentinel halts both scheduler and workers.

Process supervision, dependencies, tests

A wrapper .cmd registered as a logon Task Scheduler task loops the kernel with a ~5s crash-restart, pinned to the dedicated venv interpreter (ambient py -3 user-site is invisible under Task Scheduler) and forcing UTF-8. Kernel exit 3 (another instance holds the PID lock) is not looped. An independent watchdog task every 5 minutes taskkills a stale-heartbeat zombie and restarts a down daemon (STALE_S=120).

Dependencies (venv, Python 3.14): claude-agent-sdk 0.2.116, discord.py, PyYAML, pytest, tzdata, Pillow (GUI screen capture), and optional sqlite-vec plus fastembed (bge-small, 384-dim CPU ONNX) for the vector lane of hybrid retrieval, degrading to an FTS5/BM25 floor if absent. The test suite (roughly 50 to 60 modules) installs an autouse real-registry mutation guard that fails any test which changes content under projects/ or knowledge/, added after a suite once leaked adopt() writes into the real registry and the nightly sync committed the damage. The root cause was writers reaching disk through import-time-bound module paths, so patching db.ROOT alone is insufficient.

Security tests are the sharpest: the permission matrix with every check audited; fail-closed fence loading (crown jewels denied, the sibling workload's runtime state read-only, construction aborts on a bad or empty fence); an injection corpus proving untrusted content can never trigger actions; and the hook-enforcement regression guard for the shadowed can_use_tool finding. Dispatch tests run one shared contract against every adapter (what makes "swappable adapters" a guarantee) and verify the local-offload provider maps exit 3 to a GATED requeue and exit 2 to a DOWN cloud-fallback, never a force flag.

Observability

procutil.EventLog is an append-only JSONL ops log under state/logs/, one file per component per UTC day, emitting a broad unschematized vocabulary (boot lock reclaim, daemon start/stop, dispatch halt/resume, scheduled-job fire/done/error, worker lifecycle, adapter supervise-restart, PreCompact telemetry, watchdog zombie/down events). Distinct from it, the os.db audit table is the queryable, tamper-evident security record of gate decisions (actor/action/target/allowed/reason), backed up nightly and the source of truth for security tests.

platform_doc

system/platform_doc.py keeps the internal PLATFORM.md reference fresh without regenerating the whole authoring fleet nightly. The doc assembles deterministically (zero tokens) from section sources. A SECTIONS manifest gives each section a file, slug, globs (git pathspecs whose change marks it stale), and covers. The nightly job diffs since..HEAD on those globs and drives one bounded T1 session per stale section with a minimal-edit prompt; a no-change night spends nothing. A well_formed guard accepts a regen only if it opens with an ATX heading and has balanced code fences, reverting malformed edits rather than baselining them. A completeness test fails if any new system/*.py module is uncovered by some section's globs. (This snapshot was condensed from the output of exactly that job.)


Appendix: quick reference

Security tiers and model aliases

Four tiers ladder capability. T0 is read/answer only. T1 adds Write/Edit to any non-fenced path. T2 adds Bash for any non-danger-list command. T3 adds external side effects, reachable only through an operator retyped-nonce flow. Security tier and model tier are separate axes. Model names live only in config/registry.yaml, exposed as six aliases: route (cheapest classify floor), work (cloud workhorse and fallback), deep (planning default), reserve (scarce, explicit-only), bulk-local (offloaded batch, operator-pinned, gated during the reserved GPU window), and codex (separate subscription, explicit-only).

Job and result state machines

Jobs move QUEUED to RUNNING to DONE/FAILED, with parking states. AWAITING_APPROVAL holds a danger-cap worker waiting on a nonce; a lapsed 30-minute TTL fails the job at next boot rather than parking forever. PARKED_CAP marks a weekly cap hit. PARKED_GATED marks a gated local model. INTERRUPTED covers daemon death or operator cancel and is resumable. Providers return typed refusals as ResultStatus values, never exceptions: GATED, DOWN, CAP_EXHAUSTED.

Governor and budget

Frugality ladder: interactive work degrades one rung at 75 percent, background parks at 80 percent, everything parks at the 85 percent ceiling. Background also parks once its own attributed spend reaches 25 percent of cap. The downgrade map is reserve -> deep -> work -> route, where route is the floor. Local and codex aliases are ledgered but excluded from the cap denominator. Admission gates on capped (Claude-only) spend while reporting counts all spend.

Danger caps

CAP_DELETE and CAP_EDIT_SECURITY are auto-grantable via a single-use 128-bit nonce (30-minute TTL, five wrong guesses clears all pending). CAP_GUI (desktop drive) is grantable but always one-tap approve and inherently unfenceable. A trading-execution capability is never grantable, is excluded from the auto-grant set, and stays inert because no trading tool ever mounts.

Core invariants

Prompts are advisory and code is enforcement, re-checked every tool call, fail-closed and audited. The PreToolUse hook is the real gate because the Claude Agent SDK 0.2.116 can_use_tool callback is silently shadowed (allowed_tools must stay empty). setting_sources=[] plus strict_mcp_config makes an unwanted MCP unreachable by construction. Only the kernel writes the DB (single-writer WAL). Every fence change is proven with a live canary, not a unit test. Contracts are frozen. A JobSpec is cold-restartable from spec_json alone, and scheduling is reboot-safe via a UNIQUE minute-window idempotency key. Context rotation is permanently off after it fired every turn.

Exit-code map (local offload)

Exit 2 is DOWN; exit 3 is GATED (retry after the gate window); exit 5 is a loud FAILED on a model-pin mismatch (never substitutes); blank or truncated output is FAILED. The kernel wrapper loop-relaunches on crash but treats exit 3 (another instance holds the PID lock) as a hard no-loop stop.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment