Status:
draft · Version 0.2 · Filed 2026-04-25SPEC-040: Implement hd_exec — Handoff Pickup Verb (Symmetric to prism_wrap)
Version
0.2Status
draftOrigin
PRISM.md §3 (lines 88, 120-126) and CLAUDE.md documenthd_exec as the
session-pickup verb — symmetric counterpart to prism_wrap:
Agent A callsZero source files implement this. Six markdown files reference it. ADR-008 (filed 2026-04-17, statusprism_wrapwhen finishing. Agent B callshd_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, callhd_execinstead ofprism_start. The distinction matters for Ring 4 enforcement — handoff pickups inherit the previous agent’s accounting expectations.
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
- Identity resolution: same chain as
prism_start(identityarg →PRISM_AGENT_IDENTITYenv → elicitation). After SPEC-038 lands,--asabsent →Botdefault. - Active-session exclusion (SPEC-038 §2): if the resolved identity
already has an active controller registration on this PID, return
identity_conflicterror unlessforce=true. Bot exempt. - 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_activeerror unlessforce=true. Withforce=true, the prior session’s registration is released with reasonpreempted_by_pickup. - Baton retrieval: load the most recent
delta_kind='wrap'row for the project. Iffrom_sessionis supplied, filter to that session (lets the picker explicitly target a specific predecessor). If no wrap exists, returnbaton: nullwith a warning. - 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_execis NOT a lifecycle verb in SPEC-041’s sense, it’s a session-pickup verb that DOES drain).
- Controller registration: register the new session via the same path
as
prism_start. The new registration carries apicked_up_session_idmetadata field pointing atfrom_session(or the inferred predecessor). - 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. - 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_start | hd_exec | |
|---|---|---|
| Use case | Beginning fresh work, or after a clean wrap | Picking up another agent’s open / wrapped work |
| Returns baton? | No — only last_wrapped_at summary fields | Yes — 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 accounting | Inherits 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” |
prism_start after another agent’s wrap loses the baton (only the summary
remains); calling hd_exec preserves it.
§3 — Response Shape
§4 — Implementation
MCP server (mcp/server.py):
- Add
async def hd_exec(...)afterprism_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 likeprism_start/prism_checkpoint/prism_wrapper SPEC-041). - After registration succeeds, write the
delta_kind='pickup'delta.
backend/app/services/):
- Extend
load_project_context(or addload_handoff_context) to returnlast_wrap_deltafiltered todelta_kind='wrap'. Optionally acceptpredecessor_session_idto filter further. - Reuse
controller_service.register()with newmetadata={ picked_up_session_id, inherited_from }. - Add
predecessor_activecheck using existing heartbeat freshness logic (SPEC-030 stale-sweep cutoff).
backend/app/schemas/):
- New
HdExecResponsePydantic model. - Optional
from_session: UUID | Noneandforce: boolrequest fields.
- Remove
*(PRISM-NYI — SPEC-040)*markers from PRISM.md / CLAUDE.md / AGENTS.md / templates (added when SPEC-039 sweep tagged dangling refs).
§5 — Verification
- Agent A calls
prism_wrap. Agent B callshd_exec(pid)→ returnedbaton.bodymatches A’s wrap delta payload exactly. hd_execon a project with no prior wrap →baton: null,warnings: [{"kind": "no_baton", "message": "..."}].hd_execwithforce=falsewhile predecessor IDENTITY active (same identity claiming pickup) →identity_conflicterror (SPEC-038 §2).hd_execwithforce=falsewhile predecessor SESSION still active (different identity, heartbeat fresh) →predecessor_activeerror (Q2 decision).hd_exec --as Donna --forcewhile another Donna active → succeeds, prior Donna’s registration released with reasonpreempted_by_pickup.hd_execfrom a different identity (Donna picking up Lola’s wrap) → succeeds, predecessor and inheritance metadata set.- Pickup writes a
delta_kind='pickup'delta. Project’slast_wrapped_*fields are NOT updated (Q1 decision). - Ring 4 audit: a delta written by the picker references
inherited_fromcorrectly. - SPEC-039 audit no longer lists
hd_execunderstubs_in_source(decorator + marker removed by this spec). prism_auditreports zero remaining*(PRISM-NYI — SPEC-040)*markers in constitution markdown.
§6 — Decisions
- Q1 (
last_wrapped_*field update on pickup): FRESH.hd_execdoes NOT update the project’slast_wrapped_*fields. Those advance only on actualprism_wrapcalls. Pickup is opening a baton, not closing one. Wrap-discipline metrics stay honest. Optional follow-up (separate spec): introducelast_session_activity_aton projects to give dashboards a freshness signal independent of wrap state. - Q2 (block when predecessor still active): BLOCK BY DEFAULT.
hd_execrejects pickup withpredecessor_activeerror when the predecessor session has a fresh heartbeat.force=trueparameter overrides, releasing the prior session with reasonpreempted_by_pickup. SPEC-038 §2 active-session exclusion handles same-identity collision separately (alsoforce=true-overridable). - Q3 (pickup delta): SYMMETRIC.
hd_execwrites adelta_kind='pickup'delta on success. The corresponding fix toprism_start(writedelta_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_runpreview mode): DEFERRED. Existing tools (semantic_recall, delta listing) cover the “what would I be picking up” discovery path. Add a separatehd_previewverb if/when a real use case emerges (e.g., a UI showing available batons with hover-preview).

