ian-operator Version 0.1.0 Node ESM · SQLite Model Context Protocol service Folio I of VII

An operator kernel, and the gate it will not cross

A standalone service that holds a person’s tasks, goals and decisions in durable state, and interposes a deterministic policy gate between an autonomous language model and every act that would touch the outside world. The model proposes. Code disposes.

Article I — Preamble and instrument of delegation

Article I Preamble

1.1

What the instrument is

ian-operator is a single Node process that speaks the Model Context Protocol over loopback HTTP. It holds one person’s operating state — the day’s tasks, the decisions awaiting a verdict, the audit trail, the record of what each autonomous tick attempted and what came of it — and it exposes that state to any MCP client as a compact context packet.

It also holds something rarer: a gate. Before an autonomous agent may send, spend, publish, delete or telephone, it must first record an intent. Recording is not doing. The intent passes through a pure function that stamps it allowed, requires approval or blocked, and that stamp is the law of the rest of the system.

1.2

The governing doctrine

The model proposes, code disposes. lib/comms.js:10

Every safety property in this repository descends from that one sentence. A language model may compose an email, choose a task, argue for a purchase, or draft a note. It may never be the thing that decides whether the email leaves the building. That decision is made by deterministic code the model cannot reach, cannot read the internals of, and cannot forge a token for.

The kernel modules state the same constraint from the other side: lib/safety-compose.js describes itself as “deterministic code the brain can never reach,” pure, with no LLM, no I/O and no database access.1

1.3

The chain of custody for a single act

Five parties handle any consequential action, and the boundaries between them are enforced by different mechanisms: a bearer token at the network edge, a pure policy function at the gate, an approver allowlist bound to a content hash at the approval step, and a compare-and-set claim at the executor.

Chain of custody for one consequential action in ian-operator Five vertical bands — human, brain, kernel, executor and outside world. The brain may only record an intent through the MCP face, which is guarded by a bearer token. The kernel stamps the intent allowed, requires approval, or blocked. Approval can only be granted out of band by an allowlisted human, binding a decision row to the intent identifier and a hash of its salient fields. Only the separate executor process may act, and only on an intent the kernel predicate still judges executable. HUMAN BRAIN (proposer) KERNEL (disposer) EXECUTOR OUTSIDE WORLD TRUST BOUNDARY · bearer token, then the gate EFFECT BOUNDARY Goals + instructions confirmed goal_model Approval, out of band approve-intent.mjs CLI allowlisted subject only Reads context packet goal, plan step, progress, tried_and_failed Chooses one action JSON decision, one tool, actor field overwritten No send. No spend. No call. Ever. The gate evaluateOperator- ActionPolicy — pure allowed → ready requires_approval blocked — terminal Decision bound to intent_id + action_hash mutation voids it Claims the intent compare-and-set to 'sending', once only One handler per type enumerated tools only, dry-run on by default File, note, draft, message, e-mail money is never machine-spent only this path can approve an intent record intent
Figure 1 — chain of custody The brain’s only reach into the world is operator_record_action_intent. Everything to the right of the first dashed line is deterministic. The amber path is the sole route by which an intent that requires approval can become executable, and it does not exist on the MCP face at all.

1.4

Fault isolation as a design premise

The service is deliberately a sibling and not a subsystem. It runs in its own process, opens its own SQLite file at ~/.ian-operator/operator.db, listens on its own loopback port (127.0.0.1:3401 by default), carries its own bearer token, and — the comment is emphatic — “never imports” the codebase it was forked from.2

The database module says the same thing in different words: it is “deliberately not imported from zschool-mcp — fault isolation requires a wholly separate DB.” The connection is opened with WAL journaling, a five-second busy timeout, and foreign keys on.

Runtime surface
RouteAuthBehaviour
GET /healthnoneProbes the database with SELECT 1; answers 200 or 503 with service, version, profile id, timezone, database path and uptime. Performs no inference.
POST /mcpBearerStateless streamable-HTTP transport; a fresh McpServer is built per request and closed when the response closes.
anything else404 {"error":"not found"}

1.5

Powers withheld

A large part of the design is negative space — capabilities that were deliberately not built, or were built and then removed. These are stated as invariants in the source, each with the reasoning that produced it.

  • no approval toolThe MCP face has 24 tools and none of them can approve. operator_update_action_intent throws if asked to set status:'approved'; operator_update_decision throws on approved or accepted.
  • no runner approverAny actor subject matching the runner pattern — the fast tick, the slow tick, the brain, the executor, a relay — can never be treated as a human approver, even if mistakenly added to the allowlist.
  • no auto-purchaseEven an approved spend intent is not executed. The spend handler notifies the owner with the full details and marks the intent a terminal human hand-off.
  • no arbitrary toolsThe executor may call exactly four downstream tools; a handler requesting anything else throws before any side effect.
  • dry-run by defaultThe executor runs in dry-run unless the environment explicitly sets the string 0. The shipped unit file pins it to 1 and the timer is not enabled.

1.6

Composition

Three runtime dependencies, no build step, no bundler, no transpiler. The package is "type": "module" throughout; tests run on the Node built-in runner.

@modelcontextprotocol/sdk ^1.26.0
Server and streamable-HTTP transport for the MCP face.
better-sqlite3 ^12.8.0
Synchronous SQLite. The whole state layer is prepared statements against one file.
zod ^4.0.0
Argument schemas for the tool definitions.
engines: node >=20.19.0 <21
Pinned to a single major line.

Two npm scripts: npm start runs node server.js; npm test runs node --test.

1.7

Status of this document

This is a description of a private repository’s capabilities, prepared while assessing it for possible public release. It documents mechanisms, invariants and interfaces. It contains no task content, no personal data, no credentials, no host or channel identifiers, and no operating figures.3 Line references point at the source as read; quoted text is quoted exactly.

Notes

  1. lib/safety-compose.js:3–7. The module additionally forbids itself from importing operator.js or the runner library, so purity is structural rather than conventional.
  2. server.js:3–5. The header comment describes the process as a “fault-isolated sibling”.
  3. The repository does contain deployment paths, a channel identifier used as a fallback default, and business-specific planning notes. None of it appears on this site. See Article VII §7.6.