Skip to content

HATCH bulk agent-key issuance ("A")

Date: 2026-05-24 Status: open Supersedes: none Superseded-by: none — current

HERMES Phase 1f closed cleanly (registry entry pinned at 4dd1b98dde8f per 2026-05-24-contract-sha-hash-method-fix-hermes.md), but its CAIRNET pipeline is blocked at the only manual step in onboarding: provisioning the six per-role agent keys. The operator must visit hatch.devarno.cloud/keys, toggle agentMode, type the same producer six times with different roles, then collect six reveal-once keys and paste them into the project’s .petrova/cairnet.env. For HERMES alone that is six form submits; across the registry’s roster (≈30 governed children × 6 roles = ≈180 issuances) it is roughly 90 minutes of pure tedium plus a non-trivial mis-paste failure rate.

The airlock SDK already exposes the primitive (createAgentKey({producer, role})) and the /agents page already lists AdminAgent[] read-only, so the gap is purely UX + a thin batch wrapper — not a missing capability. This is the minimum change that removes the tedium without changing substrate shape, and it is reversible.

A separate decision (2026-05-24-petrova-cairnet-onboard-wire-keys.md, “D”) covers the eventual substrate-side closure that lets petrova cairnet-onboard --wire-keys provision automatically. A and D are complementary: A unblocks today’s operator flow; D removes the UI step from future bootstraps. We are landing both because A ships in ~1 day and protects every already-bootstrapped repo while D matures.

Add bulk agent-key issuance to HATCH, paired with a thin airlock createAgentKeys wrapper.

  1. Airlock — add createAgentKeys(inputs: Array<{producer, role}>): Promise<Array<{agent, key}>> that loops createAgentKey server-side inside a single admin-session call, returning all minted credentials in one response. No new persistence semantics; reuses existing per-role <producer>-<role>-NNN agent-id generation and reveal-once key contract.
  2. HATCH /keys — extend IssueKeyForm.tsx’s agentMode toggle with a “Bulk” sub-mode. Input is a single textarea, one <producer>:<role> per line, validated against the CAIRNET role enum (fleet|coordinator|reviewer|planner|bridge|other). Submission calls issueAgentKeys(inputs) (new server action) which delegates to airlock.createAgentKeys. Result view renders a ready-to-paste .env block plus a “Download as .env” button.
  3. No /agents page changes in this decision. That surface remains read-only here; restructuring it is out of scope (deferred until D ships and we re-evaluate whether /keys should fold into /agents entirely).

Scope explicitly excludes: project-aware wizards, integration presets beyond CAIRNET, persistence of unrevealed keys, retrieval of previously-issued keys. Those belong to D or to a later HATCH restructure.

  • Move issuance to /agents and drop /keys agent-mode entirely — right placement long-term, but per-agent (doesn’t solve bulk) and requires /agents to grow write affordances we’d rebuild once D lands. Reject as the next step; revisit post-D.
  • Project-aware “Provision keyset” wizard on /agents — collapses six form submits to one click, but hardcodes CAIRNET’s role enum into HATCH product code. The role enum is a substrate fact (lives in schemas/agentic-emission.v1.json), so this baking is misplaced. Reject; the right home is D.
  • Skip the UI change entirely, only ship D — leaves every already-bootstrapped repo (HERMES today, future repos before D ships) stuck behind the manual flow. Reject; the cost of A is ~1 day and it derisks D’s rollout.
  • Allow free-form agent_id instead of auto-generated <producer>-<role>-NNN — would let operators name agents, but breaks CAIRNET’s agent_id regex ^[a-z0-9-]+-(fleet|coordinator|reviewer|planner|bridge|other)-\d{3}$ and makes the agent→role mapping non-mechanical. Reject; the auto-generated shape is load-bearing.

For code:

  • airlock: new createAgentKeys SDK method + admin route handler; thin loop over existing primitive.
  • hatch: IssueKeyForm.tsx gains Bulk sub-mode (textarea + parser + multi-row result view); new issueAgentKeys server action in src/app/keys/actions.ts; new client SDK method in src/lib/airlock.ts.
  • No DB changes; no schema migrations.

For docs:

  • hatch/README.md (or new hatch/docs/operator-guide.md if it warrants its own surface) gets a Bulk-issuance section with the producer:role-per-line format.
  • This ADR is referenced from D as its complement.

For in-flight phases:

  • Unblocks HERMES CAIRNET emission (envelope at hermes/.petrova/cairnet-buffer/2026-05-24T08:18:25Z-verify.json is still buffered awaiting key).
  • Removes the “PREREQUISITE A (operator, manual)” footnote from petrova cairnet-onboard output for the duration before D lands.

For invariants:

  • None added/removed/modified. Stays inside MR-7 (this is a new decision, not an edit of a closed one) and respects the push-down policy (substrate role enum stays in substrate; HATCH only validates against it).
  • docs/decisions/2026-05-24-hatch-bulk-agent-key-issuance.md companion: docs/decisions/2026-05-24-petrova-cairnet-onboard-wire-keys.md (“D”)
  • docs/decisions/2026-05-24-contract-sha-hash-method-fix-hermes.md (preceding work that closed HERMES Phase 1f and surfaced the key-provisioning bottleneck)
  • Schema: petrova-codes/contracts/vendor/agentic-emission.v1.json (role enum + agent_id regex)
  • Code surfaces: hatch/src/app/keys/IssueKeyForm.tsx, hatch/src/app/keys/actions.ts, hatch/src/lib/airlock.ts:349,393