Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
a8d0993
audit: robust vulnerability detection via native fetch, bypass broken…
ofriw Jul 26, 2026
5eacdaf
handoff+notebook: sequential epoch counter and transactional discardP…
ofriw Jul 26, 2026
b62d88f
spawn: remove sessionFactory mock seam, pure functions + real invocat…
ofriw Jul 26, 2026
8c8f77a
prompt: teach agents about discardPages and spawn output truncation
ofriw Jul 26, 2026
9dcc8e4
Rename handoff brief to handoff prompt in documentation
ofriw Jul 26, 2026
b312f68
Rename brief to prompt in source code comments and strings
ofriw Jul 26, 2026
dd6b359
Simplify handoff tool description, remove internal implementation detail
ofriw Jul 26, 2026
c406c1c
Update tests: rename brief to prompt, add coverage for simplified too…
ofriw Jul 26, 2026
2351244
Merge branch 'main' into notebook-deletion
ofriw Jul 26, 2026
1b470ef
fix: restore sessionFactory parameter lost in auto-merge, fix brace-e…
ofriw Jul 26, 2026
f4804dc
Rename grounding to memory in docs
ofriw Jul 27, 2026
114d415
Rename grounding to memory and formalize notebook as shared-memory ch…
ofriw Jul 27, 2026
387cd32
Update tests for memory terminology and shared-memory contract
ofriw Jul 27, 2026
bc44c78
Harden spawn-after-handoff tests with sandbox isolation
ofriw Jul 27, 2026
b6dba05
Guard npm_execpath against invalid values with shared validator
ofriw Jul 28, 2026
c200a5f
Sync brace-expansion expected version with lockfile
ofriw Jul 28, 2026
53bde26
Reframe context management from job-based to topic-based
ofriw Jul 28, 2026
0bea801
Add notebook completeness check before handoff
ofriw Jul 28, 2026
93b951e
fix: pass loader as file URL to --import for Windows compat
ofriw Jul 28, 2026
0ee912a
docs(index.ts): fix grounding→memory in JSDoc to complete terminology…
ofriw Jul 29, 2026
ed2e95f
Remove stale audit exceptions
ofriw Jul 29, 2026
51135b9
Make headless spawn cleanup failures observable via AggregateError
ofriw Jul 29, 2026
9511957
Clarify discardPages semantics: obsolete vs merely irrelevant pages
ofriw Jul 30, 2026
9e77e9a
Refine handoff scan guidance to open critical pages with notebook_read
ofriw Jul 30, 2026
2baffad
Refactor npm_execpath validation with resolveNpmExecpath helper
ofriw Jul 30, 2026
4c0a0ea
Replace npm ls brace-expansion check with lockfile-based parsing
ofriw Jul 30, 2026
b10391e
Harden spawn error preservation with notification failure wrapping in…
ofriw Jul 30, 2026
eb1e419
Add createAgentSession callability test after handoff-first import se…
ofriw Jul 30, 2026
c5aab29
Move temp dir from os.tmpdir() to process.cwd() for cross-drive Windo…
ofriw Jul 30, 2026
bcb0bf7
Guard discard retries with an epoch high-water mark
ofriw Aug 4, 2026
36cbaa2
Rehydrate notebook state branch-scoped on session_tree
ofriw Aug 4, 2026
699de3b
Report handoff completion truthfully after the discard commit
ofriw Aug 4, 2026
f8337ff
Clarify notebook as two-tier cache with explicit pruning guidance
ofriw Aug 4, 2026
7e6c703
Add fresh-process session test for handoff-first import order
ofriw Aug 4, 2026
cebaafb
Extend audit-ci allowlist with undici and brace-expansion advisory en…
ofriw Aug 4, 2026
9e5df13
Add undici lockfile guard to config-invariant tests
ofriw Aug 4, 2026
8e509ac
Add version discriminator to notebook rehydration, ignoring future-fo…
ofriw Aug 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Deeper rationale: [docs/why.md](docs/why.md) · companion book: [agenticoding.ai
- **Spawn** — run research or implementation in a clean child context so the parent stays focused
- **Model Groups** — manage durable project/global model pools with `/model-groups`; route `spawn` by an exact group name, with `#group` autocomplete showing model/thinking details
- **Notebook** — task-scoped named pages for facts and decisions; survives handoff, dies with the conversation (`/new`) — no forever-memory rot
- **Handoff** — deliberate clean restart with a task brief when the job changes or context turns to noise
- **Handoff** — deliberate clean restart with a task prompt when the topic changes or context turns to noise
- **Topic** — same problem → prefer spawn; new problem → prefer handoff (human-set topics win)
- **Readonly** — explore and plan without writing the tree (`/readonly`, Ctrl+Shift+R, or `--readonly`); macOS/Linux can OS-sandbox bash, Windows is classifier-only
- **Visibility** — status bar shows context pressure, notebook count, topic, and readonly; warning at high usage
Expand Down Expand Up @@ -77,8 +77,8 @@ The agent set a topic, spawned research, saved decisions, delegated implementati
| | |
|---|---|
| **Spawn** | Subtask in a clean child context. Parent orchestrates; siblings run in parallel. Children inherit active registered parent tools executable in the child session — MCP/extension tools such as ChunkHound — plus child-local notebook tools. Children cannot spawn grandchildren or handoff. Omit `group` to inherit the parent model/thinking. An unknown group reports fallback to the parent. A known group randomly selects among configured/authenticated usable entries and fails before child creation if none are usable. The selected entry supplies the model and, when configured, overrides explicit/inherited thinking before Pi clamps it; the final selected public model runs in the child-owned runtime. |
| **Notebook** | Named pages coupled to this conversation/task. Carries grounding across handoff; cleared on `/new`. Not a long-lived memory store — lifetime matches the work, so it cannot go stale across unrelated sessions. |
| **Handoff** | Write a brief, compact, resume clean. Notebook holds reusable grounding for this task; the brief holds only remaining situational context. |
| **Notebook** | Named pages coupled to this conversation/task. Carries memory across handoff; cleared on `/new`. Not a long-lived memory store — lifetime matches the work, so it cannot go stale across unrelated sessions. |
| **Handoff** | Write a prompt, compact, resume clean. Notebook holds reusable memory for this task; the prompt holds only remaining situational context. |
| **Readonly** | Blocks write/edit and guards bash while researching. Spawn inherits the posture. **macOS/Linux:** bash can run under OS sandbox (`sandbox-exec` / `bwrap`) — syscall-level write denial outside temp. **Windows:** no OS sandbox — **best-effort command classifier only** (interpreters and clever pipes can bypass). A coding guardrail on every OS — not a hardened security boundary. |

**Commands:** `/handoff` · `/notebook` · `/notebook <topic>` · `/readonly` · `Ctrl+Shift+R` · `--readonly`
Expand All @@ -90,7 +90,7 @@ The agent set a topic, spawned research, saved decisions, delegated implementati
| Platform auto-compaction | Runtime (late threshold) | Blunt lossy summary |
| `/compact` or `/clear` | User (timing + steer) | Lossy summarizer pass / paste |
| Forever “memory” stores | Background / RAG | Accumulates, goes stale, needs invalidation |
| **pi-agenticoding** | **Agent** | **Task-scoped notebook + handoff brief** |
| **pi-agenticoding** | **Agent** | **Task-scoped notebook + handoff prompt** |

## Learn more

Expand Down
19 changes: 15 additions & 4 deletions audit-ci.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@
"$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json",
"moderate": true,
"allowlist": [
// npm's audit report exposes this shrinkwrapped advisory as the module path
// `brace-expansion`; a config invariant separately rejects any vulnerable
// occurrence outside Pi coding agent's exact 0.82.0 shrinkwrapped path.
{ "GHSA-mh99-v99m-4gvg|brace-expansion": { "active": true, "expiry": "2026-09-01", "notes": "brace-expansion 5.0.7 only in @earendil-works/pi-coding-agent's shrinkwrap via minimatch; consumer overrides cannot replace it, so remove when Pi publishes brace-expansion 5.0.8+" } }
// brace-expansion 5.0.7 is pinned by @earendil-works/pi-coding-agent's
// published npm-shrinkwrap.json (the host under test); registry fix 5.0.9
// exists but cannot be installed without deviating from the host tree —
// remove when a pi-coding-agent release ships brace-expansion ≥5.0.9.
{ "GHSA-mh99-v99m-4gvg": { "active": true, "expiry": "2026-10-01", "notes": "brace-expansion <5.0.8 DoS via unbounded expansion; 5.0.7 pinned by @earendil-works/pi-coding-agent's npm-shrinkwrap (host under test) — remove when pi-coding-agent ships brace-expansion ≥5.0.8" } },
{ "GHSA-rgw5-rvv9-x895": { "active": true, "expiry": "2026-10-01", "notes": "brace-expansion <5.0.9 DoS bypassing the CVE-2026-14257 mitigation; 5.0.7 pinned by @earendil-works/pi-coding-agent's npm-shrinkwrap — remove when pi-coding-agent ships brace-expansion ≥5.0.9" } },
// undici 8.5.0 is pinned exactly by @earendil-works/pi-coding-agent (the host
// under test); no released pi-coding-agent ships undici ≥8.9.0 (upstream
// issue #7049, PR #7225 closed unmerged) — remove when a pi-coding-agent
// release ships undici ≥8.9.0.
{ "GHSA-4cwx-7wf7-3272|@earendil-works/pi-coding-agent>undici": { "active": true, "expiry": "2026-10-01", "notes": "undici <8.9.0 high: cross-user cache disclosure / parse-time crash; pinned exact 8.5.0 by @earendil-works/pi-coding-agent 0.82.0 (host under test); no upstream fix released — remove when pi-coding-agent ships undici ≥8.9.0" } },
{ "GHSA-8xcm-r25x-g524|@earendil-works/pi-coding-agent>undici": { "active": true, "expiry": "2026-10-01", "notes": "undici <8.9.0: retry-interceptor response desynchronization; pinned exact 8.5.0 by @earendil-works/pi-coding-agent 0.82.0 — remove when pi-coding-agent ships undici ≥8.9.0" } },
{ "GHSA-jr45-8vmc-qm54|@earendil-works/pi-coding-agent>undici": { "active": true, "expiry": "2026-10-01", "notes": "undici <8.9.0: Cache-Control whitespace bypass; pinned exact 8.5.0 by @earendil-works/pi-coding-agent 0.82.0 — remove when pi-coding-agent ships undici ≥8.9.0" } },
{ "GHSA-m8rv-5g2x-5cg5|@earendil-works/pi-coding-agent>undici": { "active": true, "expiry": "2026-10-01", "notes": "undici <8.9.0: CRLF injection via blob-like body type; pinned exact 8.5.0 by @earendil-works/pi-coding-agent 0.82.0 — remove when pi-coding-agent ships undici ≥8.9.0" } },
{ "GHSA-v3r7-h72x-cjcm|@earendil-works/pi-coding-agent>undici": { "active": true, "expiry": "2026-10-01", "notes": "undici <8.9.0: cookie attribute injection; pinned exact 8.5.0 by @earendil-works/pi-coding-agent 0.82.0 — remove when pi-coding-agent ships undici ≥8.9.0" } }
]
}
10 changes: 6 additions & 4 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ pi-agenticoding is a Pi extension. It registers tools and hooks into the agent l
| `context` | Advisory watchdog reminders when context is elevated; readonly toggle nudges |
| `input` | Queues skill/prompt names for deferred readonly frontmatter resolution |
| `tool_call` | Readonly blocks write/edit/unguarded bash; blocks handoff unless a requested bypass is active |
| `session_start` | Rehydrates notebook pages and readonly state; loads and validates Model Groups, registers group autocomplete, reports config issues, and resets session state on `/new` |
| `session_start` | Reconstructs notebook pages/epoch/watermark from the active branch and rehydrates readonly state; loads and validates Model Groups, registers group autocomplete, reports config issues, and resets session state on `/new` |
| `session_tree` | Invalidates branch-local handoff work, reconstructs notebook pages/epoch/watermark from the newly active branch, rehydrates readonly state, refreshes indicators |
| `turn_end` | Updates TUI indicators (context %, notebook count, topic, readonly) |
| `agent_end` | Records last context usage percent; handoff enforcement cleanup |
| `session_before_compact` | Consumes the pending handoff task and sets it as the compaction summary |
Expand All @@ -29,6 +30,7 @@ interface AgenticodingState {
validation: ModelGroupsBootValidation | null
}
epoch: number
discardEpochWatermark: number
lastContextPercent: number | null
pendingHandoff: { task, source } | null
pendingRequestedHandoff: { direction, resumeReadonlyAfterHandoff, ... } | null
Expand All @@ -44,9 +46,9 @@ interface AgenticodingState {

**Model Groups** — `/model-groups` manages versioned global and trusted-project JSON configuration. Project groups shadow same-named global groups. Configuration is loaded and validated against Pi's model registry into the `modelGroups` snapshot; only names are injected into the agent prompt. Routing uses the parent registry only to select configured/authenticated entries—the registry/auth objects are not passed into the child runtime.

**Notebook** — Agent-curated named pages **scoped to the current conversation/task**, not a long-lived memory product. Stored as session custom entries so pages survive handoff and resume of the same work stream; `/new` (fresh session) clears them with the conversation. That coupling avoids the stale-entry / invalidation problem of forever-memory systems. Active topic (`notebook_topic_set` or `/notebook <topic>`) frames spawn-vs-handoff preference; human-set topics are authoritative. Topic clears after a successful handoff.
**Notebook** — Agent-curated named pages **scoped to the active session branch**, not a long-lived memory product. Stored as session custom entries so pages survive handoff and resume of the same work stream; `/new` (fresh session) clears them with the conversation. The visible pages and the committed generation epoch follow the branch the user navigated to: `/tree` reconstruction rehydrates from the newly active branch, so branches diverge without cross-contamination and returning to an earlier branch restores its state; writes land on the current branch's generation. Discard is transactional — survivors are staged at the next epoch and committed only on successful handoff compaction — and the epoch high-water mark is derived from the branch during reconstruction, so a failed attempt can never resurrect staged pages after a restart. Active topic (`notebook_topic_set` or `/notebook <topic>`) frames spawn-vs-handoff preference; human-set topics are authoritative. Topic clears after a successful handoff.

**Handoff** — Requires a real brief and a meaningful context load (rejects empty briefs, very small sessions, or missing usage). Notebook bodies are not inlined into the brief; the next context in this work stream fetches pages by name. Under readonly, handoff is blocked unless the user runs `/handoff` or crosses an eligible human topic boundary; readonly can resume after compaction. Compaction replaces the prior transcript with the brief: the next turns see a small context again (quality), and providers start a new input prefix for billing/cache (the dropped history is no longer in that prefix). Spawn runs children in separate context so their token use does not permanently inflate the parent. This extension does not configure provider cache TTLs or breakpoints.
**Handoff** — Requires a real prompt and a meaningful context load (rejects empty prompts, very small sessions, or missing usage). Notebook bodies are not inlined into the prompt; the next context in this work stream fetches pages by name. Under readonly, handoff is blocked unless the user runs `/handoff` or crosses an eligible human topic boundary; readonly can resume after compaction. Compaction replaces the prior transcript with the prompt: the next turns see a small context again (quality), and providers start a new input prefix for billing/cache (the dropped history is no longer in that prefix). Spawn runs children in separate context so their token use does not permanently inflate the parent. This extension does not configure provider cache TTLs or breakpoints.

**Readonly** — Session-persisted research posture. Toggle via `/readonly`, Ctrl+Shift+R, or `--readonly`. Skills/prompts may set `readonly: true` in frontmatter to defer-enable when invoked. Write/edit always blocked at the tool boundary. Bash uses a two-layer guard:

Expand All @@ -68,7 +70,7 @@ Coding-agent guardrail on every OS — not a hardened security boundary. Stronge
| `spawn/` | Child sessions and live TUI rendering |
| `model-groups/` | Persistence, boot validation, CRUD TUI/autocomplete, and spawn routing |
| `notebook/` | Page store, tools, topic, rehydration |
| `handoff/` | Eligibility, brief, compaction bridge |
| `handoff/` | Eligibility, prompt, compaction bridge |
| `readonly-*.ts` / `os-sandbox.ts` | Readonly posture, bash policy, sandbox |
| `watchdog.ts` / `tui.ts` / `state.ts` | Pressure advisories, status UI, shared state |

Expand Down
10 changes: 5 additions & 5 deletions docs/why.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,21 @@ All three manage context **around** the model. The agent stays a passive recipie
| Move | Primitive | Prevents |
|---|---|---|
| **Isolate** | Spawn | Noisy subtasks polluting the parent |
| **Ground** | Notebook | Losing reusable knowledge across deliberate cuts *in the same task* |
| **Remember** | Notebook | Losing reusable knowledge across deliberate cuts *in the same task* |
| **Compact** | Handoff | Waiting on `/compact`, late auto-summarize, or one mixed summary blob |
| **Guard** | Readonly | Accidental edits during research and planning (write/edit blocked everywhere; bash OS-sandboxed on macOS/Linux — **Windows is classifier-only, not syscall-level**) |

### Notebook is not “memory
### Notebook is task-scoped shared memory

Standard agent memory systems try to be **long-lived**: they accumulate facts across days and projects, then rot. Stale entries, conflicting truths, and cache invalidation become the product.

The notebook is deliberately the opposite. It is **coupled to the conversation/task**:

- Pages carry grounding across **handoff** and resume of *this* work stream
- Pages carry memory across **handoff** and resume of *this* work stream
- **`/new` (or a new session) clears everything** with the conversation
- Nothing is shared into the next unrelated job unless the agent writes it again on purpose
- Pages split into two tiers: re-derivable code facts are a discardable cache; user guidance, decisions, design, and task scope are kept and refreshed for the life of the stream

So the agent can keep facts, decisions, constraints, and expensive findings **without** building a forever store that needs invalidation. Handoff still splits concerns: the notebook holds reusable grounding *for this task*; the brief holds only remaining situational context. That beats one summary blob that mixes both — and beats external memory that outlives the work and goes stale.
So the agent can keep user guidance, decisions, constraints, and expensive findings **without** building a forever store that needs invalidation: code facts are pruned as they go stale, while non-recoverable knowledge is refreshed for the life of the stream. Handoff still splits concerns: the notebook holds reusable memory *for this task*; the prompt holds only remaining situational context. That beats one summary blob that mixes both — and beats external memory that outlives the work and goes stale.

## Awareness, not autopilot

Expand Down
6 changes: 3 additions & 3 deletions handoff/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* /handoff command for the agenticoding extension.
*
* Collects a user direction, asks the LLM to complete the picture in a
* handoff brief, and lets the handoff tool perform the actual compaction.
* handoff prompt, and lets the handoff tool perform the actual compaction.
*/

import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
Expand All @@ -18,7 +18,7 @@ import { STATUS_KEY_HANDOFF } from "../tui.js";
export function registerHandoffCommand(pi: ExtensionAPI, state: AgenticodingState): void {
pi.registerCommand("handoff", {
description:
"Ask the LLM to draft a handoff brief that completes the picture from " +
"Ask the LLM to draft a handoff prompt that completes the picture from " +
"your direction, then perform the handoff automatically.",

handler: async (args, ctx) => {
Expand Down Expand Up @@ -63,7 +63,7 @@ export function registerHandoffCommand(pi: ExtensionAPI, state: AgenticodingStat
: "\n\nA real handoff is required in the current session. Do not continue normal work instead.";

pi.sendUserMessage(
`Handoff direction: ${direction}\n\nPrepare a handoff in the current session now. First, save any durable reusable knowledge that aligns with the direction above to the notebook: findings worth keeping, constraints discovered, decisions made, or other grounding future contexts will need. Then draft a concise but sufficiently detailed handoff brief capturing only the remaining situational context: current state, blockers, unresolved questions, failed paths worth avoiding, and next steps. The next context will read the notebook on demand, so do not duplicate notebook content in the brief. Use any structure that makes the next work unambiguous. Reference notebook pages by name when relevant.${readonlyNotice}`,
`Handoff direction: ${direction}\n\nPrepare a handoff in the current session now. First, update the notebook to match the direction: refresh non-recoverable knowledge (user guidance, decisions, design, task scope) and discard pages holding only recoverable code facts. Then draft a concise but sufficiently detailed handoff prompt capturing only the remaining situational context: current state, blockers, unresolved questions, failed paths worth avoiding, and next steps. The next context will read the notebook on demand, so do not duplicate notebook content in the prompt. Use any structure that makes the next work unambiguous. Reference notebook pages by name when relevant.${readonlyNotice}`,
ctx.isIdle() ? undefined : { deliverAs: "followUp" },
);
},
Expand Down
2 changes: 1 addition & 1 deletion handoff/compact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function registerHandoffCompaction(pi: ExtensionAPI, state: AgenticodingS
// pendingHandoff — cleared here (the compaction hook consumed the queued task)
// pendingRequestedHandoff — kept; cleared later by completeHandoff in tool.ts
// (on success) or preserved for retry (on error).
// Read readonlyEnabled at the cut so the brief reflects a toggle made after
// Read readonlyEnabled at the cut so the prompt reflects a toggle made after
// the handoff tool was called but before Pi consumes the queued task.
const task = buildEnrichedTask(pending.task, {
resumeReadonlyAfterHandoff: state.readonlyEnabled,
Expand Down
8 changes: 4 additions & 4 deletions handoff/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ import {
/**
* Build the enriched task that becomes the compaction summary.
*
* Shape: handoff primer + original task.
* Shape: handoff prompt + original task.
*/
export function buildEnrichedTask(task: string, options?: { resumeReadonlyAfterHandoff?: boolean }): string {
const parts: string[] = [
"## Handoff — Continue Previous Work",
"",
"You are continuing a previous agent's work in a clean context. Use the available knowledge correctly:",
"- Notebook pages hold durable grounding knowledge; fetch them with `notebook_read`",
"- This handoff brief holds the distilled next task and immediate situational context",
"- Notebook pages are a cache for this stream: code facts are re-derivable, while user guidance, decisions, and design live in pages — fetch them with `notebook_read`",
"- This handoff prompt holds the distilled next task and immediate situational context",
"- Use `notebook_index` to scan available pages when needed",
"- Use `spawn` to delegate isolated subtasks to child agents",
"- Build on notebook grounding and this brief rather than reconstructing old context",
"- Build on notebook memory and this prompt rather than reconstructing old context",
];

if (options?.resumeReadonlyAfterHandoff) {
Expand Down
Loading
Loading