Why this issue
#750 is blocked on two decisions: where the integration lives (jcode / Herdr / both) and what the stable lifecycle contract is. The Herdr-side half already exists — herdrdev/herdr#2248 adds jcode as an official integration target. This issue records the jcode-side half of the contract as it has been implemented and verified end to end against Herdr 0.8.2, so the decision in #750 has a concrete, tested baseline to accept, amend, or reject.
Reference implementation + full contract doc: branch feat/herdr-lifecycle-integration in my fork, opened as draft PR PR #TBD-pending (crates/jcode-base/src/herdr.rs, docs/HERDR.md). ~1575 lines across 8 files; the emitter is one new module, the rest is small wiring. I am not asking for it to be merged as-is — per CONTRIBUTING I expect it to serve as reference. What I am asking to freeze is the contract below.
The contract
Trigger and scope
When a client's request-scoped environment carries HERDR_ENV=1 + HERDR_SOCKET_PATH + HERDR_PANE_ID, jcode reports lifecycle natively to that pane's Herdr socket (NDJSON, the pi-integration protocol). No user-side hook configuration. JCODE_HERDR_REPORT=0 opts out. On the shared daemon, identity comes from each client's request scope (the same task-local terminal env that scopes shell hooks, #758), never from the daemon process env — one daemon serving many panes does not cross-report.
Events jcode emits
| method |
when |
key params |
pane.report_agent_session |
session create/attach/resume |
source: "herdr:jcode", agent: "jcode", monotonic seq, agent_session_id, session_start_source (startup/resume) |
pane.report_agent |
turn start → working; settle → idle; unresolved permission request → blocked; permission resolved/expired → clears blocked |
same source/agent, seq continues the ramp |
pane.release_agent |
session_end |
releases the pane's agent slot |
Semantics Herdr already accepts (verified against 0.8.2 by direct socket experiments)
- Reports from any
source drive pane label, state, rollups, and waits.
pane.release_agent and stale-seq ordering behave per docs: a release with seq below the last accepted state report is ignored — hence the monotonic per-source ramp.
agent_session_id is persisted (and later used for restore) only for official sources. herdr:jcode is accepted but the reference is dropped; custom:* stores nothing. This is the single remaining gap and it is exactly what herdr#2248 proposes to close by accepting ("herdr:jcode", "jcode").
Coexistence rule (important for the "where does it live" decision)
If a session_start hook adapter (as proposed in herdr#2248) is installed, it reports under the same herdr:jcode source; two reporters on one source drop each other's updates via the seq ramp. The emitter therefore detects an installed adapter and stands down (hook_adapter_installed). Installing or not installing the adapter is always safe; the emitter and the adapter are mutually exclusive by design, not by configuration discipline.
Boundary with shell hooks
The emitter rides the existing lifecycle observer funnel (dispatch_observer), so turn_start/turn_end/session_start/session_end semantics are shared with shell hooks rather than reinvented. hook_configured() returns true for the four lifecycle events while a Herdr client is watching (so payload construction happens), but tool-hot paths (pre_tool/post_tool) are not consumed and pay nothing. Permission blocked pins come from a new observer on the existing permission queue; it is a non-blocking hot-path observer, no-op when unattached.
What I am asking for
- A yes/no on this event shape + seq + coexistence contract as the thing both herdr#2248 and any jcode-side implementation must satisfy.
- If yes, whether the jcode half should be my emitter, Jeremy's own version, or folded into the adapter design — I am indifferent whose code lands as long as the contract is the one above.
Validation performed
End to end against Herdr 0.8.2 on Linux: panes running a jcode TUI appear in herdr agent list as jcode with live working/idle/blocked rollups, blocked clears on permission resolution, release on close; daemon process-env isolation is pinned by a regression test in the branch.
Why this issue
#750 is blocked on two decisions: where the integration lives (jcode / Herdr / both) and what the stable lifecycle contract is. The Herdr-side half already exists — herdrdev/herdr#2248 adds
jcodeas an official integration target. This issue records the jcode-side half of the contract as it has been implemented and verified end to end against Herdr 0.8.2, so the decision in #750 has a concrete, tested baseline to accept, amend, or reject.Reference implementation + full contract doc: branch
feat/herdr-lifecycle-integrationin my fork, opened as draft PR PR #TBD-pending (crates/jcode-base/src/herdr.rs,docs/HERDR.md). ~1575 lines across 8 files; the emitter is one new module, the rest is small wiring. I am not asking for it to be merged as-is — per CONTRIBUTING I expect it to serve as reference. What I am asking to freeze is the contract below.The contract
Trigger and scope
When a client's request-scoped environment carries
HERDR_ENV=1+HERDR_SOCKET_PATH+HERDR_PANE_ID, jcode reports lifecycle natively to that pane's Herdr socket (NDJSON, the pi-integration protocol). No user-side hook configuration.JCODE_HERDR_REPORT=0opts out. On the shared daemon, identity comes from each client's request scope (the same task-local terminal env that scopes shell hooks, #758), never from the daemon process env — one daemon serving many panes does not cross-report.Events jcode emits
pane.report_agent_sessionsource: "herdr:jcode",agent: "jcode", monotonicseq,agent_session_id,session_start_source(startup/resume)pane.report_agentworking; settle →idle; unresolved permission request →blocked; permission resolved/expired → clearsblockedsource/agent,seqcontinues the ramppane.release_agentsession_endSemantics Herdr already accepts (verified against 0.8.2 by direct socket experiments)
sourcedrive pane label, state, rollups, and waits.pane.release_agentand stale-seqordering behave per docs: a release withseqbelow the last accepted state report is ignored — hence the monotonic per-source ramp.agent_session_idis persisted (and later used for restore) only for official sources.herdr:jcodeis accepted but the reference is dropped;custom:*stores nothing. This is the single remaining gap and it is exactly what herdr#2248 proposes to close by accepting("herdr:jcode", "jcode").Coexistence rule (important for the "where does it live" decision)
If a
session_starthook adapter (as proposed in herdr#2248) is installed, it reports under the sameherdr:jcodesource; two reporters on one source drop each other's updates via the seq ramp. The emitter therefore detects an installed adapter and stands down (hook_adapter_installed). Installing or not installing the adapter is always safe; the emitter and the adapter are mutually exclusive by design, not by configuration discipline.Boundary with shell hooks
The emitter rides the existing lifecycle observer funnel (
dispatch_observer), soturn_start/turn_end/session_start/session_endsemantics are shared with shell hooks rather than reinvented.hook_configured()returns true for the four lifecycle events while a Herdr client is watching (so payload construction happens), but tool-hot paths (pre_tool/post_tool) are not consumed and pay nothing. Permissionblockedpins come from a new observer on the existing permission queue; it is a non-blocking hot-path observer, no-op when unattached.What I am asking for
Validation performed
End to end against Herdr 0.8.2 on Linux: panes running a jcode TUI appear in
herdr agent listasjcodewith liveworking/idle/blockedrollups,blockedclears on permission resolution, release on close; daemon process-env isolation is pinned by a regression test in the branch.