Review bootstrap and connectivity ADRs with immutable history
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

This commit is contained in:
codex 2026-10-11 02:07:18 +02:00
parent b202ed02f1
commit dcdec162d6
8 changed files with 1040 additions and 6 deletions

View file

@ -4,9 +4,11 @@ type: architecture-decision-record
title: "Custodian Agent Runtime — v0.1 Bootstrap Design"
status: accepted
owner: the-custodian
revision: "accepted-1"
last_reviewed: "2026-03-12"
revision: "accepted-2"
last_reviewed: "2026-10-11"
review_interval: 6m
reviewed_by: "codex — technical source/currency review"
review_scope: "Historical decision retained; current factual and authority scope clarified, no new grant"
decided_by: Bernd Worsch
date: "2026-03-12"
tags: ["architecture", "agent-runtime", "llm", "ooda", "bounded-agency"]
@ -18,7 +20,44 @@ tags: ["architecture", "agent-runtime", "llm", "ooda", "bounded-agency"]
Accepted.
## Context
## Technical currency review — 2026-10-11
The accepted decision remains the **v0.1 bootstrap architecture for the
standalone package under `runtime/`**, not a grant to every coding agent or
an assertion that a privileged autonomous runtime is deployed. Current source
still implements D1–D5: this package location, a single synchronous CLI cycle,
configurable `llm-connect` with `claude-code` default, three explicit HTTP write
classes, and a fenced JSON action plan plus a session note. All 32 existing
offline runtime tests pass with HTTP calls mocked. That verifies the described
prototype behavior; it proves no live authorization, runtime isolation,
credential boundary, semantic correctness of task updates, or rollout state.
D4's original permission is limited to that bootstrap's named low-impact
coordination operations. It is neither an approval token nor a Codex autopilot
grant, and it does not override owner-source work records, API authorization,
work-record lanes or agent-specific supervision. Current governance is
`docs/agent-autonomy-decision.md` (founder, 2026-09-28): new scopes start
supervised and autopilot requires a separate explicit scoped grant and verified
runtime enforcement. The inspected `custodian-codex` supervision record remains
supervised, with no autopilot grant and interactive isolation not yet migrated.
This review changes no such authority or record.
The March dependency/deployment statements below are historical context.
The review does not reaffirm that every deployment has Claude Code, that the
bootstrap is scheduled or deployed, or that its narrow operation list provides
credential isolation. API addresses and live change procedures belong to
`docs/agent-environment-orientation.md`; task/workplan sources follow ADR-001
and the later accepted ADR-012. The code's direct task-status HTTP branch is
prototype implementation evidence, not permission to override file-backed
owner records. Any widening of D4 or promotion remains a separate decision.
The original 2026-03-12 decision and accepted-1 bytes are retained in
`docs/evidence/2026-10-11-adr-currency-review/adr-002-accepted-1.md`.
`accepted-2` is this dated factual/scope annotation, not a new human adoption
of an expanded action surface. The detailed source hashes and test receipt
are in `docs/evidence/2026-10-11-adr-currency-review/review.json`.
## Context (historical decision basis)
CUST-WP-0001 requires a first working skeleton of the Custodian as an acting
agent: a loop that observes project state, reasons about it, and executes

View file

@ -4,9 +4,11 @@ type: architecture-decision-record
title: "Connectivity-First Network Posture for Custodian Infrastructure"
status: accepted
owner: the-custodian
revision: "accepted-1"
last_reviewed: "2026-03-26"
revision: "accepted-2"
last_reviewed: "2026-10-11"
review_interval: 6m
reviewed_by: "codex — technical source/currency review"
review_scope: "Historical decision retained; current factual and authority scope clarified, no new grant"
decided_by: Bernd Worsch
date: "2026-03-26"
tags: ["architecture", "network", "ops-bridge", "connectivity", "isolation", "security-posture"]
@ -18,7 +20,60 @@ tags: ["architecture", "network", "ops-bridge", "connectivity", "isolation", "se
Accepted.
## Context
## Technical currency review — 2026-10-11
The accepted decision remains a **transport-connectivity posture**: needed
connections use named, observable, managed paths with deliberate lifecycle
continuity. Persistent transport is compatible with independently limited
credentials, authenticated callers and resource-side authorization. It grants
neither access to every connected component nor authority to bypass isolation
required by the current security model. No blanket trust, permanent credential
permission or autonomous privileged execution is adopted by this review.
The March Context, Rationale and deployment consequences below are retained
as the original decision's historical basis, not current environment facts or
a present security assurance. Specifically:
- The central State Hub is on railiance01. The workstation address
`http://127.0.0.1:8000` is a managed local forward; remote in-cluster callers
use `http://10.43.68.154:8000`. The old remote-to-workstation reverse tunnel
`http://127.0.0.1:18000` is retired. An optional local relay at :18080 is a
separate explicitly enabled route, not the primary hub.
- OpsBridge owns observable connectivity, not credentials or authorization.
Its INTENT explicitly disclaims credential-authority and security-policy
ownership. Its older SCOPE still describes a workstation-local Hub and must
not be used as current topology proof. Use the Custodian environment
orientation and current owner records before selecting a route.
- Observability and a read-model design are **not proof that unauthorized
writes, lateral movement or credential exposure are prevented**. The old
narrow-write rationale is not a current control assurance. Source authority
is governed by accepted ADR-001/ADR-012, and concrete service security is
governed by its owners and accepted NetKingdom security-layer v0.8.
- SSH encryption protects its transport; it does not guarantee that a
forwarded application is authenticated, that listener binding exposes no
service, or that credentials cannot reach a process or log. The original
“no inbound ports exposed” and age/SOPS phrases are not estate-wide proof.
Current custody routing and secret-output rules remain mandatory.
- Build mode and absence of customers do not waive tenant boundaries, service
admission, credential isolation or exact live-change approval. The
agent-specific supervision decision of 2026-09-28 governs privileged
execution; the Kubernetes change gate of 2026-09-21 governs its change path.
Those existing decisions, not this transport ADR, supply authority.
These clarifications apply existing accepted governance rather than replacing
the connectivity-first decision with a new isolation-first mandate. Changing
the transport posture or granting new authority would require a separate
proposal and human disposition. No fresh live topology or isolation audit was
performed by this source review; environment facts above retain the evidence
dates and ownership in `docs/agent-environment-orientation.md`.
The original 2026-03-26 decision and accepted-1 bytes are retained in
`docs/evidence/2026-10-11-adr-currency-review/adr-004-accepted-1.md`.
`accepted-2` is this dated factual/scope annotation; the original decision
author and date remain history. Detailed source hashes and review findings
are in `docs/evidence/2026-10-11-adr-currency-review/review.json`.
## Context (historical decision basis)
The Custodian infrastructure spans multiple machines: a primary workstation, a
shared compute node (COULOMBCORE), and Railiance cluster nodes. Agents running

View file

@ -0,0 +1,242 @@
<!doctype html>
<html lang="en"><meta charset="utf-8">
<meta name="policy-source-revision" content="4039c9d1c08c92014ecc0a65dda63cc73ba187bb">
<meta name="policy-source-digest" content="6aef66cd5cf71a48f5b4e14401dc19a755e2b182445b272d5952df0eb0dea8ec">
<title>Custodian Agent Runtime — v0.1 Bootstrap Design</title>
<style>
:root{
--paper:#EDEEF0; --surface:#F6F7F8; --surface-2:#E4E6E9;
--ink:#171D24; --ink-2:#4A5561; --ink-3:#737E8A;
--rule:#D3D7DC; --rule-strong:#B6BCC3;
--brass:#8A6A2E; --brass-soft:#EFE5CD; --brass-line:#C9AE74;
--clay:#8A3A2C; --clay-soft:#F2DFDA;
--l0:#DCE0E2; --l1:#B9C4C7; --l2:#8CA1A6; --l3:#567D84; --l4:#23555E;
--chip-fg:#F6F7F8;
--font-display:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
--font-body:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
--font-mono:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;
--measure:66ch;
}
@media (prefers-color-scheme:dark){
:root:not([data-theme="light"]){
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
--rule:#2A3138; --rule-strong:#3B444D;
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
--clay:#D08A76; --clay-soft:#3A211B;
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
--chip-fg:#12161A;
}
}
:root[data-theme="dark"]{
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
--rule:#2A3138; --rule-strong:#3B444D;
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
--clay:#D08A76; --clay-soft:#3A211B;
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
--chip-fg:#12161A;
}
*{box-sizing:border-box}
body{
margin:0; background:var(--paper); color:var(--ink);
font-family:var(--font-body); font-size:17px; line-height:1.62;
-webkit-font-smoothing:antialiased;
}
.wrap{max-width:1180px;margin:0 auto;padding:0 24px 96px}
.layout{display:grid;grid-template-columns:180px minmax(0,1fr);gap:56px;align-items:start}
@media (max-width:960px){.layout{grid-template-columns:1fr;gap:0}.rail{display:none}}
/* ---------- rail ---------- */
.rail{position:sticky;top:28px;padding-top:8px;font-family:var(--font-display);font-size:12px;line-height:1.5}
.rail ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
.rail a{color:var(--ink-3);text-decoration:none;display:flex;gap:9px}
.rail a:hover,.rail a:focus-visible{color:var(--brass)}
.rail .n{font-family:var(--font-mono);font-size:10px;color:var(--rule-strong);min-width:16px;padding-top:1px}
.rail .grp{margin-top:14px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--rule-strong)}
/* ---------- header ---------- */
header{padding:64px 0 40px;border-bottom:2px solid var(--ink);margin-bottom:44px}
.eyebrow{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);display:flex;flex-wrap:wrap;gap:14px;margin-bottom:22px}
.eyebrow .stat{color:var(--clay)}
h1{font-family:var(--font-display);font-weight:800;letter-spacing:-.035em;line-height:.94;font-size:clamp(46px,9vw,92px);margin:0 0 6px;text-wrap:balance}
.sub{font-family:var(--font-display);font-weight:500;font-size:clamp(16px,2.4vw,21px);letter-spacing:-.01em;color:var(--ink-2);margin:0 0 30px;max-width:34ch;line-height:1.3}
.metagrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px 28px;border-top:1px solid var(--rule);padding-top:20px}
.metagrid dt{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:5px}
.metagrid dd{margin:0;font-family:var(--font-display);font-size:13.5px;line-height:1.45;color:var(--ink)}
/* ---------- typography ---------- */
section{margin-bottom:60px;scroll-margin-top:24px}
h2{font-family:var(--font-display);font-weight:750;letter-spacing:-.022em;font-size:clamp(24px,3.4vw,31px);line-height:1.12;margin:0 0 18px;text-wrap:balance;display:flex;gap:14px;align-items:baseline}
h2 .sn{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--brass);letter-spacing:.06em;flex:none;padding-top:2px}
h3{font-family:var(--font-display);font-weight:700;font-size:16px;letter-spacing:-.008em;margin:34px 0 10px;color:var(--ink)}
p{margin:0 0 15px;max-width:var(--measure)}
ul,ol{max-width:var(--measure);margin:0 0 15px;padding-left:20px}
li{margin-bottom:7px}
strong{font-weight:600}
em{font-style:italic}
code{font-family:var(--font-mono);font-size:.855em;background:var(--surface-2);padding:1px 5px;border-radius:2px}
a{color:var(--brass)}
.lede{font-size:19px;line-height:1.55;color:var(--ink-2);max-width:60ch}
/* ---------- devices ---------- */
.callout{border-left:3px solid var(--brass);background:var(--brass-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
.callout p:last-child{margin-bottom:0}
.callout .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
.rule-quote{border-top:2px solid var(--ink);border-bottom:2px solid var(--ink);padding:26px 0;margin:28px 0;max-width:var(--measure)}
.rule-quote p{font-family:var(--font-display);font-weight:600;font-size:19px;line-height:1.38;letter-spacing:-.014em;margin:0;text-wrap:balance}
.hard{border-left:3px solid var(--clay);background:var(--clay-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
.hard .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--clay);display:block;margin-bottom:8px}
.hard p:last-child{margin-bottom:0}
.dec{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;color:var(--brass);text-transform:uppercase}
.vec{font-family:var(--font-mono);font-size:.9em;font-weight:600;background:var(--surface-2);padding:2px 7px;border-radius:2px;white-space:nowrap;letter-spacing:.04em}
/* ---------- tables ---------- */
.scroll{overflow-x:auto;margin:0 0 24px;-webkit-overflow-scrolling:touch}
table{border-collapse:collapse;width:100%;min-width:520px;font-family:var(--font-display);font-size:13.5px;line-height:1.45}
th{text-align:left;font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);font-weight:400;padding:0 16px 8px 0;border-bottom:1px solid var(--rule-strong);vertical-align:bottom}
td{padding:11px 16px 11px 0;border-bottom:1px solid var(--rule);vertical-align:top;color:var(--ink-2)}
td:first-child{color:var(--ink);font-weight:600}
tbody tr:last-child td{border-bottom:none}
.lvl{font-family:var(--font-mono);font-weight:600;font-size:12px;letter-spacing:.04em;color:var(--ink)}
/* ---------- ladders ---------- */
.breakout{margin:34px 0 40px}
.bhead{display:flex;justify-content:space-between;align-items:baseline;gap:20px;border-bottom:1px solid var(--rule-strong);padding-bottom:9px;margin-bottom:22px;flex-wrap:wrap}
.bhead h3{margin:0;font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-family:var(--font-mono);font-weight:400;color:var(--ink-3)}
.bhead .note{font-family:var(--font-display);font-size:12.5px;color:var(--ink-3)}
.ladders{display:grid;gap:26px}
.ladder{display:grid;grid-template-columns:126px minmax(0,1fr);gap:18px;align-items:start}
@media (max-width:700px){.ladder{grid-template-columns:1fr;gap:10px}}
.ladder .pname{font-family:var(--font-display);font-weight:700;font-size:14px;letter-spacing:-.01em;padding-top:2px}
.ladder .pname span{display:block;font-family:var(--font-mono);font-size:10px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-top:3px}
.rungs{display:grid;gap:3px;grid-template-columns:repeat(5,minmax(0,1fr))}
@media (max-width:700px){.rungs{grid-template-columns:repeat(2,minmax(0,1fr))}}
.rung{padding:9px 10px 11px;background:var(--surface);border-top:4px solid var(--l0);min-width:0}
.rung.r1{border-top-color:var(--l1)} .rung.r2{border-top-color:var(--l2)}
.rung.r3{border-top-color:var(--l3)} .rung.r4{border-top-color:var(--l4)}
.rung .code{font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--ink);display:block;margin-bottom:4px}
.rung .txt{font-family:var(--font-display);font-size:11.5px;line-height:1.34;color:var(--ink-2);display:block}
.rung.na{opacity:.42}
/* ---------- matrix ---------- */
.matrix-shell{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:stretch;margin-bottom:14px}
.ylab{writing-mode:vertical-rl;transform:rotate(180deg);font-family:var(--font-mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);text-align:center;padding-bottom:22px}
.mgrid{display:grid;grid-template-columns:34px repeat(5,minmax(0,1fr));gap:3px}
.mcell{background:var(--surface);min-height:60px;padding:6px;display:flex;flex-direction:column;justify-content:flex-end;gap:4px;min-width:0}
.mcell.tint1{background:color-mix(in srgb,var(--l1) 26%,var(--surface))}
.mcell.tint2{background:color-mix(in srgb,var(--l2) 26%,var(--surface))}
.mcell.tint3{background:color-mix(in srgb,var(--l3) 24%,var(--surface))}
.mcell.tint4{background:color-mix(in srgb,var(--l4) 22%,var(--surface))}
.mcell.void{background:repeating-linear-gradient(135deg,transparent,transparent 5px,var(--rule) 5px,var(--rule) 6px);opacity:.55}
.rlab,.clab{font-family:var(--font-mono);font-size:10px;font-weight:600;letter-spacing:.05em;color:var(--ink-3);display:flex;align-items:center;justify-content:center}
.rlab{min-height:60px}
.clab{padding-top:7px;min-height:22px}
.pin{font-family:var(--font-mono);font-size:9.5px;font-weight:600;letter-spacing:.02em;background:var(--ink);color:var(--paper);padding:2px 5px;border-radius:2px;line-height:1.3;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pin.ghost{background:transparent;color:var(--ink-2);border:1px dashed var(--rule-strong)}
.mnote{display:flex;gap:22px;flex-wrap:wrap;font-family:var(--font-display);font-size:12px;color:var(--ink-3);padding-top:6px}
.mnote .k{display:flex;align-items:center;gap:7px}
.sw{width:13px;height:13px;flex:none;background:var(--ink)}
.sw.g{background:transparent;border:1px dashed var(--rule-strong)}
.sw.v{background:repeating-linear-gradient(135deg,transparent,transparent 4px,var(--rule) 4px,var(--rule) 5px);border:1px solid var(--rule)}
@media (max-width:640px){
.mgrid{grid-template-columns:28px repeat(5,minmax(0,1fr))}
.mcell{min-height:52px;padding:4px}
.pin{font-size:8px;padding:1px 3px}
.rlab{min-height:52px}
}
/* ---------- methodology ---------- */
.verbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule)}
.verb{background:var(--surface);padding:18px 18px 20px}
.verb h4{font-family:var(--font-display);font-weight:750;font-size:15px;margin:0 0 7px;letter-spacing:-.01em}
.verb p{font-family:var(--font-display);font-size:12.5px;line-height:1.46;color:var(--ink-2);margin:0;max-width:none}
.verb .step{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;color:var(--brass);display:block;margin-bottom:9px}
/* ---------- questions ---------- */
.qs{display:flex;flex-direction:column;gap:0;border-top:1px solid var(--rule-strong)}
.q{display:grid;grid-template-columns:34px minmax(0,1fr) 170px;gap:18px;padding:16px 0;border-bottom:1px solid var(--rule);align-items:start}
@media (max-width:760px){.q{grid-template-columns:28px minmax(0,1fr);gap:12px}.q .owner{grid-column:2}}
.q .qn{font-family:var(--font-mono);font-size:11px;color:var(--brass);padding-top:3px}
.q .qt{font-family:var(--font-display);font-size:14px;line-height:1.48;color:var(--ink-2)}
.q .qt b{color:var(--ink);font-weight:700;display:block;margin-bottom:2px;font-size:14.5px}
.owner{font-family:var(--font-mono);font-size:10px;letter-spacing:.05em;color:var(--ink-3);padding-top:4px}
.owner .tag{display:inline-block;border:1px solid var(--rule-strong);padding:2px 7px;border-radius:2px}
.owner .tag.need{border-color:var(--clay);color:var(--clay)}
/* ---------- misc ---------- */
.numbers{font-family:var(--font-mono);font-size:12.5px;line-height:1.85;background:var(--surface);border-left:3px solid var(--l3);padding:16px 20px;margin:0 0 22px;overflow-x:auto;max-width:var(--measure)}
.numbers .v{color:var(--ink);font-weight:600}
.numbers .k{color:var(--ink-3)}
pre{font-family:var(--font-mono);font-size:12.5px;line-height:1.68;background:var(--surface);border-left:3px solid var(--rule-strong);padding:16px 20px;overflow-x:auto;margin:0 0 22px;max-width:var(--measure);color:var(--ink-2)}
.alt{border-bottom:1px solid var(--rule);padding:14px 0;max-width:var(--measure)}
.alt:last-of-type{border-bottom:none}
.alt b{font-family:var(--font-display);font-size:14px;display:block;margin-bottom:3px}
.alt p{font-size:14.5px;margin:0;color:var(--ink-2)}
.alt .verdict{font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--clay)}
footer{border-top:2px solid var(--ink);margin-top:20px;padding-top:22px;font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;color:var(--ink-3);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
.tm td,.tm th{text-align:center}
.tm td:first-child,.tm th:first-child{text-align:left}
.yes{color:var(--l4);font-weight:700}
.no{color:var(--clay);font-weight:700}
.kind{font-family:var(--font-mono);font-size:9px;letter-spacing:.09em;text-transform:uppercase;padding:2px 6px;border-radius:2px;white-space:nowrap;border:1px solid var(--rule-strong);color:var(--ink-3)}
.kind.adv{border-color:var(--clay);color:var(--clay)}
.routes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule);margin:0 0 22px}
.route{background:var(--surface);padding:16px 18px}
.route h4{font-family:var(--font-display);font-weight:750;font-size:14px;margin:0 0 6px}
.route p{font-family:var(--font-display);font-size:12.5px;line-height:1.45;color:var(--ink-2);margin:0;max-width:none}
.route .tag{font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
</style>
<div class="wrap"><header><div class="eyebrow"><span>CUST-ADR-002</span> <span class="stat">accepted · accepted-1</span> <span>the-custodian</span> <span>reviewed 2026-03-12</span><span>generated from canonical source — do not edit</span></div><h1>Custodian Agent Runtime — v0.1 Bootstrap Design</h1><p class="sub">Source: <code>the-custodian · canon/architecture/adr-002-custodian-agent-runtime-design.md · 4039c9d1c08c92014ecc0a65dda63cc73ba187bb</code></p><p class="sub">Review due: 2026-09-12</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#status"><span class="n">·</span>Status</a></li><li><a href="#context"><span class="n">·</span>Context</a></li><li><a href="#decisions"><span class="n">·</span>Decisions</a></li><li><a href="#consequences"><span class="n">·</span>Consequences</a></li><li><a href="#deferred"><span class="n">·</span>Deferred</a></li></ol></nav><main><section id="status"><h2>Status</h2>
<p>Accepted.</p>
</section>
<section id="context"><h2>Context</h2>
<p>CUST-WP-0001 requires a first working skeleton of the Custodian as an acting agent: a loop that observes project state, reasons about it, and executes bounded write operations — without human interaction for each step.</p>
<p>The dependencies (llm-connect, operational Railiance infra) are now resolved. This ADR captures the five key architectural decisions for the v0.1 bootstrap.</p>
</section>
<section id="decisions"><h2>Decisions</h2>
<h3>D1 — Location: <code>runtime/</code> inside the-custodian (not a new repo)</h3>
<p><strong>Decision:</strong> The runtime lives under <code>the-custodian/runtime/</code> as a standalone Python package (<code>pyproject.toml</code>, own venv) rather than a new repository.</p>
<p><strong>Rationale:</strong> The runtime is tightly coupled to canon (reads constitution, memory) and the state-hub (its primary coordination layer). A separate repo adds friction with no v0.1 benefit. The existing <code>runtime/</code> scaffold confirms the original intent. Extraction to its own repo is deferred to when the runtime has stable boundaries and multiple consumers.</p>
<h3>D2 — OODA loop: single-pass synchronous CLI</h3>
<p><strong>Decision:</strong> One <code>run()</code> call = one complete Observe → Orient → Decide → Act cycle. Entry point is a CLI (<code>agent.py</code>) invoked manually or by a cron job.</p>
<pre>Observe — HTTP GET to state-hub: state summary or domain summary
Orient — Load constitution + build structured LLM context prompt
Decide — Single LLM call (via llm-connect) returns a JSON action plan
Act — Execute only sanctioned write operations from the plan</pre>
<p><strong>Rationale:</strong> Async event loops and daemons add operational complexity that v0.1 doesn't need. A single-pass CLI is testable, debuggable, and can be scheduled externally. The transition to an event-driven loop is Phase 2.</p>
<h3>D3 — LLM backend: ClaudeCodeAdapter by default</h3>
<p><strong>Decision:</strong> The runtime uses <code>llm_connect.ClaudeCodeAdapter</code> as its default LLM backend (shells out to <code>claude --print</code>). Provider is configurable via <code>--llm</code> flag to support <code>gemini</code>, <code>openrouter</code>, or <code>openai</code>.</p>
<p><strong>Rationale:</strong> <code>ClaudeCodeAdapter</code> requires no API key and honours the Local-First value (V2). All current deployments have Claude Code available. The llm-connect abstraction means switching providers is a one-line change.</p>
<h3>D4 — Action constraint: three sanctioned write operations only</h3>
<p><strong>Decision:</strong> The agent may execute exactly three state-hub write operations without human approval:</p>
<ol><li><code>add_progress_event</code> — append an observation to the event log</li><li><code>update_task_status</code> — mark a task done/in_progress (reversible)</li><li><code>flag_for_human</code> — raise an intervention flag (escalation, not action)</li></ol>
<p>All other operations (create workstream, record decision, resolve decision, write to canon) require human approval before execution.</p>
<p><strong>Rationale:</strong> Constitution §3/§4 require bounded agency. The three operations are either append-only (progress events), reversible (task status), or explicitly escalating (flag). They cannot produce irreversible harm.</p>
<h3>D5 — LLM response format: JSON block in markdown</h3>
<p><strong>Decision:</strong> The LLM is prompted to return a Markdown response with a fenced ```json block containing the structured action plan:</p>
<pre>{
&quot;observations&quot;: [&quot;...&quot;],
&quot;progress_events&quot;: [
{&quot;summary&quot;: &quot;...&quot;, &quot;workstream_id&quot;: &quot;...&quot;, &quot;event_type&quot;: &quot;note&quot;}
],
&quot;tasks_to_update&quot;: [
{&quot;task_id&quot;: &quot;...&quot;, &quot;status&quot;: &quot;done&quot;}
],
&quot;tasks_to_flag&quot;: [
{&quot;task_id&quot;: &quot;...&quot;, &quot;note&quot;: &quot;...&quot;}
]
}</pre>
<p>The surrounding Markdown is preserved as a human-readable reasoning trace and written to <code>memory/working/</code> as a session note.</p>
<p><strong>Rationale:</strong> JSON blocks are robust to extraction (delimited), LLMs produce them reliably with clear instructions, and the surrounding prose gives Bernd an auditable reasoning trace without requiring a separate reasoning step.</p>
</section>
<section id="consequences"><h2>Consequences</h2>
<ul><li><code>runtime/</code> becomes a standalone Python package; <code>make agent-run DOMAIN=x</code> invokes it.</li><li>The runtime has no DB schema changes and no new API endpoints — it is a pure client of the existing state-hub HTTP API.</li><li>Autonomous actions are limited to append-only writes and escalations. Any expansion of the action surface requires a new ADR and human approval.</li><li>The v0.1 loop is single-user (Bernd). Multi-agent expansion is Phase 2+.</li></ul>
</section>
<section id="deferred"><h2>Deferred</h2>
<ul><li>Async event loop / daemon mode (Phase 2)</li><li>RAG over canon (Phase 1 roadmap item)</li><li>Tool adapters beyond state-hub HTTP (planned in <code>runtime/tool_adapters/</code>)</li><li>Deployment on Railiance k3s as a scheduled CronJob</li></ul>
</section><footer><span>CUST-ADR-002 · accepted-1 · accepted</span><span>the-custodian · canon/architecture/adr-002-custodian-agent-runtime-design.md · 4039c9d1c08c92014ecc0a65dda63cc73ba187bb</span></footer></main></div></div></html>

View file

@ -0,0 +1,126 @@
---
id: CUST-ADR-002
type: architecture-decision-record
title: "Custodian Agent Runtime — v0.1 Bootstrap Design"
status: accepted
owner: the-custodian
revision: "accepted-1"
last_reviewed: "2026-03-12"
review_interval: 6m
decided_by: Bernd Worsch
date: "2026-03-12"
tags: ["architecture", "agent-runtime", "llm", "ooda", "bounded-agency"]
---
# ADR-002: Custodian Agent Runtime — v0.1 Bootstrap Design
## Status
Accepted.
## Context
CUST-WP-0001 requires a first working skeleton of the Custodian as an acting
agent: a loop that observes project state, reasons about it, and executes
bounded write operations — without human interaction for each step.
The dependencies (llm-connect, operational Railiance infra) are now resolved.
This ADR captures the five key architectural decisions for the v0.1 bootstrap.
## Decisions
### D1 — Location: `runtime/` inside the-custodian (not a new repo)
**Decision:** The runtime lives under `the-custodian/runtime/` as a standalone
Python package (`pyproject.toml`, own venv) rather than a new repository.
**Rationale:** The runtime is tightly coupled to canon (reads constitution,
memory) and the state-hub (its primary coordination layer). A separate repo
adds friction with no v0.1 benefit. The existing `runtime/` scaffold confirms
the original intent. Extraction to its own repo is deferred to when the
runtime has stable boundaries and multiple consumers.
### D2 — OODA loop: single-pass synchronous CLI
**Decision:** One `run()` call = one complete Observe → Orient → Decide → Act
cycle. Entry point is a CLI (`agent.py`) invoked manually or by a cron job.
```
Observe — HTTP GET to state-hub: state summary or domain summary
Orient — Load constitution + build structured LLM context prompt
Decide — Single LLM call (via llm-connect) returns a JSON action plan
Act — Execute only sanctioned write operations from the plan
```
**Rationale:** Async event loops and daemons add operational complexity that
v0.1 doesn't need. A single-pass CLI is testable, debuggable, and can be
scheduled externally. The transition to an event-driven loop is Phase 2.
### D3 — LLM backend: ClaudeCodeAdapter by default
**Decision:** The runtime uses `llm_connect.ClaudeCodeAdapter` as its default
LLM backend (shells out to `claude --print`). Provider is configurable via
`--llm` flag to support `gemini`, `openrouter`, or `openai`.
**Rationale:** `ClaudeCodeAdapter` requires no API key and honours the
Local-First value (V2). All current deployments have Claude Code available.
The llm-connect abstraction means switching providers is a one-line change.
### D4 — Action constraint: three sanctioned write operations only
**Decision:** The agent may execute exactly three state-hub write operations
without human approval:
1. `add_progress_event` — append an observation to the event log
2. `update_task_status` — mark a task done/in_progress (reversible)
3. `flag_for_human` — raise an intervention flag (escalation, not action)
All other operations (create workstream, record decision, resolve decision,
write to canon) require human approval before execution.
**Rationale:** Constitution §3/§4 require bounded agency. The three operations
are either append-only (progress events), reversible (task status), or
explicitly escalating (flag). They cannot produce irreversible harm.
### D5 — LLM response format: JSON block in markdown
**Decision:** The LLM is prompted to return a Markdown response with a
fenced ```json block containing the structured action plan:
```json
{
"observations": ["..."],
"progress_events": [
{"summary": "...", "workstream_id": "...", "event_type": "note"}
],
"tasks_to_update": [
{"task_id": "...", "status": "done"}
],
"tasks_to_flag": [
{"task_id": "...", "note": "..."}
]
}
```
The surrounding Markdown is preserved as a human-readable reasoning trace
and written to `memory/working/` as a session note.
**Rationale:** JSON blocks are robust to extraction (delimited), LLMs produce
them reliably with clear instructions, and the surrounding prose gives Bernd
an auditable reasoning trace without requiring a separate reasoning step.
## Consequences
- `runtime/` becomes a standalone Python package; `make agent-run DOMAIN=x`
invokes it.
- The runtime has no DB schema changes and no new API endpoints — it is a
pure client of the existing state-hub HTTP API.
- Autonomous actions are limited to append-only writes and escalations. Any
expansion of the action surface requires a new ADR and human approval.
- The v0.1 loop is single-user (Bernd). Multi-agent expansion is Phase 2+.
## Deferred
- Async event loop / daemon mode (Phase 2)
- RAG over canon (Phase 1 roadmap item)
- Tool adapters beyond state-hub HTTP (planned in `runtime/tool_adapters/`)
- Deployment on Railiance k3s as a scheduled CronJob

View file

@ -0,0 +1,236 @@
<!doctype html>
<html lang="en"><meta charset="utf-8">
<meta name="policy-source-revision" content="4039c9d1c08c92014ecc0a65dda63cc73ba187bb">
<meta name="policy-source-digest" content="3b68adfa6ab329e73f857cf691dc405136d2d66a0135e2c37c236aabe4659557">
<title>Connectivity-First Network Posture for Custodian Infrastructure</title>
<style>
:root{
--paper:#EDEEF0; --surface:#F6F7F8; --surface-2:#E4E6E9;
--ink:#171D24; --ink-2:#4A5561; --ink-3:#737E8A;
--rule:#D3D7DC; --rule-strong:#B6BCC3;
--brass:#8A6A2E; --brass-soft:#EFE5CD; --brass-line:#C9AE74;
--clay:#8A3A2C; --clay-soft:#F2DFDA;
--l0:#DCE0E2; --l1:#B9C4C7; --l2:#8CA1A6; --l3:#567D84; --l4:#23555E;
--chip-fg:#F6F7F8;
--font-display:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
--font-body:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
--font-mono:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;
--measure:66ch;
}
@media (prefers-color-scheme:dark){
:root:not([data-theme="light"]){
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
--rule:#2A3138; --rule-strong:#3B444D;
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
--clay:#D08A76; --clay-soft:#3A211B;
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
--chip-fg:#12161A;
}
}
:root[data-theme="dark"]{
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
--rule:#2A3138; --rule-strong:#3B444D;
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
--clay:#D08A76; --clay-soft:#3A211B;
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
--chip-fg:#12161A;
}
*{box-sizing:border-box}
body{
margin:0; background:var(--paper); color:var(--ink);
font-family:var(--font-body); font-size:17px; line-height:1.62;
-webkit-font-smoothing:antialiased;
}
.wrap{max-width:1180px;margin:0 auto;padding:0 24px 96px}
.layout{display:grid;grid-template-columns:180px minmax(0,1fr);gap:56px;align-items:start}
@media (max-width:960px){.layout{grid-template-columns:1fr;gap:0}.rail{display:none}}
/* ---------- rail ---------- */
.rail{position:sticky;top:28px;padding-top:8px;font-family:var(--font-display);font-size:12px;line-height:1.5}
.rail ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
.rail a{color:var(--ink-3);text-decoration:none;display:flex;gap:9px}
.rail a:hover,.rail a:focus-visible{color:var(--brass)}
.rail .n{font-family:var(--font-mono);font-size:10px;color:var(--rule-strong);min-width:16px;padding-top:1px}
.rail .grp{margin-top:14px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--rule-strong)}
/* ---------- header ---------- */
header{padding:64px 0 40px;border-bottom:2px solid var(--ink);margin-bottom:44px}
.eyebrow{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);display:flex;flex-wrap:wrap;gap:14px;margin-bottom:22px}
.eyebrow .stat{color:var(--clay)}
h1{font-family:var(--font-display);font-weight:800;letter-spacing:-.035em;line-height:.94;font-size:clamp(46px,9vw,92px);margin:0 0 6px;text-wrap:balance}
.sub{font-family:var(--font-display);font-weight:500;font-size:clamp(16px,2.4vw,21px);letter-spacing:-.01em;color:var(--ink-2);margin:0 0 30px;max-width:34ch;line-height:1.3}
.metagrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px 28px;border-top:1px solid var(--rule);padding-top:20px}
.metagrid dt{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:5px}
.metagrid dd{margin:0;font-family:var(--font-display);font-size:13.5px;line-height:1.45;color:var(--ink)}
/* ---------- typography ---------- */
section{margin-bottom:60px;scroll-margin-top:24px}
h2{font-family:var(--font-display);font-weight:750;letter-spacing:-.022em;font-size:clamp(24px,3.4vw,31px);line-height:1.12;margin:0 0 18px;text-wrap:balance;display:flex;gap:14px;align-items:baseline}
h2 .sn{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--brass);letter-spacing:.06em;flex:none;padding-top:2px}
h3{font-family:var(--font-display);font-weight:700;font-size:16px;letter-spacing:-.008em;margin:34px 0 10px;color:var(--ink)}
p{margin:0 0 15px;max-width:var(--measure)}
ul,ol{max-width:var(--measure);margin:0 0 15px;padding-left:20px}
li{margin-bottom:7px}
strong{font-weight:600}
em{font-style:italic}
code{font-family:var(--font-mono);font-size:.855em;background:var(--surface-2);padding:1px 5px;border-radius:2px}
a{color:var(--brass)}
.lede{font-size:19px;line-height:1.55;color:var(--ink-2);max-width:60ch}
/* ---------- devices ---------- */
.callout{border-left:3px solid var(--brass);background:var(--brass-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
.callout p:last-child{margin-bottom:0}
.callout .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
.rule-quote{border-top:2px solid var(--ink);border-bottom:2px solid var(--ink);padding:26px 0;margin:28px 0;max-width:var(--measure)}
.rule-quote p{font-family:var(--font-display);font-weight:600;font-size:19px;line-height:1.38;letter-spacing:-.014em;margin:0;text-wrap:balance}
.hard{border-left:3px solid var(--clay);background:var(--clay-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
.hard .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--clay);display:block;margin-bottom:8px}
.hard p:last-child{margin-bottom:0}
.dec{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;color:var(--brass);text-transform:uppercase}
.vec{font-family:var(--font-mono);font-size:.9em;font-weight:600;background:var(--surface-2);padding:2px 7px;border-radius:2px;white-space:nowrap;letter-spacing:.04em}
/* ---------- tables ---------- */
.scroll{overflow-x:auto;margin:0 0 24px;-webkit-overflow-scrolling:touch}
table{border-collapse:collapse;width:100%;min-width:520px;font-family:var(--font-display);font-size:13.5px;line-height:1.45}
th{text-align:left;font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);font-weight:400;padding:0 16px 8px 0;border-bottom:1px solid var(--rule-strong);vertical-align:bottom}
td{padding:11px 16px 11px 0;border-bottom:1px solid var(--rule);vertical-align:top;color:var(--ink-2)}
td:first-child{color:var(--ink);font-weight:600}
tbody tr:last-child td{border-bottom:none}
.lvl{font-family:var(--font-mono);font-weight:600;font-size:12px;letter-spacing:.04em;color:var(--ink)}
/* ---------- ladders ---------- */
.breakout{margin:34px 0 40px}
.bhead{display:flex;justify-content:space-between;align-items:baseline;gap:20px;border-bottom:1px solid var(--rule-strong);padding-bottom:9px;margin-bottom:22px;flex-wrap:wrap}
.bhead h3{margin:0;font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-family:var(--font-mono);font-weight:400;color:var(--ink-3)}
.bhead .note{font-family:var(--font-display);font-size:12.5px;color:var(--ink-3)}
.ladders{display:grid;gap:26px}
.ladder{display:grid;grid-template-columns:126px minmax(0,1fr);gap:18px;align-items:start}
@media (max-width:700px){.ladder{grid-template-columns:1fr;gap:10px}}
.ladder .pname{font-family:var(--font-display);font-weight:700;font-size:14px;letter-spacing:-.01em;padding-top:2px}
.ladder .pname span{display:block;font-family:var(--font-mono);font-size:10px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-top:3px}
.rungs{display:grid;gap:3px;grid-template-columns:repeat(5,minmax(0,1fr))}
@media (max-width:700px){.rungs{grid-template-columns:repeat(2,minmax(0,1fr))}}
.rung{padding:9px 10px 11px;background:var(--surface);border-top:4px solid var(--l0);min-width:0}
.rung.r1{border-top-color:var(--l1)} .rung.r2{border-top-color:var(--l2)}
.rung.r3{border-top-color:var(--l3)} .rung.r4{border-top-color:var(--l4)}
.rung .code{font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--ink);display:block;margin-bottom:4px}
.rung .txt{font-family:var(--font-display);font-size:11.5px;line-height:1.34;color:var(--ink-2);display:block}
.rung.na{opacity:.42}
/* ---------- matrix ---------- */
.matrix-shell{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:stretch;margin-bottom:14px}
.ylab{writing-mode:vertical-rl;transform:rotate(180deg);font-family:var(--font-mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);text-align:center;padding-bottom:22px}
.mgrid{display:grid;grid-template-columns:34px repeat(5,minmax(0,1fr));gap:3px}
.mcell{background:var(--surface);min-height:60px;padding:6px;display:flex;flex-direction:column;justify-content:flex-end;gap:4px;min-width:0}
.mcell.tint1{background:color-mix(in srgb,var(--l1) 26%,var(--surface))}
.mcell.tint2{background:color-mix(in srgb,var(--l2) 26%,var(--surface))}
.mcell.tint3{background:color-mix(in srgb,var(--l3) 24%,var(--surface))}
.mcell.tint4{background:color-mix(in srgb,var(--l4) 22%,var(--surface))}
.mcell.void{background:repeating-linear-gradient(135deg,transparent,transparent 5px,var(--rule) 5px,var(--rule) 6px);opacity:.55}
.rlab,.clab{font-family:var(--font-mono);font-size:10px;font-weight:600;letter-spacing:.05em;color:var(--ink-3);display:flex;align-items:center;justify-content:center}
.rlab{min-height:60px}
.clab{padding-top:7px;min-height:22px}
.pin{font-family:var(--font-mono);font-size:9.5px;font-weight:600;letter-spacing:.02em;background:var(--ink);color:var(--paper);padding:2px 5px;border-radius:2px;line-height:1.3;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pin.ghost{background:transparent;color:var(--ink-2);border:1px dashed var(--rule-strong)}
.mnote{display:flex;gap:22px;flex-wrap:wrap;font-family:var(--font-display);font-size:12px;color:var(--ink-3);padding-top:6px}
.mnote .k{display:flex;align-items:center;gap:7px}
.sw{width:13px;height:13px;flex:none;background:var(--ink)}
.sw.g{background:transparent;border:1px dashed var(--rule-strong)}
.sw.v{background:repeating-linear-gradient(135deg,transparent,transparent 4px,var(--rule) 4px,var(--rule) 5px);border:1px solid var(--rule)}
@media (max-width:640px){
.mgrid{grid-template-columns:28px repeat(5,minmax(0,1fr))}
.mcell{min-height:52px;padding:4px}
.pin{font-size:8px;padding:1px 3px}
.rlab{min-height:52px}
}
/* ---------- methodology ---------- */
.verbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule)}
.verb{background:var(--surface);padding:18px 18px 20px}
.verb h4{font-family:var(--font-display);font-weight:750;font-size:15px;margin:0 0 7px;letter-spacing:-.01em}
.verb p{font-family:var(--font-display);font-size:12.5px;line-height:1.46;color:var(--ink-2);margin:0;max-width:none}
.verb .step{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;color:var(--brass);display:block;margin-bottom:9px}
/* ---------- questions ---------- */
.qs{display:flex;flex-direction:column;gap:0;border-top:1px solid var(--rule-strong)}
.q{display:grid;grid-template-columns:34px minmax(0,1fr) 170px;gap:18px;padding:16px 0;border-bottom:1px solid var(--rule);align-items:start}
@media (max-width:760px){.q{grid-template-columns:28px minmax(0,1fr);gap:12px}.q .owner{grid-column:2}}
.q .qn{font-family:var(--font-mono);font-size:11px;color:var(--brass);padding-top:3px}
.q .qt{font-family:var(--font-display);font-size:14px;line-height:1.48;color:var(--ink-2)}
.q .qt b{color:var(--ink);font-weight:700;display:block;margin-bottom:2px;font-size:14.5px}
.owner{font-family:var(--font-mono);font-size:10px;letter-spacing:.05em;color:var(--ink-3);padding-top:4px}
.owner .tag{display:inline-block;border:1px solid var(--rule-strong);padding:2px 7px;border-radius:2px}
.owner .tag.need{border-color:var(--clay);color:var(--clay)}
/* ---------- misc ---------- */
.numbers{font-family:var(--font-mono);font-size:12.5px;line-height:1.85;background:var(--surface);border-left:3px solid var(--l3);padding:16px 20px;margin:0 0 22px;overflow-x:auto;max-width:var(--measure)}
.numbers .v{color:var(--ink);font-weight:600}
.numbers .k{color:var(--ink-3)}
pre{font-family:var(--font-mono);font-size:12.5px;line-height:1.68;background:var(--surface);border-left:3px solid var(--rule-strong);padding:16px 20px;overflow-x:auto;margin:0 0 22px;max-width:var(--measure);color:var(--ink-2)}
.alt{border-bottom:1px solid var(--rule);padding:14px 0;max-width:var(--measure)}
.alt:last-of-type{border-bottom:none}
.alt b{font-family:var(--font-display);font-size:14px;display:block;margin-bottom:3px}
.alt p{font-size:14.5px;margin:0;color:var(--ink-2)}
.alt .verdict{font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--clay)}
footer{border-top:2px solid var(--ink);margin-top:20px;padding-top:22px;font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;color:var(--ink-3);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
.tm td,.tm th{text-align:center}
.tm td:first-child,.tm th:first-child{text-align:left}
.yes{color:var(--l4);font-weight:700}
.no{color:var(--clay);font-weight:700}
.kind{font-family:var(--font-mono);font-size:9px;letter-spacing:.09em;text-transform:uppercase;padding:2px 6px;border-radius:2px;white-space:nowrap;border:1px solid var(--rule-strong);color:var(--ink-3)}
.kind.adv{border-color:var(--clay);color:var(--clay)}
.routes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule);margin:0 0 22px}
.route{background:var(--surface);padding:16px 18px}
.route h4{font-family:var(--font-display);font-weight:750;font-size:14px;margin:0 0 6px}
.route p{font-family:var(--font-display);font-size:12.5px;line-height:1.45;color:var(--ink-2);margin:0;max-width:none}
.route .tag{font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
</style>
<div class="wrap"><header><div class="eyebrow"><span>CUST-ADR-004</span> <span class="stat">accepted · accepted-1</span> <span>the-custodian</span> <span>reviewed 2026-03-26</span><span>generated from canonical source — do not edit</span></div><h1>Connectivity-First Network Posture for Custodian Infrastructure</h1><p class="sub">Source: <code>the-custodian · canon/architecture/adr-004-connectivity-first-network-posture.md · 4039c9d1c08c92014ecc0a65dda63cc73ba187bb</code></p><p class="sub">Review due: 2026-09-26</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#status"><span class="n">·</span>Status</a></li><li><a href="#context"><span class="n">·</span>Context</a></li><li><a href="#decision"><span class="n">·</span>Decision</a></li><li><a href="#rationale"><span class="n">·</span>Rationale</a></li><li><a href="#consequences"><span class="n">·</span>Consequences</a></li><li><a href="#alternatives-rejected"><span class="n">·</span>Alternatives Rejected</a></li></ol></nav><main><section id="status"><h2>Status</h2>
<p>Accepted.</p>
</section>
<section id="context"><h2>Context</h2>
<p>The Custodian infrastructure spans multiple machines: a primary workstation, a shared compute node (COULOMBCORE), and Railiance cluster nodes. Agents running on remote machines need to reach the state-hub API and MCP server, which live on the workstation. Human operators and agents also need to reach remote services (k3s API, Gitea, Temporal) from the workstation.</p>
<p>Two network postures were considered for how these components communicate:</p>
<p><strong>Option A — Connectivity-first:</strong> Components are connected by default via controlled, observable access paths (reverse SSH tunnels managed by ops-bridge). Isolation is added selectively where there is a specific threat model or compliance reason to do so.</p>
<p><strong>Option B — Isolation-first (zero-trust):</strong> No component trusts any other by default. Every connection requires mutual authentication, short-lived credentials, and explicit authorisation at the point of use. Connectivity is earned, not assumed.</p>
<p>This decision is architectural policy — it governs how ops-bridge tunnels are designed, how agent-to-hub communication works, and how new infrastructure components are onboarded.</p>
</section>
<section id="decision"><h2>Decision</h2>
<p><strong>Connectivity-first, with isolation as a deliberate option.</strong></p>
<p>The default posture for Custodian infrastructure is: components that need to work together are connected. Access paths are explicit, observable, and managed (via ops-bridge), but they are persistent by default rather than ephemeral. Isolation is introduced where there is a specific, articulated reason — not as a blanket policy applied uniformly.</p>
</section>
<section id="rationale"><h2>Rationale</h2>
<h3>1. Scale and team size</h3>
<p>The infrastructure is operated by a single human and a bounded set of automation agents. The overhead of zero-trust (credential rotation, SPIFFE/SPIRE identity, mTLS everywhere, short-lived tokens per request) is disproportionate for this scale. It would consume significant operational complexity without a commensurate security return.</p>
<h3>2. Observability over perimeter hardening</h3>
<p>The primary security control at this scale is <strong>observability</strong>: knowing what connected, when, from where, and what it did. ops-bridge provides this — every tunnel is named, actor-attributed, health-checked, and audited. A perimeter of invisible short-lived connections would actually reduce observability.</p>
<h3>3. The threat model does not require zero-trust today</h3>
<p>The main threats are:</p>
<ul><li>A runaway agent consuming resources (mitigated by nproc/memory cgroups)</li><li>A compromised workload reaching state-hub and corrupting state (mitigated by the read-model design of state-hub — write surface is narrow and sanctioned)</li><li>An external attacker reaching internal services (mitigated by the tunnels being reverse SSH — no inbound ports exposed)</li></ul>
<p>Zero-trust would address a different threat model: lateral movement between hostile tenants, or untrusted code running in the same environment as sensitive data. That is not the current situation.</p>
<h3>4. Degrade-gracefully requires persistent connectivity</h3>
<p>The Custodian's foundational value of <strong>local-first, degrade-gracefully</strong> requires that agents can orient themselves even when some connections are slow or partially degraded. Ephemeral connectivity (zero-trust tokens, service mesh sidecars) introduces additional failure modes that conflict with graceful degradation. Persistent SSH tunnels with auto-reconnect are more resilient to intermittent conditions.</p>
<h3>5. Isolation remains the right choice in specific cases</h3>
<p>Connectivity-first does not mean no isolation. The following cases call for explicit isolation and are handled separately:</p>
<ul><li><strong>Tenant separation</strong> (when/if multi-user or multi-org) — each tenant gets its own network segment</li><li><strong>Privileged execution</strong> — CI runners and agent actions with write access to production systems run in ephemeral, isolated environments (per the Privileged Execution Control standard)</li><li><strong>Secrets</strong> — credentials are never transmitted over tunnels in plaintext; age-encrypted at rest, SOPS for config</li></ul>
</section>
<section id="consequences"><h2>Consequences</h2>
<h3>Immediate</h3>
<ul><li>ops-bridge tunnels are <strong>persistent</strong> (max_attempts: 0, auto-reconnect) and are treated as infrastructure, not one-off connections</li><li>Agents on remote machines check tunnel health at session start and restore dropped tunnels before accessing state-hub (documented in global CLAUDE.md)</li><li>New infrastructure components are onboarded with a named tunnel entry in <code>~/.config/bridge/tunnels.yaml</code> — not ad-hoc SSH commands</li></ul>
<h3>Deferred</h3>
<ul><li>If the infrastructure grows to multi-tenant or multi-operator, this decision should be revisited. At that point, isolation-first becomes proportionate.</li><li>If a security audit identifies a specific lateral movement risk, targeted isolation (network policy, mTLS for that service) is the response — not a wholesale posture change.</li></ul>
</section>
<section id="alternatives-rejected"><h2>Alternatives Rejected</h2>
<h3>Zero-trust / isolation-first</h3>
<p>Rejected for current scale. The operational overhead (credential lifecycle, service mesh, mutual TLS) is disproportionate, observability would decrease, and the threat model does not require it. Noted for re-evaluation at multi- tenant scale.</p>
<h3>VPN (WireGuard / Tailscale)</h3>
<p>Considered briefly. VPN would solve the connectivity problem but introduces a persistent network layer that all traffic traverses, reducing the explicitness of individual access paths. ops-bridge tunnels are per-service and per-actor, which gives better observability and blast-radius control. VPN is not ruled out as a future complement but is not the primary approach.</p>
<h3>Ad-hoc SSH (no ops-bridge)</h3>
<p>The pre-ops-bridge approach. Rejected because it has no health checks, no actor attribution, no audit log, and requires manual intervention to restore. ops-bridge formalises the same SSH tunnel pattern with operational discipline.</p>
</section><footer><span>CUST-ADR-004 · accepted-1 · accepted</span><span>the-custodian · canon/architecture/adr-004-connectivity-first-network-posture.md · 4039c9d1c08c92014ecc0a65dda63cc73ba187bb</span></footer></main></div></div></html>

View file

@ -0,0 +1,147 @@
---
id: CUST-ADR-004
type: architecture-decision-record
title: "Connectivity-First Network Posture for Custodian Infrastructure"
status: accepted
owner: the-custodian
revision: "accepted-1"
last_reviewed: "2026-03-26"
review_interval: 6m
decided_by: Bernd Worsch
date: "2026-03-26"
tags: ["architecture", "network", "ops-bridge", "connectivity", "isolation", "security-posture"]
---
# ADR-004: Connectivity-First Network Posture for Custodian Infrastructure
## Status
Accepted.
## Context
The Custodian infrastructure spans multiple machines: a primary workstation, a
shared compute node (COULOMBCORE), and Railiance cluster nodes. Agents running
on remote machines need to reach the state-hub API and MCP server, which live
on the workstation. Human operators and agents also need to reach remote
services (k3s API, Gitea, Temporal) from the workstation.
Two network postures were considered for how these components communicate:
**Option A — Connectivity-first:** Components are connected by default via
controlled, observable access paths (reverse SSH tunnels managed by ops-bridge).
Isolation is added selectively where there is a specific threat model or
compliance reason to do so.
**Option B — Isolation-first (zero-trust):** No component trusts any other by
default. Every connection requires mutual authentication, short-lived
credentials, and explicit authorisation at the point of use. Connectivity is
earned, not assumed.
This decision is architectural policy — it governs how ops-bridge tunnels are
designed, how agent-to-hub communication works, and how new infrastructure
components are onboarded.
## Decision
**Connectivity-first, with isolation as a deliberate option.**
The default posture for Custodian infrastructure is: components that need to
work together are connected. Access paths are explicit, observable, and managed
(via ops-bridge), but they are persistent by default rather than ephemeral.
Isolation is introduced where there is a specific, articulated reason — not as
a blanket policy applied uniformly.
## Rationale
### 1. Scale and team size
The infrastructure is operated by a single human and a bounded set of
automation agents. The overhead of zero-trust (credential rotation, SPIFFE/SPIRE
identity, mTLS everywhere, short-lived tokens per request) is disproportionate
for this scale. It would consume significant operational complexity without
a commensurate security return.
### 2. Observability over perimeter hardening
The primary security control at this scale is **observability**: knowing what
connected, when, from where, and what it did. ops-bridge provides this — every
tunnel is named, actor-attributed, health-checked, and audited. A perimeter of
invisible short-lived connections would actually reduce observability.
### 3. The threat model does not require zero-trust today
The main threats are:
- A runaway agent consuming resources (mitigated by nproc/memory cgroups)
- A compromised workload reaching state-hub and corrupting state (mitigated by
the read-model design of state-hub — write surface is narrow and sanctioned)
- An external attacker reaching internal services (mitigated by the tunnels
being reverse SSH — no inbound ports exposed)
Zero-trust would address a different threat model: lateral movement between
hostile tenants, or untrusted code running in the same environment as sensitive
data. That is not the current situation.
### 4. Degrade-gracefully requires persistent connectivity
The Custodian's foundational value of **local-first, degrade-gracefully**
requires that agents can orient themselves even when some connections are slow
or partially degraded. Ephemeral connectivity (zero-trust tokens, service mesh
sidecars) introduces additional failure modes that conflict with graceful
degradation. Persistent SSH tunnels with auto-reconnect are more resilient to
intermittent conditions.
### 5. Isolation remains the right choice in specific cases
Connectivity-first does not mean no isolation. The following cases call for
explicit isolation and are handled separately:
- **Tenant separation** (when/if multi-user or multi-org) — each tenant gets
its own network segment
- **Privileged execution** — CI runners and agent actions with write access to
production systems run in ephemeral, isolated environments (per the
Privileged Execution Control standard)
- **Secrets** — credentials are never transmitted over tunnels in plaintext;
age-encrypted at rest, SOPS for config
## Consequences
### Immediate
- ops-bridge tunnels are **persistent** (max_attempts: 0, auto-reconnect) and
are treated as infrastructure, not one-off connections
- Agents on remote machines check tunnel health at session start and restore
dropped tunnels before accessing state-hub (documented in global CLAUDE.md)
- New infrastructure components are onboarded with a named tunnel entry in
`~/.config/bridge/tunnels.yaml` — not ad-hoc SSH commands
### Deferred
- If the infrastructure grows to multi-tenant or multi-operator, this decision
should be revisited. At that point, isolation-first becomes proportionate.
- If a security audit identifies a specific lateral movement risk, targeted
isolation (network policy, mTLS for that service) is the response — not a
wholesale posture change.
## Alternatives Rejected
### Zero-trust / isolation-first
Rejected for current scale. The operational overhead (credential lifecycle,
service mesh, mutual TLS) is disproportionate, observability would decrease,
and the threat model does not require it. Noted for re-evaluation at multi-
tenant scale.
### VPN (WireGuard / Tailscale)
Considered briefly. VPN would solve the connectivity problem but introduces
a persistent network layer that all traffic traverses, reducing the
explicitness of individual access paths. ops-bridge tunnels are per-service
and per-actor, which gives better observability and blast-radius control.
VPN is not ruled out as a future complement but is not the primary approach.
### Ad-hoc SSH (no ops-bridge)
The pre-ops-bridge approach. Rejected because it has no health checks, no
actor attribution, no audit log, and requires manual intervention to restore.
ops-bridge formalises the same SSH tunnel pattern with operational discipline.

View file

@ -0,0 +1,59 @@
{
"scope": "Fresh local preview only; no sourcecommit or live publication claimed",
"proof": [
{
"id": "CUST-ADR-002",
"immutable_accepted1_preserved_byte_exact": true,
"served_page_sha256": "ea4eae05b62d50cd8204a47c1a4f3e3e0beda571880ed1de93ccbb17987d84aa",
"current_accepted2_has_dated_review": true,
"original_decided_by": "Bernd Worsch",
"original_decision_date": "2026-03-12",
"current_currency": "current"
},
{
"id": "CUST-ADR-004",
"immutable_accepted1_preserved_byte_exact": true,
"served_page_sha256": "3db1b0f5c50543ff0fb022ea67f1d85b30f8ff691eaa1a2443220b7474b54de7",
"current_accepted2_has_dated_review": true,
"original_decided_by": "Bernd Worsch",
"original_decision_date": "2026-03-26",
"current_currency": "current"
}
],
"records": [
{
"id": "CUST-ADR-002",
"title": "Custodian Agent Runtime \u2014 v0.1 Bootstrap Design",
"status": "accepted",
"revision": "accepted-2",
"owner": "the-custodian",
"last_reviewed": "2026-10-11",
"review_due": "2027-04-11",
"currency": "current",
"lifecycle": "active",
"canonical_path": "adr/custodian-agent-runtime/v1/index.html",
"revision_path": "adr/custodian-agent-runtime/v1/revisions/accepted-2/index.html",
"source_repo": "the-custodian",
"source_path": "canon/architecture/adr-002-custodian-agent-runtime-design.md",
"source_revision": "sha256:b860b02b1e8f47b914d41c6ecf67f451e86326ed51c7c53dcc6b7870fc889e42",
"source_digest": "b860b02b1e8f47b914d41c6ecf67f451e86326ed51c7c53dcc6b7870fc889e42"
},
{
"id": "CUST-ADR-004",
"title": "Connectivity-First Network Posture for Custodian Infrastructure",
"status": "accepted",
"revision": "accepted-2",
"owner": "the-custodian",
"last_reviewed": "2026-10-11",
"review_due": "2027-04-11",
"currency": "current",
"lifecycle": "active",
"canonical_path": "adr/custodian-connectivity-first/v1/index.html",
"revision_path": "adr/custodian-connectivity-first/v1/revisions/accepted-2/index.html",
"source_repo": "the-custodian",
"source_path": "canon/architecture/adr-004-connectivity-first-network-posture.md",
"source_revision": "sha256:4f9978b40c104600c0ccb981a8ee52376a5d3003da4786752029af524b7dede7",
"source_digest": "4f9978b40c104600c0ccb981a8ee52376a5d3003da4786752029af524b7dede7"
}
]
}

View file

@ -0,0 +1,130 @@
{
"review_date": "2026-10-11",
"reviewer": "Dedicated Custodian technical author review under current consolidation assignment",
"scope": "Source/currency review of two accepted historical ADRs; no live audit, ratification, promotion or privileged authorization",
"originals": [
{
"id": "CUST-ADR-002",
"path": "canon/architecture/adr-002-custodian-agent-runtime-design.md",
"original_revision": "accepted-1",
"original_sha256": "6aef66cd5cf71a48f5b4e14401dc19a755e2b182445b272d5952df0eb0dea8ec",
"historical_snapshot": "docs/evidence/2026-10-11-adr-currency-review/adr-002-accepted-1.md",
"annotated_revision": "accepted-2",
"normative_decision_changed": false
},
{
"id": "CUST-ADR-004",
"path": "canon/architecture/adr-004-connectivity-first-network-posture.md",
"original_revision": "accepted-1",
"original_sha256": "3b68adfa6ab329e73f857cf691dc405136d2d66a0135e2c37c236aabe4659557",
"historical_snapshot": "docs/evidence/2026-10-11-adr-currency-review/adr-004-accepted-1.md",
"annotated_revision": "accepted-2",
"normative_decision_changed": false
}
],
"sources": [
{
"path": "/home/worsch/the-custodian/runtime/agent.py",
"sha256": "3b1e6dfd8bfca20d91cd17b75ac03739a97e3ca2dd35719c2b14f76d5eab653e"
},
{
"path": "/home/worsch/the-custodian/runtime/actions.py",
"sha256": "b69f6bc4e8ec463526a578ed28627884df6633bf13d56bc110fb77815d2e0f53"
},
{
"path": "/home/worsch/the-custodian/runtime/context.py",
"sha256": "988e37757360207356b8292599c812ca56450ba53ea6c25fa8486a4b02d17f08"
},
{
"path": "/home/worsch/the-custodian/runtime/README.md",
"sha256": "c9bcfe727705b1503447d863f6fed4a84e97eb18a84b335c9073dac6dbb756e4"
},
{
"path": "/home/worsch/the-custodian/docs/agent-autonomy-decision.md",
"sha256": "4f65dfd2b56b67cd64eb9577644d574d2e687adcfd8b5bdb65a2e0d14f8bf1ff"
},
{
"path": "/home/worsch/the-custodian/docs/agent-environment-orientation.md",
"sha256": "6eb712593adaeb2efd8c69edb22b02662e6c9b155f84f1559e7bdaeb76730439"
},
{
"path": "/home/worsch/the-custodian/docs/kubernetes-change-gate-decision.md",
"sha256": "063d9625f9de2d40d1f4e95f9423a54186fa0b429d76d5f173caa646317a50ab"
},
{
"path": "/home/worsch/the-custodian/.kaizen/agents/custodian-codex/supervision.json",
"sha256": "28b798e04a310eb0c502bb80780f6f0586f61326f1e16e1e85d13863384032b5"
},
{
"path": "/home/worsch/the-custodian/canon/architecture/adr-001-workplans-as-repo-artefacts.md",
"sha256": "183023ee57bae9c29e726fec5ee0361633fe3a2b182436a57869ae4b2a27af24"
},
{
"path": "/home/worsch/the-custodian/canon/architecture/adr-012-projection-source-and-preliminary-overlay.md",
"sha256": "b5e8582459f546ae789ad5fd62f458454aa19997b520e32b6b9f792d6af55987"
},
{
"path": "/home/worsch/the-custodian/canon/standards/autonomy-lanes_v0.1.md",
"sha256": "106cb0c99f73983199c9f1d8491143aa2a2eb41ee450a02ec7968d3763f30399"
},
{
"path": "/home/worsch/ops-bridge/INTENT.md",
"sha256": "2134f693649803414d7ab2f9839416f1fe06169aa86732e64c9dd962c35f412b"
},
{
"path": "/home/worsch/ops-bridge/SCOPE.md",
"sha256": "63cd52d1cdf38e72af0285c25aa2292f959080c6befe565a1d2690747a813975"
},
{
"path": "/home/worsch/net-kingdom/canon/standards/security-layer-model_v0.8.md",
"sha256": "3d8298560069ef471fba28d2b1245fb2309328f231dad1a667cdf0ce5a5a7c8b"
}
],
"test": {
"cwd": "/home/worsch/the-custodian/runtime",
"argv": [
"python3",
"-m",
"pytest",
"-q",
"-p",
"no:cacheprovider",
"tests"
],
"env_overrides": {
"PYTHONPATH": ".",
"PYTHONDONTWRITEBYTECODE": "1"
},
"exit_code": 0,
"stdout": "................................ [100%]\n32 passed in 0.15s\n",
"stderr": ""
},
"findings": [
"ADR002 remains accurate as bootstrap architecture; current deployment claims unverified and not reaffirmed.",
"ADR002 D4 does not supply Codex autopilot, scope authorization or file-source override.",
"ADR004 connectivity-only intent remains compatible; historical Hub topology and broad security-assurance rationale are obsolete or unproven and prominently qualified.",
"Existing September supervision/change gates and accepted NetKingdom model supply mandatory current controls; no fresh authority or posture change requested.",
"accepted-1 bytes retained exactly; publisher must retain existing immutable accepted-1 route and publish reviewed annotations as accepted-2, not overwrite history."
],
"new_human_decision_required": false,
"reason": "No original architectural decision or current authority is expanded; annotations apply existing accepted constraints and correct historical/current scope.",
"historical_publication": {
"strategy": "Explicit per-document historical_revisions preserves hash-pinned actual served accepted-1 HTML from source-owned historical artifacts, even in a clean fresh build. Source snapshot identity/revision/digest and page provenance commit/digest are checked; conflicting immutable existing bytes fail.",
"served_original_source_commit": "4039c9d1c08c92014ecc0a65dda63cc73ba187bb",
"actual_served_pages": [
{
"id": "CUST-ADR-002",
"url": "https://policy.coulomb.social/adr/custodian-agent-runtime/v1/revisions/accepted-1/index.html",
"sha256": "ea4eae05b62d50cd8204a47c1a4f3e3e0beda571880ed1de93ccbb17987d84aa"
},
{
"id": "CUST-ADR-004",
"url": "https://policy.coulomb.social/adr/custodian-connectivity-first/v1/revisions/accepted-1/index.html",
"sha256": "3db1b0f5c50543ff0fb022ea67f1d85b30f8ff691eaa1a2443220b7474b54de7"
}
],
"publisher_tests": "30 tests passed, including five new retention/corruption/escape regression tests.",
"actual_preview": "publisher-preview-proof.json proves both old immutable URLs byte-exact and current accepted-2 annotation pages in a fresh local output. No live publication claimed.",
"inventory": "Only existing two canonical corpus records remain active publications. Historical source/page artifacts live under docs/evidence outside canon/ADR selectors; they are explicit build dependencies in publication.json, not extra governing records or stale documents hidden from currency checks."
}
}