Skip to main content
Status: draft · Version 0.2 · Filed 2026-04-25

SPEC-040: Implement hd_exec — Handoff Pickup Verb (Symmetric to prism_wrap)

Version

0.2

Status

draft

Origin

PRISM.md §3 (lines 88, 120-126) and CLAUDE.md document hd_exec as the session-pickup verb — symmetric counterpart to prism_wrap:
Agent A calls prism_wrap when finishing. Agent B calls hd_exec(pid) to retrieve the last handoff, recent deltas, focus-tagged TODOs, recent ADRs, and current-phase context. Rule: when continuing another agent’s work, call hd_exec instead of prism_start. The distinction matters for Ring 4 enforcement — handoff pickups inherit the previous agent’s accounting expectations.
Zero source files implement this. Six markdown files reference it. ADR-008 (filed 2026-04-17, status accepted) made the decision to add the verb; this spec is the implementation. SPEC-039 (NYI runtime contract) ships first to formally tag this as vapor; this spec ships the implementation that retires the marker.

§1 — Verb Contract

hd_exec(
  pid: str,
  identity: str = "",
  from_session: str = "",
  force: bool = false,
) -> HdExecResponse
Behavior:
  1. Identity resolution: same chain as prism_start (identity arg → PRISM_AGENT_IDENTITY env → elicitation). After SPEC-038 lands, --as absent → Bot default.
  2. Active-session exclusion (SPEC-038 §2): if the resolved identity already has an active controller registration on this PID, return identity_conflict error unless force=true. Bot exempt.
  3. Predecessor-active block (Q2 decision): if the predecessor session (the one whose wrap this pickup targets) is still active (heartbeat fresh, not wrapped), return predecessor_active error unless force=true. With force=true, the prior session’s registration is released with reason preempted_by_pickup.
  4. Baton retrieval: load the most recent delta_kind='wrap' row for the project. If from_session is supplied, filter to that session (lets the picker explicitly target a specific predecessor). If no wrap exists, return baton: null with a warning.
  5. Context bundle: load
    • recent deltas (last 10 OR last 24h, whichever’s smaller)
    • focus-tagged open TODOs
    • recent ADRs (last 5)
    • current-phase context (same as prism_start)
    • open WIP states
    • pending signals addressed to the resolved identity (drained per SPEC-037 piggyback rules; not bypassed — hd_exec is NOT a lifecycle verb in SPEC-041’s sense, it’s a session-pickup verb that DOES drain).
  6. Controller registration: register the new session via the same path as prism_start. The new registration carries a picked_up_session_id metadata field pointing at from_session (or the inferred predecessor).
  7. Pickup delta (Q3 decision): write a delta_kind='pickup' delta with body {predecessor_session_id, inherited_from_wrap_delta_id, picker_identity, picked_up_at}. Companion to SPEC-043’s start delta.
  8. Ring 4 inheritance: the new session’s accounting metadata is marked inherited_from = <predecessor_session_id> so audit trails trace continuity.

§2 — Distinction from prism_start

prism_starthd_exec
Use caseBeginning fresh work, or after a clean wrapPicking up another agent’s open / wrapped work
Returns baton?No — only last_wrapped_at summary fieldsYes — full wrap delta payload
last_wrapped_* advance?No (project-level fields advance only on actual prism_wrap)No — pickup is opening a baton, not closing one (Q1 decision)
Identity inheritance?Self-declared, fresh accountingInherits Ring 4 accounting context from predecessor
Signal drain?Skipped (SPEC-041, lifecycle verb)Active (SPEC-037 piggyback — non-lifecycle session-pickup verb)
Lifecycle delta written?delta_kind='start' (SPEC-043)delta_kind='pickup' (this spec)
Constitution intent”I’m starting""I’m continuing what someone else started”
The agent’s choice between the two is a deliberate signal. Calling prism_start after another agent’s wrap loses the baton (only the summary remains); calling hd_exec preserves it.

§3 — Response Shape

{
  "session_id": "<new uuid>",
  "predecessor_session_id": "<from prior wrap>",
  "baton": {
    "delta_id": "<uuid>",
    "kind": "wrap",
    "agent_identity": "<predecessor identity>",
    "created_at": "<iso8601>",
    "body": "<the wrap delta payload>",
    "summary": "<one-line summary if computed>"
  },
  "pickup_delta_id": "<uuid of the delta_kind='pickup' just written>",
  "context": { /* same shape as prism_start */ },
  "recent_deltas": [...],
  "todos": {...},
  "adrs": [...],
  "wip": [...],
  "pending_signals": [...],
  "controller_status": { /* same shape as prism_start */ },
  "instruction": "Display status card; baton is in result.baton — incorporate it before deciding next step.",
  "rules_reminders": [...]
}

§4 — Implementation

MCP server (mcp/server.py):
  • Add async def hd_exec(...) after prism_handoff (around line 2313).
  • Reuse prism_start’s identity resolution and registration code paths (extract the shared logic into a helper if duplication grows).
  • Wrap with require_bootstrap (so it gets the SPEC-037 piggyback drain; it’s NOT lifecycle-exempt like prism_start / prism_checkpoint / prism_wrap per SPEC-041).
  • After registration succeeds, write the delta_kind='pickup' delta.
Backend (backend/app/services/):
  • Extend load_project_context (or add load_handoff_context) to return last_wrap_delta filtered to delta_kind='wrap'. Optionally accept predecessor_session_id to filter further.
  • Reuse controller_service.register() with new metadata={ picked_up_session_id, inherited_from }.
  • Add predecessor_active check using existing heartbeat freshness logic (SPEC-030 stale-sweep cutoff).
Schema (backend/app/schemas/):
  • New HdExecResponse Pydantic model.
  • Optional from_session: UUID | None and force: bool request fields.
Constitution updates (after impl ships):
  • Remove *(PRISM-NYI — SPEC-040)* markers from PRISM.md / CLAUDE.md / AGENTS.md / templates (added when SPEC-039 sweep tagged dangling refs).

§5 — Verification

  1. Agent A calls prism_wrap. Agent B calls hd_exec(pid) → returned baton.body matches A’s wrap delta payload exactly.
  2. hd_exec on a project with no prior wrap → baton: null, warnings: [{"kind": "no_baton", "message": "..."}].
  3. hd_exec with force=false while predecessor IDENTITY active (same identity claiming pickup) → identity_conflict error (SPEC-038 §2).
  4. hd_exec with force=false while predecessor SESSION still active (different identity, heartbeat fresh) → predecessor_active error (Q2 decision).
  5. hd_exec --as Donna --force while another Donna active → succeeds, prior Donna’s registration released with reason preempted_by_pickup.
  6. hd_exec from a different identity (Donna picking up Lola’s wrap) → succeeds, predecessor and inheritance metadata set.
  7. Pickup writes a delta_kind='pickup' delta. Project’s last_wrapped_* fields are NOT updated (Q1 decision).
  8. Ring 4 audit: a delta written by the picker references inherited_from correctly.
  9. SPEC-039 audit no longer lists hd_exec under stubs_in_source (decorator + marker removed by this spec).
  10. prism_audit reports zero remaining *(PRISM-NYI — SPEC-040)* markers in constitution markdown.

§6 — Decisions

  • Q1 (last_wrapped_* field update on pickup): FRESH. hd_exec does NOT update the project’s last_wrapped_* fields. Those advance only on actual prism_wrap calls. Pickup is opening a baton, not closing one. Wrap-discipline metrics stay honest. Optional follow-up (separate spec): introduce last_session_activity_at on projects to give dashboards a freshness signal independent of wrap state.
  • Q2 (block when predecessor still active): BLOCK BY DEFAULT. hd_exec rejects pickup with predecessor_active error when the predecessor session has a fresh heartbeat. force=true parameter overrides, releasing the prior session with reason preempted_by_pickup. SPEC-038 §2 active-session exclusion handles same-identity collision separately (also force=true-overridable).
  • Q3 (pickup delta): SYMMETRIC. hd_exec writes a delta_kind='pickup' delta on success. The corresponding fix to prism_start (write delta_kind='start' delta on registration) is moved to SPEC-043 (Session-Start Delta Symmetry) — out of SPEC-040’s scope but tracked.
  • Q4 (hd_exec_dry_run preview mode): DEFERRED. Existing tools (semantic_recall, delta listing) cover the “what would I be picking up” discovery path. Add a separate hd_preview verb if/when a real use case emerges (e.g., a UI showing available batons with hover-preview).
Last modified on April 27, 2026