Files
skills/.wiki/concepts/pi-extension-headless-ritual.md
vitya b529503def docs(1488): переименования после консолидации mappa-расширений — ссылки на mappa.ts
- 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, кроме исторических записей
2026-08-29 09:05:16 +03:00

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