docs(wiki): ingest pi-extension-headless-ritual concept (agent_end/mode-guard/loop-guard lessons)
This commit is contained in:
63
.wiki/concepts/pi-extension-headless-ritual.md
Normal file
63
.wiki/concepts/pi-extension-headless-ritual.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
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` (`.common/lib/pi-extensions/`),
|
||||||
|
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: `.common/lib/pi-extensions/session-close-ritual.ts` (+ `.test.mjs`, 12 blocks)
|
||||||
|
- Skill: `session-handoff` v0.5.0 — «Headless (pi)» section
|
||||||
|
- pi docs: `extensions.md` — lifecycle diagram, `sendUserMessage` (deliverAs/triggerTurn), mode table
|
||||||
@@ -58,3 +58,5 @@ Catalog of all wiki pages. One line per page, organized by type. Updated on ever
|
|||||||
## Sources
|
## Sources
|
||||||
|
|
||||||
<!-- (none yet) -->
|
<!-- (none yet) -->
|
||||||
|
- [pi-extension-headless-ritual.md](concepts/pi-extension-headless-ritual.md) — agent_end (not agent_settled) for followUp injection; mode guard (`print` not hasUI); loop-guard flag-before-send; opt-in mirrors skill
|
||||||
|
|
||||||
|
|||||||
@@ -78,3 +78,4 @@ Parseable: `grep "^## \[" .wiki/log.md | tail -20`.
|
|||||||
## [2026-06-09] decision | using-markitdown-cli-migration — `using-markitdown` 1.0.0→1.0.1 (PATCH): rewrote the skill from the Docker-based `mcp__markitdown__convert_to_markdown` MCP tool to the native `markitdown` CLI (v0.1.6, on PATH). Tool block now `markitdown <path|url>` → stdout (or `-o file`); removed the whole "Docker-mount caveat (READ FIRST)" section (host→container `file://` translation + `[Errno 2] /c:/Users/...` symptom are gone — CLI sees the full host FS). Updated the ingest pattern (use `-o` straight into `.wiki/raw/`), the gotchas table (`command not found` → check `markitdown --version`, install `pip install markitdown[all]`; dropped the MCP "tool not available / ToolSearch" row), and the contrast-table header (CLI, not MCP). Description frontmatter (the WHEN-to-use triggers) left unchanged. Container decommission: the task's literal `docker stop/rm markitdown-mcp` had no target — no container is named that; the MCP spawns anonymously-named containers from `markitdown-mcp:latest` per session (3 had piled up). Removed all by image ancestor (`docker rm -f $(docker ps -aq --filter "ancestor=markitdown-mcp:latest")`), verified none remain. Left the `mcpServers.markitdown` entry in `~/.claude.json` untouched (out of scope; a container will respawn next session until it's deregistered — flagged as a follow-up). Concept page concepts/using-markitdown-cli-migration.md + index. TDD N/A (markdown skill).
|
## [2026-06-09] decision | using-markitdown-cli-migration — `using-markitdown` 1.0.0→1.0.1 (PATCH): rewrote the skill from the Docker-based `mcp__markitdown__convert_to_markdown` MCP tool to the native `markitdown` CLI (v0.1.6, on PATH). Tool block now `markitdown <path|url>` → stdout (or `-o file`); removed the whole "Docker-mount caveat (READ FIRST)" section (host→container `file://` translation + `[Errno 2] /c:/Users/...` symptom are gone — CLI sees the full host FS). Updated the ingest pattern (use `-o` straight into `.wiki/raw/`), the gotchas table (`command not found` → check `markitdown --version`, install `pip install markitdown[all]`; dropped the MCP "tool not available / ToolSearch" row), and the contrast-table header (CLI, not MCP). Description frontmatter (the WHEN-to-use triggers) left unchanged. Container decommission: the task's literal `docker stop/rm markitdown-mcp` had no target — no container is named that; the MCP spawns anonymously-named containers from `markitdown-mcp:latest` per session (3 had piled up). Removed all by image ancestor (`docker rm -f $(docker ps -aq --filter "ancestor=markitdown-mcp:latest")`), verified none remain. Left the `mcpServers.markitdown` entry in `~/.claude.json` untouched (out of scope; a container will respawn next session until it's deregistered — flagged as a follow-up). Concept page concepts/using-markitdown-cli-migration.md + index. TDD N/A (markdown skill).
|
||||||
## [2026-06-17] decision | session-inbox-monitor-received-msg-fp — finding from `session-inbox-monitor-test-trigger` (VERDICT PASS, clean session, 7 unprimed clean-context subagents: pos 4/4 incl. CLAUDE.md-line P4, neg 2/3). The 1 FP: RU «обработай полученное письмо из инбокса» (N1) routed to `session-inbox-monitor`; the EN twin (N3) and the multi-machine-backend negative (N2) routed to `none` cleanly. Root cause = a new dimension on top of [[delegate-task-negative-trigger-fp]]: the carve-out is already literal+routed (`NOT for handling a received message → inter-session-peer-discipline`), but the route target `inter-session-peer-discipline` is **not installed** → no real competitor, so the nearest in-domain skill (session-inbox-monitor) wins by default; non-deterministic, self-corrects on body-load (cost = one wasted skill-load, not a wrong action; isomorphic to [[using-tasks-session-break]] session_break). New page concepts/session-inbox-monitor-received-msg-fp.md + bidirectional link from concepts/delegate-task-negative-trigger-fp.md + index. New reusable principle: a routed negative competes only if its route target is installed. Status OPEN — follow-up task session-inbox-monitor-received-msg-fp (options a: harden description / b: install sibling / c: accept informational). Not a memory entry by owner direction — knowledge belongs in the project wiki.
|
## [2026-06-17] decision | session-inbox-monitor-received-msg-fp — finding from `session-inbox-monitor-test-trigger` (VERDICT PASS, clean session, 7 unprimed clean-context subagents: pos 4/4 incl. CLAUDE.md-line P4, neg 2/3). The 1 FP: RU «обработай полученное письмо из инбокса» (N1) routed to `session-inbox-monitor`; the EN twin (N3) and the multi-machine-backend negative (N2) routed to `none` cleanly. Root cause = a new dimension on top of [[delegate-task-negative-trigger-fp]]: the carve-out is already literal+routed (`NOT for handling a received message → inter-session-peer-discipline`), but the route target `inter-session-peer-discipline` is **not installed** → no real competitor, so the nearest in-domain skill (session-inbox-monitor) wins by default; non-deterministic, self-corrects on body-load (cost = one wasted skill-load, not a wrong action; isomorphic to [[using-tasks-session-break]] session_break). New page concepts/session-inbox-monitor-received-msg-fp.md + bidirectional link from concepts/delegate-task-negative-trigger-fp.md + index. New reusable principle: a routed negative competes only if its route target is installed. Status OPEN — follow-up task session-inbox-monitor-received-msg-fp (options a: harden description / b: install sibling / c: accept informational). Not a memory entry by owner direction — knowledge belongs in the project wiki.
|
||||||
## [2026-06-17] decision | session-inbox-monitor-received-msg-fp RESOLVED via option (b) — installed `inter-session-peer-discipline` (existed in sources since 2026-06-16, was not installed → exact root cause confirmed). install.ps1 -Names, byte-identical parity. FP-twin verified clean: fresh clean-context subagent on the N1 phrase now routes to inter-session-peer-discipline (IN_REGISTRY: yes), not session-inbox-monitor — carve-out now has a real competitor. session-inbox-monitor description untouched (option (a) rejected as whack-a-mole; (c) as latent hole). Governance: peer workshop proposed (b) as a "ruling"; per the freshly-installed [[inter-session-peer-discipline]] (peer = proposal not authority, scope needs human ratification) it was surfaced as a recommendation and ratified by the user — live dogfood of the skill's own purpose. concepts/session-inbox-monitor-received-msg-fp.md Status section updated open→resolved. Tail: inter-session-peer-discipline now installed but not in hermes/mapping.yaml — possible red build, flagged as separate follow-up.
|
## [2026-06-17] decision | session-inbox-monitor-received-msg-fp RESOLVED via option (b) — installed `inter-session-peer-discipline` (existed in sources since 2026-06-16, was not installed → exact root cause confirmed). install.ps1 -Names, byte-identical parity. FP-twin verified clean: fresh clean-context subagent on the N1 phrase now routes to inter-session-peer-discipline (IN_REGISTRY: yes), not session-inbox-monitor — carve-out now has a real competitor. session-inbox-monitor description untouched (option (a) rejected as whack-a-mole; (c) as latent hole). Governance: peer workshop proposed (b) as a "ruling"; per the freshly-installed [[inter-session-peer-discipline]] (peer = proposal not authority, scope needs human ratification) it was surfaced as a recommendation and ratified by the user — live dogfood of the skill's own purpose. concepts/session-inbox-monitor-received-msg-fp.md Status section updated open→resolved. Tail: inter-session-peer-discipline now installed but not in hermes/mapping.yaml — possible red build, flagged as separate follow-up.
|
||||||
|
## [2026-08-12] ingest | pi-extension-headless-ritual — agent_end/mode-guard/loop-guard lessons from session-close-ritual build
|
||||||
|
|||||||
Reference in New Issue
Block a user