Skip to content
This repository was archived by the owner on Aug 10, 2026. It is now read-only.

feat(agent): dual-discovery — register agents from #agent/thread alongside #agent/definition (rc.22) - #178

Merged
unforced merged 1 commit into
mainfrom
ag-unforced-dev
Jun 30, 2026
Merged

feat(agent): dual-discovery — register agents from #agent/thread alongside #agent/definition (rc.22)#178
unforced merged 1 commit into
mainfrom
ag-unforced-dev

Conversation

@unforced

Copy link
Copy Markdown
Contributor

Phase 4a of the threads-roles-context flatten (committed-core). The daemon now discovers agents from #agent/thread notes alongside #agent/definition, so the def can later be deleted. ADDITIVE + SAFE + reversible — in the default both mode the def path is untouched and every currently-registered agent registers exactly as today.

What changed

  • parseThreadSpec(threadNote) → AgentSpec (pure): name = metadata.agent; backend/model/mode/definition + sandbox knobs (workspace/filesystem/network/egress/mounts) from thread metadata, safe defaults when absent (the live agents run on defaults). systemPrompt is EMPTY — identity composes from roles①+thread-content②+loadout③ at turn time; the thread body is the rolling summary, never the prompt. Shares the field validators with parseAgentDef (extracted parseBackendField/parseModeField/parseModelField/parseSandboxKnobs — behavior-preserving).
  • DefVaultClient.listThreadNotes() — queries the exact #agent/thread tag, include_content unset.
  • Dual-list + register in loadAll, deduped by name — defs first, then threads; a thread whose name a def already registered dedups (def wins in both). New reloadThread mirrors reload for the reactive path; both mode-gated. New POST /api/vault/agent-thread webhook + a thread-watch CREATE trigger (create-only — per-turn upserts would churn the registry; edits converge via the 60s poll).
  • agent_status (metadata.agent_status, default enabled) — the enable/disable axis, DISTINCT from metadata.status (the turn outcome, never read here). Discovery SKIPS a disabled thread, so a retired agent isn't re-registered from its thread.
  • Toggle PARACHUTE_AGENT_DISCOVERY = both(default) | thread | def — the cutover is a flag-flip + restart, not a new PR.
  • Observability: source: "def"|"thread" on the agent listing + a per-agent boot log line.
  • Routing UNCHANGED. Thread-discovery never tears down via the poll (teardown stays on the def-set diff + the thread-status webhook + reloadThread(deleted)); no status stamp / no grant registration for thread-sourced agents (only #agent/role carries capability).

Safety

The 4am weave has no retry. In both mode (default) the def path is completely untouched — loadAll lists defs first and instantiates them exactly as before; threads only dedup against them (or add agents with no def). Verified: full suite 1413 pass / 0 fail, tsc --noEmit clean. No test boots a live daemon (ephemeral Bun.serve({port:0}) + injected recorders).

Cutover (orchestrator's gated steps, not in this PR)

  1. Deploy rc.22 in both (default) — additive, byte-identical for uni/eco-civ.
  2. Mark agent_status: disabled on the retired agents' threads (steward, uni-evolve, uni-weaver).
  3. Flip PARACHUTE_AGENT_DISCOVERY=thread, restart, confirm source=thread, watch a real weave fire.
  4. Only then delete the def notes (rollback saved); a later PR removes the def-discovery code.

🤖 Generated with Claude Code

https://claude.ai/code/session_01T6HAzWsgiJy4ndsWSCWY5S

…gside #agent/definition (rc.22)

Phase 4a of the threads-roles-context flatten. The daemon now discovers agents
from `#agent/thread` notes ALONGSIDE `#agent/definition`, so the def can later be
deleted. ADDITIVE + SAFE — in the default `both` mode the def path is untouched and
every currently-registered agent registers exactly as today (threads dedup against
defs; def wins on a name collision).

- `parseThreadSpec(threadNote) → AgentSpec` (pure): name = metadata.agent; backend/
  model/mode/definition + sandbox knobs from thread metadata (safe defaults when
  absent); systemPrompt EMPTY (identity composes from roles①+thread-content②+loadout③
  at turn time — the body is the rolling summary, never the prompt). Shares the field
  validators with parseAgentDef (extracted parseBackendField/parseModeField/
  parseModelField/parseSandboxKnobs — behavior-preserving refactor).
- `DefVaultClient.listThreadNotes()` — queries the exact `#agent/thread` tag,
  include_content unset (body isn't the prompt).
- Dual-list + register in loadAll, deduped by name (defs first → threads dedup; DEF
  WINS in `both`). `reload` (def) + new `reloadThread` (thread) cover the reactive
  path; both mode-gated. New `POST /api/vault/agent-thread` webhook + a thread-watch
  CREATE trigger (create-only — per-turn upserts would churn; edits converge via the
  60s poll).
- `agent_status` (metadata.agent_status, default enabled) — the enable/disable axis,
  DISTINCT from metadata.status (the turn outcome). Discovery SKIPS a `disabled`
  thread so a retired agent isn't re-registered from its thread.
- Discovery toggle `PARACHUTE_AGENT_DISCOVERY` = both(default)|thread|def — the cutover
  is a flag-flip + restart, not a new PR. `thread` proves thread-discovery in isolation
  before deleting defs; `def` is the escape hatch.
- Observability: `source: "def"|"thread"` on the agent listing + a per-agent boot log
  line ("instantiated X from def/thread … source=…").
- Routing UNCHANGED. Thread-discovery is purely additive (never tears down via the
  poll — teardown stays on the def-set diff + thread-status webhook + reloadThread).
  No status stamp / no grant registration for thread-sourced agents (only #agent/role
  carries capability).

Tests: parseThreadSpec, listThreadNotes, dual-discovery dedupe, agent_status skip,
the live shape (empty def body + thread → one agent), the toggle, reloadThread mode-
gating + deleted-teardown, reactive-ordering (def evicts a thread registered first),
the agent-thread webhook. Full suite 1413 pass / 0 fail; typecheck clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T6HAzWsgiJy4ndsWSCWY5S
@unforced
unforced merged commit 47656ff into main Jun 30, 2026
2 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant