Skip to content

refactor: collapse multi-agent model into a single @deuce agent#34

Merged
clintberry merged 6 commits into
mainfrom
feat/single-deuce-agent
Jun 10, 2026
Merged

refactor: collapse multi-agent model into a single @deuce agent#34
clintberry merged 6 commits into
mainfrom
feat/single-deuce-agent

Conversation

@clintberry

Copy link
Copy Markdown
Contributor

Collapses the multi-agent model (five role agents, per-session rosters, @mention-by-UUID routing, agent CRUD) into a single built-in agent, @Deuce, implicitly present in every session. Under the Pi harness the role distinction was already a UI fiction — role/system_prompt were fetched but never reached Pi — so this removes a model the execution layer ignored and clears the ground for skills/subagents on one agent.

Plan: docs/plans/2026-06-09-001-refactor-single-deuce-agent-plan.md (committed in this PR, status completed).

What changed

  • Legacy claude -p harness deleted — executor/queue/output, DEUCE_AGENT_HARNESS, and the agent_status/typing_indicator/agent_output WS events only it emitted. Pi is the sole harness.
  • Migration 013 (destructive by design): drops session_agents, de-keys tasks from agents (FK/column drop ordered before row deletes so task history survives the ON DELETE CASCADE), cancels still-queued tasks, repoints historical agent messages to deuce's fixed UUID (nil-UUID system sentinel excluded), reshapes agents to one seeded row (id + system_prompt), and drops messages.mentions. Down restores the pre-013 schema + the five role seed rows (verified Down/Up cycle on a data-bearing DB, including the NOT NULL backfill).
  • Runtime keys on session ID — one Pi process and one serial queue per session. Scheduler lookups use filtered queries against the new (session_id, state) index.
  • Server-side mention parsing(?i)(^|\W)@deuce\b (emails like clint@deuce.dev and @deucebot don't trigger); the client mentions plumbing is gone end to end. /stop is exact-match and now drains the queue along with the running task.
  • GET/PUT /api/agent replaces agent CRUD: deuce's global system prompt. Saving recycles idle Pi processes (synchronous deregister + async signal, so a racing enqueue launches fresh and the handler doesn't block); sessions mid-task pick the prompt up on their next process launch, and the editor says so.
  • Steer path gains the same workspace-status gate as chat mentions (shared helper, so the copy can't drift).
  • Frontend: DEUCE constant carries identity (a Go test pins the UUID across Go/SQL/TS); visibility filter hides agent-typed messages except nil-UUID system notices; Stop button moves to the running task card + drawer header; the drawer is the session's single thread; summary panel derives status from task state; settings dialog is a single prompt editor with global-scope/staleness/save-error states; vitest wired (npm test).

Authorization notes (route audit)

  • POST /sessions/{id}/agent/stop keeps requireSessionMember (gate before lookup; 403, no 404 oracle). WS steer stays Authorize-gated at join and steer.
  • GET/PUT /api/agent are authenticated-user gated by deliberate decision: no finer role model exists yet; the prompt is instructions, not secrets. An admin gate / audit trail for the global prompt is deferred (see plan Scope Boundaries) — flagged here because any team member can change agent behavior instance-wide.

Testing

  • Go: full suite green, including new tests for queue-drain cancel (R6), recycle-idle (busy/awaiting sessions survive, race-guard fresh launch), StopAndForget synchronous deregister, server-side mention regex, exact /stop, and the cross-layer UUID drift test.
  • Migration: fresh 001→013, staged multi-agent data transform assertions (tasks survive, queued→cancelled, repoint excludes nil sentinel), Down/Up cycle — all on scratch DBs.
  • Frontend: npm test (23 vitest specs), tsc -b, lint, production build.
  • Boot smoke against a fresh-migrated DB: clean recovery, GET/PUT /api/agent round-trip, deleted routes 404.

Deploy notes

Stop the server before migrating (a task enqueued mid-migration window would slip past the queued-task cancellation). Stale browser tabs hard-crash on the changed session shape — reload recovers; accepted pre-1.0. Running/awaiting tasks at deploy are failed by boot recovery; users re-mention @Deuce.

Post-Deploy Monitoring & Validation

  • Logs: watch for pi harness boot recovery failed (boot panic — should not appear) and runtime: error lines in the first hour.
  • Validate: send @deuce in an existing session → task card renders, reply lands in the drawer; /stop cancels running + queued; prompt edit via settings shows on the next task; clint@deuce.dev-style text does NOT trigger a run.
  • Failure signal / rollback: boot panic or no task on @Deucemake migrate-down (restores pre-013 schema + role seeds) and redeploy the previous image. Window: first day of dogfood use; owner: @clintberry.

Known Residuals / Deferred

Deferred follow-ups are recorded in the plan's Scope Boundaries: system author type to replace the nil-UUID sentinel, Pi session resume, prompt-edit audit trail/admin gate, unread badges incremented by hidden replies, multi-human answer collisions on a shared pending question.

🤖 Generated with Claude Code

clintberry and others added 6 commits June 10, 2026 00:20
Pi is the sole agent harness. Removes the executor/queue/output legacy
path, DEUCE_AGENT_HARNESS selection, the agent_status/typing_indicator/
agent_output WS events only it emitted, and the claude_session_id
queries. Also commits the single-deuce-agent plan doc.
Migration 013 drops session_agents, de-keys tasks from agents (FK/column
drop ordered before row deletes so task history survives), cancels
still-queued tasks, repoints historical agent messages to deuce's fixed
UUID (nil-UUID system sentinel excluded), reshapes agents to one seeded
row (id/name/system_prompt), and drops messages.mentions. Down restores
the pre-013 schema and the five role seed rows.

Runtime keys on session ID — one Pi process and one serial queue per
session. @Deuce mention detection moves server-side (word-boundary regex;
emails and @deucebot don't trigger); the mentions plumbing is gone end to
end. /stop is exact-match and drains the queue along with the running
task (R6). Agent CRUD is replaced by GET/PUT /api/agent for deuce's
global system prompt; saving recycles idle Pi processes (StopAndForget
deregisters synchronously so a racing enqueue launches fresh). The steer
path gains the same workspace-status gate as chat mentions (R8). WS
task/action payloads and the steer frame drop agentId; stale-tab fields
are tolerated and ignored.
Shared workspace-status gate for the chat-mention and steer paths;
scheduler lookups use filtered queries against the (session_id, state)
index from 013 instead of walking full task history; the prompt-edit
recycle deregisters synchronously but signals the dying process without
blocking the HTTP handler; the agents row drops the name column
(identity lives in the DeuceAgentName/DEUCE constants, with a drift
test pinning the UUID across Go, SQL, and TS); /api/agent's contract
shrinks to the system prompt.
The DEUCE constant (src/lib/deuce.ts, id matching the Go constant and
migration 013) carries the agent's identity; Session.agents, the Agent
type, mention parsing, and the legacy agent_status/typing_indicator/
agent_output handling are gone. The chat visibility filter pins agent-
typed messages to hidden unless nil-UUID system notices. Stop relocates
to the running task card and the thread-drawer header via a shared
atom. The drawer is the session's single deuce thread; the summary
panel and drawer derive the agent's status from task state. The
settings dialog becomes a single system-prompt editor disclosing global
scope, next-launch staleness, and save errors. The session-create agent
picker, role color tokens, and the unreferenced src/mocks prototype
data are removed. Wires vitest (npm test) for the pure-logic suites.
CLAUDE.md and README drop the role-agent framing, document the Pi-only
agent runtime, GET/PUT /api/agent, the AgentRunEvent WS family, and the
real frontend check commands (npm test, tsc -b). Marks the plan
completed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The status label sat on a second line under the name, pushing the name
off-center against the avatar. Name, status dot, and label now share
one row, vertically centered.
@clintberry clintberry merged commit c30282e into main Jun 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant