- skills/session-health/SKILL.md: поллер → extensions/mappa.ts (секция session-health, task:1486) - .wiki/concepts/pi-extension-headless-ritual.md: session-close-ritual → mappa.ts (исторически отдельный файл) - критерий 6 requirements:1: старые имена в docs/skills/wiki = 0, кроме исторических записей
68 lines
3.2 KiB
Markdown
68 lines
3.2 KiB
Markdown
---
|
|
title: pi-extension headless ritual — lifecycle + mode lessons
|
|
type: concept
|
|
created: 2026-08-12
|
|
---
|
|
|
|
# pi-extension headless ritual (agent_end, mode guard, loop-guard)
|
|
|
|
Durable lessons from building `session-close-ritual` (консолидирован в
|
|
`extensions/mappa.ts` репо `OpeItcLoc03/pi-extensions`, task:1486; исторически —
|
|
отдельный файл `session-close-ritual.ts`),
|
|
the headless injector for the session-handoff closing ritual. All three points
|
|
were live-verified, not docs-read-only.
|
|
|
|
## 1. `agent_settled` is TOO LATE for followUp injection
|
|
|
|
`agent_settled` fires when pi "will not continue running automatically" — the
|
|
process is tearing down (no retry/compaction/follow-up left). A `sendUserMessage`
|
|
with `deliverAs: "followUp"` queued there is never processed; the run ends, and
|
|
the extension handler even hits a stale-ctx error during teardown.
|
|
|
|
**Use `agent_end`** — it fires right after the agent run ends, while queued
|
|
follow-ups are still delivered (`followUp` waits for the agent to finish, then
|
|
delivers; `triggerTurn: true` starts a new turn when idle). Verified against
|
|
`agent-session.js:779-780` ("agent loop drains both queues before emitting
|
|
agent_end") + live runs.
|
|
|
|
## 2. `ctx.hasUI === false` is NOT headless-only — guard by `mode`
|
|
|
|
`hasUI` is `false` in BOTH `-p` (print) and `--mode json`. An unsolicited
|
|
injected user-message into an event-stream consumer (JSON mode) is a protocol
|
|
surprise. RPC mode has `hasUI === true` (so a hasUI-guard accidentally allows
|
|
rpc while missing json).
|
|
|
|
**Guard: `ctx.mode === "print"`** for pi -p / scripted runs. Excludes
|
|
tui/json/rpc in one condition.
|
|
|
|
## 3. Loop-guard pattern: flag-before-send, per-session-per-cwd
|
|
|
|
`agent_end` fires again after the injected ritual turn (the agent made tool
|
|
calls, then the run ends) — without a guard: agent_end → ritual → agent_end →
|
|
ritual → … loop.
|
|
|
|
- Set the flag **synchronously BEFORE** `sendUserMessage` (no `await` between
|
|
check and set → no race; `emit()` is serial).
|
|
- Per-session-per-cwd `Map`, reset on `session_start`.
|
|
- On send-failure: keep the flag (at-most-once wins over retry — a missed
|
|
ritual is cheaper than double-inject). This is a deliberate asymmetry vs
|
|
`inbox-monitor` (which unmarks and retries).
|
|
- `injectRitual`'s send is wrapped in try/catch: the real `sendUserMessage` is
|
|
a sync wrapper (`assertActive()` throws on shutdown race).
|
|
|
|
## 4. Opt-in mirrors the skill, not the extension
|
|
|
|
The extension checks the same opt-in as the skill it serves: the project
|
|
`CLAUDE.md` contains the skill's trigger line (`session handoff: read on start,
|
|
write on end`) AND `.tasks/` exists AND `.git` exists. No opt-in → silent.
|
|
Cache per-cwd; staleness within a long session is accepted (same as
|
|
`inbox-monitor`).
|
|
|
|
## References
|
|
|
|
- Source: `~/projects/pi-extensions/extensions/mappa.ts` (секция close-ritual;
|
|
консолидация 6 расширений, task:1486 — исторически `session-close-ritual.ts`
|
|
+ `scripts/session-close-ritual.test.mjs`, 12 blocks, ныне тесты на mappa.ts)
|
|
- Skill: `session-handoff` v0.5.0 — «Headless (pi)» section
|
|
- pi docs: `extensions.md` — lifecycle diagram, `sendUserMessage` (deliverAs/triggerTurn), mode table
|