How it works

Architecture

Four processes and one rule: every credential lives in the harness, and nothing else is trusted to hold one.

sentinel-agent system mapHTTP + SSEMCPcreate_sub_agentexecsentinel-agent UINext.js 16 · React 19 · 127.0.0.1:3000, loopback onlyA view over harness events. Holds no agent logic./tf/[...path] route handlerAttaches TRUEFORGE_TOKEN server-side · streams SSERefuses cross-origin and untokened mutationsTRUEFORGE HARNESS · localhost:8790Agent loop · tool routing · approval gating · subagent delegationSandbox orchestration · session persistence · context managementsentinel-ops MCP :89407 read-only · 1 write GATED2 destructive GATEDSubagents (dynamic)perf / deploy / codeisolated contexts, conclusions onlySandbox · Python 3.13pandas, requests, pydanticno credential ever enters itEvery credential lives inside the harness box.The UI holds none. The MCP server holds none. The sandbox holds none — its tool calls are bridged back out.

The pieces

ComponentRuns onHolds
sentinel-agent UI
Next.js 16 · React 19
127.0.0.1:3000No agent logic and no credentials. A view over harness events: timeline, subagent threads, evidence, approval gate, audit trail.
/tf route handler
server-side proxy
same processThe harness token, server-side only. Streams SSE through; refuses cross-origin and untokened mutations.
TrueForge harnesslocalhost:8790Every credential. Agent loop, tool routing, approval gating, subagents, sandbox orchestration, session persistence, context management.
sentinel-ops MCP server127.0.0.1:8940Thirteen tools and a simulated estate. No credentials. Optional bearer token for its own protection, not for anyone else’s.

Credential boundaries

  • The UI never sees the harness token — it is attached server-side by the route handler, so a browser devtools tab has nothing to steal.
  • The sandbox never sees any key. Its tool calls are bridged out to the harness and executed there.
  • The one external credential this repo ever handles is a model provider key, read once by npm run provision from .env and handed to the harness. Never read again.
  • OPS_LAB_TOKEN and OPS_MCP_TOKEN are secrets you generate yourself with openssl rand -hex 24, not issued by anything.

Trust model

Two consequences, both found by code review rather than by design, and both now closed:

WasMeantNow
MCP server bound 0.0.0.0, /mcp unauthenticatedrollback_deployment reachable from the LAN, never passing through the harnessBinds 127.0.0.1 (OPS_MCP_HOST to override); optional bearer auth with a constant-time compare; insecure posture logged at error; /estate CORS narrowed from * to known origins
Proxy attached the server-held token for any callerAnything able to reach :3000 could approve a production rollbackSec-Fetch-Site refuses cross-origin browser requests, and an operator token (x-sentinel-operator) is required for every state-changing method — closing local curl callers, which send no Sec-Fetch-Site at all. Fails closed when unconfigured

The second one took two rounds. The first attempt added the origin check and documented caller authentication as out of scope — and the reviewer did not mark it resolved, correctly: an origin check is not authentication, and the guard explicitly allowed non-browser callers, so a local curl could still submit an approval. The operator token is the actual fix.

Event flow

one gated call, end to end
agent decides           → model.message { toolCalls: [{ id: call_71c, ... }] }
harness sees @destructive → tool.approval_required { tool_calls: [{ id, source_event_id }] }
turn ends                 → state.required_actions populated
UI joins on source_event_id → renders "rollback_deployment(dpl-4c21)" + the evidence brief
you decide                → POST /turns  { type: 'user.tool_approval', approval: { status } }
harness dispatches or not → tool.response, or the agent continues without it
estate records            → /estate/audit  (independent of the event stream)

The join on source_event_id is the non-obvious part — the approval event carries no tool name and no arguments, so a client without an event index has nothing to show but an id. The gate page has the detail.

Deliberate omissions

  • No database. Session state lives in the harness; the estate is in-process with its own audit log. Nothing here is worth persisting past a restart.
  • No auth system. One operator token, checked in constant time, failing closed. A login screen would be a bigger surface for no gain on a loopback-only tool.
  • No client-side agent logic. Every decision is the harness’s. If the UI could decide anything, the UI would be part of the safety model.