diff --git a/.specify/feature.json b/.specify/feature.json index add480a..8dff5f9 100644 --- a/.specify/feature.json +++ b/.specify/feature.json @@ -1,3 +1,3 @@ { - "feature_directory": "specs/005-container-thin-client" + "feature_directory": "specs/006-agent-registration" } diff --git a/CLAUDE.md b/CLAUDE.md index b582293..dc28887 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,7 +1,7 @@ For additional context about technologies to be used, project structure, shell commands, and other important information, read the current plan: -`specs/005-container-thin-client/plan.md`. +`specs/006-agent-registration/plan.md`. # AgentTower Agent Context diff --git a/specs/006-agent-registration/checklists/agent-registry-quality.md b/specs/006-agent-registration/checklists/agent-registry-quality.md new file mode 100644 index 0000000..9883e0d --- /dev/null +++ b/specs/006-agent-registration/checklists/agent-registry-quality.md @@ -0,0 +1,81 @@ +# Combined Quality Checklist: Agent Registration and Role Metadata + +**Purpose**: Unit-test the FEAT-006 requirements for security, CLI contract, data model, and concurrency quality. Validates whether the spec is *written* well enough — complete, unambiguous, consistent, measurable — for implementation and PR review. Does NOT verify behavior of the implementation. + +**Created**: 2026-05-07 +**Walked**: 2026-05-07 (post-`/speckit.analyze` fixes) +**Feature**: [spec.md](../spec.md) +**Companion artifacts**: [plan.md](../plan.md), [research.md](../research.md), [data-model.md](../data-model.md), [contracts/cli.md](../contracts/cli.md), [contracts/socket-api.md](../contracts/socket-api.md), [quickstart.md](../quickstart.md) +**Audience**: spec author (pre-`/speckit.tasks` gate) AND PR reviewer (post-implementation) +**Depth**: standard release gate (~30 items across four domains) + +## Security: Master safety boundary + +- [x] CHK001 Are the conditions for `set-role --role master --confirm` to succeed enumerated as a closed set (target exists, target `active=true`, container `active=true`)? [Completeness, Spec §FR-011] — **Resolved**: FR-011 enumerates "if and only if the target agent exists, is `active=true`, and the target's bench container is `active=true`". +- [x] CHK002 Is the rationale for *asymmetric* master-promotion vs. demotion gating (`--confirm` required for promote; not for demote) documented so reviewers can judge whether the trade-off is intentional? [Clarity, Spec §FR-013] — **Resolved**: FR-013 documents the asymmetry explicitly and defers in-flight prompt arbitration to FEAT-009/FEAT-010. +- [x] CHK003 Are the three master-rejection paths (`register-self --role master`, `set-role --role master` without `--confirm`, `set-role --role swarm`) each mapped to a *distinct* closed-set error code? [Consistency, Spec §FR-010, §FR-011, §FR-012] — **Resolved**: `master_via_register_self_rejected` (FR-010), `master_confirm_required` (FR-011), `swarm_role_via_set_role_rejected` (FR-012). Three distinct codes in FR-040. +- [x] CHK004 Is the behavior of `set-role --role master --confirm` when the agent's container becomes inactive *between* validation and write specified, or is the spec silent on this race? [Coverage, Edge Case, Gap] — **Resolved by Clarifications session 2026-05-07-continued Q3**: FR-011 now mandates atomic re-check inside `BEGIN IMMEDIATE`; SQLite serialization closes the race window. +- [x] CHK005 Does the spec specify whether `confirm_provided=true` is recorded on demotion paths that were called *with* `--confirm` even though `--confirm` was unnecessary? [Ambiguity, Spec §FR-014] — **Resolved by Clarifications session 2026-05-07-continued Q5**: FR-014 locks the literal-value rule; demotion-with-redundant-`--confirm` logs `confirm_provided: true`. + +## Security: Audit log and observability + +- [x] CHK006 Is the JSONL audit row *required field set* defined as a closed set, with explicit handling for any future field additions (additive-only vs. version-bumped)? [Completeness, Spec §FR-014] — **Deferred** to [opensoft/AgentTower#7](https://github.com/opensoft/AgentTower/issues/7). FR-014 + data-model.md §4.4 lock the closed required field set (`agent_id`, `prior_role`, `new_role`, `confirm_provided`, `socket_peer_uid`, `ts_utc`, `event_type`). Future-field-additions policy (additive-only vs. version-bumped) is documented as a known gap in the issue; addressed when the first audit-log consumer ships (FEAT-007 or FEAT-008). No FEAT-006 implementation impact. +- [x] CHK007 Is the source of `socket_peer_uid` (FEAT-002 `SO_PEERCRED` inheritance) explicitly stated in the spec rather than left implicit? [Traceability, Spec §FR-014, Gap] — **Resolved**: data-model.md §4.4 says "`socket_peer_uid` — host uid of the calling process, from FEAT-002 `SO_PEERCRED`." +- [x] CHK008 Are the conditions under which an audit row MUST NOT be appended fully enumerated (failed transitions per FR-014; no-op writes per FR-027; `set-label` and `set-capability` calls)? [Completeness, Spec §FR-014, §FR-027] — **Resolved**: FR-014 (failed → no row), FR-027 (no-op → no row), FR-014 + contracts/socket-api.md §2.4–2.5 (set-label / set-capability never audit). All three conditions enumerated. +- [x] CHK009 Is the audit-append ordering relative to the SQLite COMMIT specified (append after COMMIT, never before; rollback ⇒ no append)? [Clarity, Spec §FR-035] — **Resolved**: data-model.md §5.3 explicitly states "after the SQLite COMMIT and only when `prior_role != new_role`. Failed COMMITs (rolled back) MUST NOT append a row". +- [x] CHK010 Does the spec guarantee that `prior_role: null` is encoded as the JSON literal `null` (and not as a missing key, empty string, or `"null"` string)? [Clarity, Spec Clarifications 2026-05-07 Q4] — **Resolved**: Clarifications Q4 locks "the audit row's `prior_role` MUST be the JSON literal `null`"; data-model.md §4.4 reaffirms. + +## Security: Wire encoding, sanitization, and trust boundary + +- [x] CHK011 Is the supplied-vs-default wire contract specified at *both* ends (CLI MUST NOT transmit defaults; daemon MUST distinguish absent key from default value), so a future CLI rewrite cannot regress the silent-demotion property? [Completeness, Spec §FR-007, §FR-028, Clarifications Q1] — **Resolved**: Round 1 Q1 + FR-007 (daemon side) + FR-028 (CLI side) both explicitly require absent-key encoding for omitted flags. Research R-002 + contracts/socket-api.md §2.1 amplify the wire contract. +- [x] CHK012 Are the FR-033 / FR-034 sanitization rules consistent with the FEAT-004 sanitization rules they claim to inherit, with no FEAT-006-specific divergence (NUL strip, C0 strip, multi-byte-safe truncation, per-field length cap)? [Consistency, Spec §FR-033, §FR-034] — **Resolved**: FR-033 explicitly inherits FEAT-004 rules; research R-022 reuses `agenttower.tmux.parsers.sanitize_text`; bounds (label ≤ 64, project_path ≤ 4096) are FEAT-006-specific but the algorithm is FEAT-004's verbatim. +- [x] CHK013 Is the trust boundary for FR-024 (CLI resolves container_id; daemon does NOT re-derive from socket peer) explicitly tied to the host-user-only socket-file authorization assumption, so reviewers can judge whether spoofing is in or out of the threat model? [Traceability, Spec §FR-024, §FR-043, Assumption] — **Resolved**: FR-024 + FR-043 + Assumptions ("the new socket methods inherit the FEAT-002 socket-file authorization (`0600`, host user only) verbatim") + edge case line 78 (bench user vs socket peer uid as descriptive metadata, not auth boundary). The trust model is: host-user access to the socket = trusted; container internals are not a security boundary. +- [x] CHK014 Is the `parent_immutable` rejection's atomicity guarantee (no mutable field updated when other fields are also supplied; transaction rolled back; no audit row) specified rather than left to inference? [Clarity, Spec §FR-018a, Clarifications Q3] — **Resolved**: FR-018a explicitly locks "rejection is atomic and all-or-nothing" with the three guarantees enumerated. + +## CLI contract: Locked TSV column schema + +- [x] CHK015 Does the spec disambiguate whether the `PARENT` column renders as the bare 12-hex portion, the full `agt_<12-hex>` form (16 chars), or some other short form — given that `AGENT_ID` already uses the full form? [Ambiguity, Spec Clarifications Q5, Conflict with contracts/cli.md] — **Resolved by Clarifications session 2026-05-07-continued Q1**: PARENT renders full `agt_<12-hex>`; only CONTAINER uses the bare 12-char short form. FR-029 locked. +- [x] CHK016 Is the rendering of free-text fields (`LABEL`, `PROJECT`) that contain literal `\t` or `\n` after FR-033 sanitization specified (replace with single space, drop, escape, or reject)? [Coverage, Spec §FR-029, Gap] — **Resolved**: contracts/cli.md C-CLI-602 specifies "embedded `\t` and `\n` in `LABEL`/`PROJECT` after FR-033 sanitization replaced with single space (matches FEAT-005 cli.md convention)". Note: FR-033 inherits FEAT-004 C0-stripping which already removes `\t` (0x09) and `\n` (0x0A) before TSV rendering, so the case is largely defensive. +- [x] CHK017 Are the closed-set rendering tokens for `ACTIVE` (`true`/`false`) and `PARENT` (`-` for null) specified verbatim, or could implementations diverge (`yes`/`no`, `null`, `none`)? [Clarity, Spec §FR-029] — **Resolved**: FR-029 says verbatim "ACTIVE MUST render as the literal `true` or `false`" and "the literal `-` (single ASCII hyphen) when null". +- [x] CHK018 Is the contract for "future fields go to `--json` or `--wide`, never the default form" expressed as a hard rule rather than guidance, so reviewers can reject silent column additions? [Clarity, Spec Clarifications Q5] — **Resolved**: FR-029 (post-clarification) uses "MUST NOT" hard rule: "Any future fields added to the agent record MUST NOT be added to the default form; they MUST be exposed via `--json` or a separately-introduced `--wide` flag". + +## CLI contract: Error codes and JSON purity + +- [x] CHK019 Are the closed-set error codes specified to appear verbatim in *both* `--json` output AND human-readable stderr, or only in `--json` (FR-040 mentions only the latter)? [Coverage, Spec §FR-040, Gap] — **Resolved**: contracts/cli.md cross-cutting CLI rules: "every failure path surfaces a closed-set error code that appears verbatim in `--json` output and in the human-readable stderr message". Spec FR-040 is amplified by contracts. +- [x] CHK020 Is `--json` purity defined (exactly one JSON object on stdout per invocation; no incidental stderr lines except daemon-unavailable; canonical key order)? [Completeness, Spec §FR-028, Gap] — **Resolved**: contracts/cli.md cross-cutting "`--json` purity" subsection: "exactly one JSON object per invocation. No incidental human-readable lines on stderr (except the standard FEAT-002 daemon-unavailable message)". Canonical key order is locked by the example shapes in C-CLI-601..605 and snapshot tests (T031, T039, T065). +- [x] CHK021 Are the exit-code mappings for the five new CLIs specified across the full success / no-op / closed-set-error / daemon-unavailable / internal-error matrix, with the same code never overloaded across distinct semantic outcomes? [Coverage, Spec §FR-032, §FR-040] — **Resolved**: contracts/cli.md C-CLI-601 has the explicit table (`0` success / no-op, `1` closed-set error, `2` daemon-unavailable, `4` reserved internal CLI). C-CLI-602..605 inherit the same mapping. No overloading. +- [x] CHK022 Is the case-sensitivity contract for closed-set values (`role`, `capability`) and identifier values (`container_id` 12-char short, `parent_agent_id`) explicitly defined, so reviewers can judge whether `Slave` should equal `slave`? [Clarity, Spec §FR-004, §FR-005, §FR-026, Gap] — **Resolved by Clarifications session 2026-05-07-continued Q2**: FR-001/004/005/026 all updated; case-sensitive matching mandated; mixed-case rejected with `value_out_of_set`, never normalized. + +## Data model: Schema, migration, and derived state + +- [x] CHK023 Does the spec specify the exact migration ordering rule (table created → `schema_version` updated, never the reverse) so that crash-during-migration leaves `v3` rather than `v4`-without-table? [Clarity, Spec §FR-036] — **Resolved**: FR-036 says "MUST bump `schema_version` only after the new table exists". data-model.md §3 reaffirms. +- [x] CHK024 Are the JSON key ordering and value types of the `effective_permissions` column specified concretely enough that snapshot tests can lock the form (e.g., always `{"can_send", "can_receive", "can_send_to_roles"}` with `can_send_to_roles` always an array even when empty)? [Measurability, Spec §FR-021] — **Resolved**: FR-021 locks the closed-set fields; data-model.md §4.3 TypedDict; research R-008 derivation table; task T006 pins the JSON key ordering as `["can_send", "can_receive", "can_send_to_roles"]` and `can_send_to_roles` always a list incl. empty `[]`. Snapshot test T015 will lock at runtime. +- [x] CHK025 Is the relationship between `agents` and `panes` (denormalized, no foreign key, application-layer reconciliation) specified with enough detail that a reader can reason about referential integrity without consulting the implementation? [Completeness, Spec §FR-037] — **Resolved**: FR-037 explicitly states "denormalized columns rather than by foreign-key constraint... referential integrity is enforced at the application layer". data-model.md §5.1 documents the cross-table relationship and reconciliation transactions. +- [x] CHK026 Is the `agent_id` collision-handling policy (retry budget; failure-mode error code) defined in the spec proper rather than only in research / implementation? [Coverage, Spec §FR-001, Gap] — **Resolved by C2 fix**: FR-001 now states "bounded retry loop (≤ 5 attempts) under the per-(container_id, pane_composite_key) registration mutex; an exhausted retry budget MUST surface as `internal_error` (FR-035)". +- [x] CHK027 Is the `last_seen_at` column type and nullability defined (TEXT, nullable until first scan observation), so the `null`-to-string transition is unambiguous in `--json` output? [Clarity, Spec §FR-009a, Gap] — **Resolved**: data-model.md §2.1 SQL declares `last_seen_at TEXT` (nullable, no NOT NULL); §2.1 notes explicitly say "nullable on creation"; contracts/socket-api.md §6.2 example shows `"last_seen_at": null`. +- [x] CHK028 Are the consequences of re-activating an inactive agent at the same composite key when the *previously stored* `parent_agent_id` was non-null and the re-registration omits `--parent` specified — does parent persist (per FR-018) or get re-validated against the now-possibly-changed parent state? [Coverage, Spec §FR-008, §FR-018, Gap] — **Resolved**: FR-018 locks "swarm child's `parent_agent_id` MUST remain pointing at the historical parent" + Round 1 Q1 (omitted flag = leave unchanged) + FR-008 (re-activation preserves `parent_agent_id`). Composing these three: omitting `--parent` on re-activation preserves stored value with no re-validation. + +## Concurrency: Mutex layout and atomicity + +- [x] CHK029 Is the lifecycle of the per-(container, pane-key) and per-`agent_id` mutex maps specified (creation on demand; never evicted; bounded by MVP agent count), so memory growth is auditable? [Completeness, Spec §FR-038, §FR-039, Gap] — **Resolved**: research R-005 + plan.md "Scale/Scope": "The advisory mutex maps grow with the number of distinct pane composite keys / agent_ids observed per daemon lifetime; entries are not evicted (memory overhead is bounded by MVP agent count)." +- [x] CHK030 Is the mutex / SQLite-transaction nesting order specified (acquire mutex → BEGIN IMMEDIATE → COMMIT or ROLLBACK → release mutex), so reviewers can rule out lost-update races on `effective_permissions`? [Clarity, Spec §FR-035, §FR-038] — **Resolved**: data-model.md §7.3 (register_agent steps 12–22) and §7.3a (set_role steps 9–19) both explicitly enumerate "acquire mutex → BEGIN IMMEDIATE → write/COMMIT or ROLLBACK → release mutex". Plan.md Constraints reaffirms. +- [x] CHK031 Does the spec address potential deadlock or contention between (a) FEAT-004 pane reconciliation transactions that update `last_seen_at` on agents and (b) in-flight `register_agent` transactions touching the same composite key? [Coverage, Spec §FR-009a, §FR-038, Gap] — **Resolved by Clarifications session 2026-05-07-continued Q4**: FR-038 + plan.md + contracts/socket-api.md §5 explicitly state cross-subsystem ordering via SQLite `BEGIN IMMEDIATE`; FEAT-004 reconciliation does not acquire the FEAT-006 mutex; last committed transaction wins; `SQLITE_BUSY` → `internal_error` without retry. +- [x] CHK032 Is the convergence-on-same-`agent_id` property under concurrent `register-self` from the same pane defined as a *guarantee* rather than a likely outcome, with a specified ordering (first INSERT, second UPDATE; both observe post-commit state)? [Measurability, Spec Edge Case line 72] — **Resolved**: spec edge case line 72 mandates the property as a MUST: "Both MUST converge on the same `agent_id`; the second call's mutable-field writes MUST not race with the first call's insert". Research R-017 documents the INSERT-then-UPDATE ordering under the per-key mutex. + +## Cross-cutting consistency + +- [x] CHK033 Are spec, plan, research, data-model, and contracts/* internally consistent on the question of "does the daemon apply default values on first registration" — Q1 lock states the wire is symmetric; do all artifacts agree? [Consistency, Spec Clarifications Q1, Cross-artifact] — **Resolved**: spec FR-007 + FR-028 (CLI applies defaults on first registration), research R-002 (daemon also applies defaults on first registration so wire is symmetric), data-model.md §4.2 + §6.1, contracts/socket-api.md §2.1 — all four artifacts agree. +- [x] CHK034 Are FR-040's listed closed-set error codes a strict superset of every code referenced in user stories, edge cases, and acceptance scenarios — or do any narrative passages reference codes that aren't in the closed set? [Consistency, Spec §FR-040] — **Resolved**: walked all edge cases and acceptance scenarios; every error-code reference appears in FR-040's closed set (incl. `parent_immutable` added by Round 1 Q3 fix). No drift detected. +- [x] CHK035 Is the FEAT-005 forward-compat policy ("daemon newer than CLI ⇒ refuse") consistently applied to all five new CLIs, including read-only `list-agents`, with the same `schema_version_newer` exit-code behavior? [Consistency, Spec Edge Case line 79] — **Resolved**: spec edge case line 79 explicitly enumerates all five CLIs ("`register-self`, `list-agents`, `set-role`, `set-label`, `set-capability` MUST inherit the FEAT-005 forward-compat policy"). Research R-018 reaffirms the read-only `list-agents` is also subject. +- [x] CHK036 Are the FEAT-006 success criteria (SC-001..SC-012) each traceable to at least one functional requirement, and does every functional requirement that introduces observable behavior have at least one SC entry that locks measurable acceptance? [Traceability, Spec §SC-001..SC-012] — **Resolved**: walked the SC ↔ FR matrix in `/speckit.analyze` coverage table — 100% bi-directional coverage; every observable-behavior FR has at least one SC. + +--- + +## Walk Summary + +- **Walked**: 36 of 36 items +- **Resolved (`[x]`)**: 35 +- **Deferred (`[x]` with issue link)**: 1 (CHK006 → [opensoft/AgentTower#7](https://github.com/opensoft/AgentTower/issues/7)) +- **Outstanding (`[ ]`)**: 0 +- **Resolution sources**: spec.md (incl. Clarifications Round 1 Q1–Q5 and Round 2 Q1–Q5), plan.md, research.md, data-model.md, contracts/cli.md, contracts/socket-api.md, post-`/analyze` fixes (C1, I1–I4, S1, C2) +- **Verdict**: ✅ **PASS** — implementation is unblocked. CHK006 is a documentation-quality nit deferred to a tracked issue; no FEAT-006 implementation impact. diff --git a/specs/006-agent-registration/checklists/requirements.md b/specs/006-agent-registration/checklists/requirements.md new file mode 100644 index 0000000..2f7c2bf --- /dev/null +++ b/specs/006-agent-registration/checklists/requirements.md @@ -0,0 +1,37 @@ +# Specification Quality Checklist: Agent Registration and Role Metadata + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-05-07 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- The spec uses the same SQLite/JSONL/Unix-socket vocabulary that prior FEAT-001..FEAT-005 specs in this repo already use; these are operational contracts for AgentTower's MVP, not implementation language choices, and matching them is required for cross-feature consistency. +- The `agt_<12-hex>` agent_id shape is locked at the contract layer (see FR-001, Assumption block) so downstream features (FEAT-009 routing, FEAT-010 arbitration) have a stable identifier shape; this is a contract decision, not an implementation detail. +- Master safety is encoded as: (a) `register-self` cannot ever assign `role=master`; (b) `set-role --role master` requires `--confirm`; (c) `set-role --role swarm` is rejected outright (swarm role is only set via `register-self --parent`). This three-pronged boundary closes every silent-escalation path. +- Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan`. diff --git a/specs/006-agent-registration/contracts/cli.md b/specs/006-agent-registration/contracts/cli.md new file mode 100644 index 0000000..399cab5 --- /dev/null +++ b/specs/006-agent-registration/contracts/cli.md @@ -0,0 +1,439 @@ +# CLI Contracts: Agent Registration and Role Metadata + +**Branch**: `006-agent-registration` | **Date**: 2026-05-07 + +This document is the authoritative contract for the five additive +CLI surfaces FEAT-006 introduces. It supplements `spec.md` +FR-028, FR-029, FR-030, FR-031, FR-032, FR-040, and Clarifications +2026-05-07 Q1, Q3, Q4, Q5. Anything here overrides informal CLI +descriptions in spec.md. + +FEAT-001 / FEAT-002 / FEAT-003 / FEAT-004 / FEAT-005 CLI surfaces +are unchanged byte-for-byte (SC-010). + +--- + +## C-CLI-601 — `agenttower register-self` + +### Synopsis + +```text +agenttower register-self [--role ] [--capability ] [--label ] + [--project ] [--parent ] + [--json] +``` + +### Behavior + +Resolves the caller's container id (FEAT-005) and tmux pane +composite key (FEAT-005 + FEAT-004 lookup; FR-041 focused rescan +on miss), then registers (or idempotently re-registers / +re-activates) the bound agent via the daemon's `register_agent` +socket method. + +### Flags + +| Flag | Argparse default | Wire behavior on absence | Notes | +| ---- | ---------------- | ------------------------ | ----- | +| `--role ` | `argparse.SUPPRESS` | absent in JSON params | First registration applies CLI default `unknown` server-side; idempotent re-registration leaves stored role unchanged (Clarifications Q1). `r` ∈ `{master, slave, swarm, test-runner, shell, unknown}`. `--role master` is rejected unconditionally per FR-010. | +| `--capability ` | `argparse.SUPPRESS` | absent in JSON params | Same semantics. `c` ∈ `{claude, codex, gemini, opencode, shell, test-runner, unknown}`. | +| `--label ` | `argparse.SUPPRESS` | absent in JSON params | Sanitized + bounded to 64 chars (FR-033). Empty string is allowed when explicitly passed. | +| `--project ` | `argparse.SUPPRESS` | absent in JSON params | Validated as absolute, NUL-free, no `..` segment, ≤ 4096 chars (FR-034). | +| `--parent ` | `argparse.SUPPRESS` | absent in JSON params | Only valid with `--role swarm` (FR-016). Re-registration with a *different* `--parent` rejected `parent_immutable` (Clarifications Q3). | +| `--json` | flag | — | Emit one JSON object on stdout; suppress human-readable output. | + +### Exit codes (FR-032 + FR-040) + +The FEAT-006 CLI handlers follow the FEAT-002 / FEAT-005 +exit-code surface (daemon errors → `3`) rather than the +spec-prose `1`-for-error sketch. The `1` slot is reserved for +client-side context errors (today only `host_context_unsupported`). + +| Pattern | Exit code | +| ------- | --------- | +| Successful registration / idempotent re-registration / re-activation | `0` | +| `host_context_unsupported` (running on the host shell, not in a bench container) | `1` | +| `daemon_unavailable` (daemon down) | `2` (FEAT-002 inheritance) | +| Any other closed-set error code (e.g., `container_unresolved`, `not_in_tmux`, `tmux_pane_malformed`, `pane_unknown_to_daemon`, `master_via_register_self_rejected`, `swarm_parent_required`, `parent_role_mismatch`, `parent_not_found`, `parent_inactive`, `parent_role_invalid`, `parent_immutable`, `value_out_of_set`, `field_too_long`, `project_path_invalid`, `schema_version_newer`) | `3` | +| Internal CLI error | `4` (reserved per FEAT-002) | + +### Default output + +One `key=value` line per field on stdout (matching the +established multi-line key=value style FEAT-002 / FEAT-005 use for +single-record success output): + +```text +agent_id= +role= +capability= +label=