> ## Documentation Index
> Fetch the complete documentation index at: https://prism.ntecdev.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# SPEC-040 v0.2: Implement hd_exec — Handoff Pickup Verb (Symmetric to prism_wrap)

> Implement hd_exec — Handoff Pickup Verb (Symmetric to prism_wrap)

<Info>**Status:** `draft` · **Version** `0.2` · Filed 2026-04-25</Info>

# 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_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"                      |

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

```json theme={null}
{
  "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).
