The rest of this site describes the pattern. This page maps it onto a reference implementation: a Node / TypeScript orchestrator on the Claude Agent SDK, with tool servers for GitLab, Ansible Automation Platform, NetBox, Proxmox VE, ServiceNow and Grafana, a Next.js web client and a SwiftUI phone app. Nothing on the other pages depends on these choices.
01Concept → implementation
| Concept | How the reference implementation does it | Source |
|---|---|---|
| Agents propose in code; pipelines apply | The change specialist's Git tool server opens merge requests with a change record; the automation tool server can only check and run records through a change-runner job template; no tool changes a device directly. | server/src/changes.ts:1-22 mcp/aap/src/server.ts:1-18 mcp/gitlab/src/server.ts:1-16 |
| Agents never hold credentials | Each agent process gets an environment rebuilt from a short allow-list plus one model credential; ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_BASE_URL and CLAUDE_CODE_OAUTH_TOKEN from the backend's own environment are never copied. | server/src/auth.ts:12-13, 37-51 |
| Secrets rendered outside the agent's reach | Tool-server credentials are rendered by the deploy automation into the site's MCP configuration; each tool server receives only the env in its own entry; admission hashes exclude env. | docs/DEPLOY.md:168-179 server/src/admission.ts:8-17 |
| Secret files are unreadable | The Git tool server refuses to read vault files, env files and the MCP configuration. | mcp/gitlab/src/server.ts:233 |
| Output redacted before the model | The automation tool server redacts device and API output; source-of-truth, hypervisor and ITSM servers redact secret-looking fields; ITSM credentials only from the server's own env; the audit writer drops secret-looking keys. | mcp/aap/src/redact.ts mcp/servicenow/src/guards.ts server/src/audit.ts:9-35 |
| Reads are free; writes need a grant | The change gate classifies the message once per turn; without a grant write-class tools are refused. | server/src/changegate.ts:1-36 server/src/sessions.ts:342-371, 401-405 |
| The nine checks | A PreToolUse hook (scope, profile, change gate, budget) and the SDK's canUseTool callback (all but budget, then guards, admission, policy, change path, verdict). | server/src/sessions.ts:695-840 |
| Approval cards and digests | Frozen input, a SHA-256 digest over session, approval id, tool and input; single use; re-checked on approve. | server/src/sessions.ts:566-583, 769-788, 922-944 |
| Merge card raised by the orchestrator | After a check passes at a known head; bound to request, change and head commit; 24 h. | server/src/sessions.ts:951-994, 1064-1065 |
| Admission by hash | Launch definition, source files or binary, every tool classed; changed servers refused at agent start. | server/src/admission.ts:8-17, 137-165 server/src/sessions.ts:592-616 |
| Protected targets | A hard guard on stop, shutdown, reboot of listed VMs; the hypervisor server checks its own list too. | server/src/guards.ts:21-27 |
| Budgets | Per origin: tool calls per agent run and per message, tokens per message and per chat, USD per chat (API-key mode only). | server/src/budgets.ts:5-40 config/budgets.json:3-26 server/src/sessions.ts:394-399, 820-831, 1116-1129 |
| Scope per specialist | Definitions only expose own servers; a scope check on every call. | server/src/specialists.ts:152-171, 224-266 |
| Hash-chained audit | Append-only, seq and prev, fsync per record; a verifier. | server/src/audit.ts:37-98 |
| Roles from directory groups; approvers separate | OIDC with PKCE; roles from claims; approver groups; any approver may approve any card (no self-approval block). | server/src/oidc.ts:1-12 server/src/rbac.ts:93-99 |
| Triggers and schedules | Alert triggers with secrets, dedupe and rate limits; cron schedules; job and alert chats on read-only profiles by default. | server/src/automations.ts:1-70 server/src/index.ts:580-646 |
| Metrics | odin_tokens_total, odin_cost_usd_total, odin_turns_total, odin_budget_stops_total. | server/src/metrics.ts:1-55 |
02Not in the reference implementation (yet)
| Pattern element | Status |
|---|---|
| Model gateway | The reference implementation calls the Anthropic API directly through the Claude Agent SDK, with model aliases mapped to claude-haiku-5-5, claude-sonnet-5-5 and claude-opus-5-5. Tiers map onto those aliases; a gateway is not part of it. |
| Digital twin and emulation lab | Not called by the reference implementation at this commit. Its source-of-truth server can export a site's devices and cabled links for a twin; the graph twin, what-if in CI and emulation rehearsals are specified on these pages as the pattern. |
| Vault integration | The reference implementation does not talk to a vault itself: credentials are rendered into its environment and tool-server configuration at deploy time, and the automation platform holds device credentials. Short-lived, pipeline-fetched credentials are the pattern. |
| Active/passive failover | One process with in-memory approvals and file state; failover is the platform's job (restart on a durable volume). |
| Self-approval block | Not enforced in code; separation of duties is group design. |
| ITSM container prefix | A constant in the ITSM tool server's guards, not a setting. |
03Model credentials in the reference implementation
ODIN_AUTH_MODE=api_keymakes API-key mode the default for every new chat, including jobs and alerts; the mode is stored with each chat server/src/auth.ts:15-17 server/src/sessions.ts:296-302 server/src/index.ts:460, 589.ODIN_API_KEYis injected into agent processes asANTHROPIC_API_KEYand nowhere else; the backend warns at start-up ifANTHROPIC_*variables are set on it server/src/auth.ts:19-45.- Side calls (fresh-chat summary, next-prompt suggestion, spoken summary) use the same environment builder server/src/carryover.ts:52 server/src/suggest.ts:23 server/src/voice.ts:148.
- The per-chat USD cap applies only in API-key mode, checked before each new message; cost is the SDK's own per-turn figure server/src/sessions.ts:394-399, 1080-1097.
- Its README describes API-key mode as "for one session or job only"; the code applies
ODIN_AUTH_MODEto every chat created while it is set README.md:112.
agent process environment
| CLAUDE_AGENT_SDK_CLIENT_APP | odin-…identifies ODIN to the SDK |
| PATH, HOME, USER, LANG, TMPDIR, SHELL, TERM | copiedthe only backend variables copied |
| ANTHROPIC_API_KEY | = ODIN_API_KEYthe one credential, set only inside the agent process |
| MAX_MCP_OUTPUT_TOKENS | ODIN_MAX_TOOL_OUTPUT_TOKENS ?? 15000caps what one tool result puts into context |
never reaches the agent
| ANTHROPIC_AUTH_TOKEN | never passed would override the credential ODIN chose |
| ANTHROPIC_BASE_URL | never passed would redirect agent traffic to another endpoint |
| ANTHROPIC_API_KEY (from the backend's own env) | never copied only ODIN_API_KEY becomes the agent's key, and only in api_key mode |
| CLAUDE_CODE_OAUTH_TOKEN | not passed the other mode's credential |
| everything else (vault tokens, MCP credentials, …) | not passed the environment is rebuilt from scratch, not filtered |
agentEnv(mode) starts from an empty object, copies a short allow-list, then adds the one credential for the chat's mode server/src/auth.ts:12-13, 37-51. Every agent process gets it: chats server/src/sessions.ts:639, the fresh-chat summary server/src/carryover.ts:52, the next-prompt suggestion server/src/suggest.ts:23 and the spoken reply server/src/voice.ts:148.04Workflows → code
| # | Step | Source |
|---|---|---|
| 1 | "is the core switch healthy?" | server/src/index.ts:85-141 server/src/rbac.ts:80-88 |
| 2 | change gate: no grant | server/src/sessions.ts:401-405 server/src/changegate.ts:16-34 |
| 3 | message → the chat's agent | server/src/sessions.ts:406-433 |
| 4 | delegate: health check | server/src/specialists.ts:188-209, 275-278 server/src/handoffs.ts:43-58 |
| 5 | tool call → nine checks | server/src/sessions.ts:695-840 |
| 6 | network checks (all devices, one job) | mcp/aap/src/server.ts:301-304 |
| 7 | launch a named read-only template | mcp/aap/src/server.ts:1-18 |
| 8 | parsed, redacted results | mcp/aap/src/redact.ts mcp/aap/src/server.ts:151-158 |
| 9 | report (kept as a hand-off) | server/src/handoffs.ts:27-34 |
| 10 | answer · "raise changes?" | server/src/sessions.ts:1085-1095 |
05Tests in the reference implementation
- A safety suite with no model and no network: 33 sections, 138 checks server/scripts/safety.ts:1-6; type checks and a contract check in CI .gitlab-ci.yml:17-44.
- 17 behaviour evaluations with the model in the loop, approvals auto-denied server/evals/cases.json.