commit - 7d76e21a04c7df54c9db31a841f6bbc68361030d
commit + 00c243fe224c7d37aeae83f14e4ad33b046fc8c1
blob - /dev/null
blob + 32801b04ca6157266086737fb4dbc0c864ff02df (mode 644)
--- /dev/null
+++ .scratch/context-compaction/issues/01-serialize-conversation-for-summarization.md
+# Serialize conversation history for summarization
+
+Status: needs-triage
+Type: task
+
+## What to build
+
+Change `/compact` so the summarizer receives one explicitly delimited text
+document instead of the original messages in their live User/Assistant roles.
+Render the canonical conversation with labels such as:
+
+```text
+[User]: What the Owner said
+
+[Assistant]: Response text
+
+[Assistant tool calls]: read(path="foo.c")
+
+[Tool result]: Tool output
+```
+
+Wrap that serialization in `<conversation>` tags and use a summarizer-specific
+system instruction that says to summarize, never answer or continue the
+embedded conversation. Tool results are capped at 2,000 bytes each and retain
+their beginning followed by a conspicuous count of omitted bytes.
+
+This adapts pi's
+[`serializeConversation()`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/compaction/utils.ts)
+and summarization request shape rather than copying TypeScript into fugu. The
+current fugu message model has no thinking block, so it does not emit pi's
+`[Assistant thinking]` record unless that becomes canonical conversation state
+in a separate change.
+
+## Why
+
+Today `cmd_compact()` passes the active Projection as ordinary provider
+messages and appends the summarization instruction as one more User message.
+The model can interpret that as a conversation to continue. The raw tool
+results also make the compaction request nearly as vulnerable to context
+overflow as the request it is meant to rescue.
+
+## Acceptance criteria
+
+- [ ] A pure, length-aware serializer handles User text, Assistant text,
+ Assistant tool calls, and User tool results in Projection order.
+- [ ] Labels and blank-line separation are byte-exact and covered by unit
+ tests; embedded role-looking text remains data inside the delimited document.
+- [ ] Each tool result keeps at most 2,000 bytes, never splits valid UTF-8, and
+ appends an omission marker containing the exact omitted-byte count.
+- [ ] Tool names and JSON arguments are rendered deterministically and remain
+ bounded by the existing Turn/request limits.
+- [ ] `/compact` sends exactly one serialized User document under a
+ summarizer-specific system prompt, with no tools and no live conversation
+ roles preceding it.
+- [ ] The generated summary still becomes the system-block continuation notes,
+ is journaled, resumes faithfully, and leaves manual `/compact` UI behavior
+ unchanged.
+- [ ] Adversarial tests cover control bytes, malformed UTF-8, empty blocks,
+ 1,999/2,000/2,001-byte results, multiple tools, and the request-size bound.
+- [ ] The compaction contract is documented in `docs/compaction.md` (creating
+ the document if this is the first compaction ticket implemented).
+
+## Code-cost analysis
+
+Expected size: **small-to-medium, about 350-600 lines including tests and
+documentation, across 3-5 focused commits**.
+
+| Seam | Likely work | Cost/risk |
+| --- | --- | --- |
+| Serializer | New `compaction.c/.h` over `struct msg`/`struct block`, UTF-8-safe truncation, deterministic tool formatting | 140-220 production lines; medium I13 risk |
+| Request shape | Let `cmd_compact()` supply a summarizer system prompt and one serialized message instead of the live Projection | 60-120 production lines; medium provider-parity risk |
+| Unit and wire tests | New pure suite plus `regress/turn` assertions for exact Anthropic/OpenAI/Claude Subscription bodies | 140-230 test lines |
+| Docs/build | Makefile registration and compaction documentation | 30-60 lines |
+
+The main uncertainty is whether “2 KB” means pi's exact 2,000-character
+JavaScript cap or a 2,048-byte fugu cap. This ticket recommends a named
+2,000-byte cap so the bound is deterministic in fugu's byte-oriented APIs.
+
+## Blocked by
+
+None - can start immediately.
+
+## Comments
+
blob - /dev/null
blob + 433581e5f0f4776ed170c448fd12264f83095137 (mode 644)
--- /dev/null
+++ .scratch/context-compaction/issues/02-cumulative-file-tracking-in-summaries.md
+# Carry cumulative file tracking through compaction summaries
+
+Status: needs-triage
+Type: task
+
+## What to build
+
+Every fugu-generated compaction summary carries deterministic
+`<read-files>` and `<modified-files>` blocks. Collect paths from structured
+`read`, `write`, `edit`, and `multi_edit` tool calls in the span being
+summarized, merge them with the previous fugu compaction's structured file
+details, sort and deduplicate them, and append the blocks to the model-written
+summary before it becomes continuation notes in the system block.
+
+This follows pi's cumulative file-operation design in
+[`compaction.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/compaction/compaction.ts)
+and
+[`utils.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/src/core/compaction/utils.ts):
+modified paths win over read-only paths, and prior structured details—not the
+model's prose—are the accumulation source.
+
+## Why
+
+The information is cheap to derive from tool calls, survives lossy prose
+summarization, and is directly useful to the same model when the summary is
+injected into fugu's system block. Without cumulative state, a second
+compaction can forget files that were important before the first cut.
+
+## Acceptance criteria
+
+- [ ] A length-aware collector extracts `path` only from the supported
+ structured file-tool arguments; shell command text is never guessed as a
+ path.
+- [ ] Read-only paths appear under `<read-files>` and any path written or
+ edited appears only under `<modified-files>`, regardless of operation order.
+- [ ] Lists are bytewise deterministic, sorted, and deduplicated across the
+ current span and every prior fugu-generated compaction.
+- [ ] The final XML-like blocks are generated by fugu and appended once after
+ model prose; the model is not trusted to preserve or generate the lists.
+- [ ] Paths with newlines, controls, tag delimiters, invalid UTF-8, or excessive
+ length cannot forge block structure; the encoding/rejection rule is pinned
+ and tested.
+- [ ] The compact journal record stores optional structured `read_files` and
+ `modified_files` details, remains backward-compatible with old summary-only
+ records, and restores the sets on resume.
+- [ ] `/clear` clears cumulative file state. A failed or cancelled compaction
+ changes neither the active summary nor its file sets.
+- [ ] Two successive `/compact` operations, resume-then-compact, read-then-edit
+ promotion, duplicates, malformed tool input, and empty lists have regression
+ coverage.
+- [ ] `docs/compaction.md` documents the blocks, cumulative semantics, and that
+ they describe structured tool calls rather than an audited filesystem log.
+
+## Code-cost analysis
+
+Expected size: **medium, about 500-850 lines including tests and docs, across
+4-6 focused commits**.
+
+| Seam | Likely work | Cost/risk |
+| --- | --- | --- |
+| Collection/formatting | Bounded path sets, JSON argument extraction, modified-wins normalization, safe block rendering | 180-300 production lines; medium untrusted-input risk |
+| Durable state | Extend compact journal records and replay with optional arrays; carry/reset state in the coordinator | 100-180 production lines; medium I14 compatibility risk |
+| Integration | Merge prior details with the compacted Projection and append generated blocks exactly once | 50-100 production lines |
+| Tests/docs | Pure collector tests plus journal, resume, clear, and repeated-compaction integration coverage | 220-350 lines |
+
+Using optional journal fields costs more than parsing tags back out of prose,
+but keeps cumulative state structural and makes malformed model output
+irrelevant. It is still a bounded change and introduces no dependency or new
+privilege.
+
+## Blocked by
+
+None - can start immediately. If ticket 01 is implemented concurrently, land
+the shared compaction module first to avoid overlapping edits.
+
+## Triage decision
+
+Decide whether a failed file-tool call should be tracked as model-visible work
+history (pi tracks calls) or omitted as an unsuccessful operation. The cheap
+pi-compatible default is to track accepted tool calls and document that the
+lists are not proof that an operation succeeded.
+
+## Comments
+
blob - /dev/null
blob + fefdaca4e782a2d60e8f50bcc235eadc2d9f7211 (mode 644)
--- /dev/null
+++ .scratch/context-compaction/issues/03-auto-compaction-with-overflow-recovery.md
+# Add automatic compaction with one-shot overflow recovery
+
+Status: needs-triage
+Type: task
+
+## What to build
+
+Add automatic context compaction around the canonical Lead Turn. Trigger it
+before a Generation when:
+
+```text
+contextTokens > contextWindow - reserveTokens
+```
+
+Use defaults of 16,384 reserve tokens and 20,000 recent tokens kept. Select a
+cut by walking backward over journal/Projection entries, normally cutting at a
+Turn boundary and never at a tool-result message. If one Turn alone exceeds
+the keep budget, summarize its early prefix separately and retain a valid
+Assistant boundary with every tool call/result pair intact.
+
+If a provider rejects a request specifically for context overflow, compact the
+same Turn state and retry that rejected Generation exactly once. Never rerun an
+already executed tool, never retry after accepted content, and never recurse if
+the summarization request itself overflows.
+
+This adopts the high-value behavior documented in pi's
+[`docs/compaction.md`](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/compaction.md),
+adapted to fugu's append-only Journal, active Turn transaction, provider-neutral
+Generation, and privilege-separated API worker.
+
+## Current behavior and implementation constraints
+
+- `/compact` is manual, requires an idle `turn_txn`, summarizes the entire
+ Projection, journals one summary string, and resets all messages.
+- `/context` has a byte-derived estimate and `effective_window()` can resolve a
+ configured, provider-reported, or compiled window. There is no tokenizer for
+ the unsent Projection.
+- `turn_run()` treats every provider error as terminal failure. Provider error
+ type/message currently collapse into a display string, so overflow cannot be
+ classified safely without extending the Generation/API seam.
+- A Lead Turn accepts its User message, Assistant tool calls, and tool results
+ into an active transaction before commit. Prefix replacement therefore must
+ preserve `pending_first`, Projection receipts, cache state, and journal
+ abandonment semantics.
+- Resume currently treats a compact record as a full reset. Keeping a recent
+ suffix requires a durable cut reference or a bounded retained-message
+ snapshot; prose alone is insufficient.
+
+## Acceptance criteria
+
+- [ ] Auto-compaction defaults to enabled and uses named defaults of 16,384
+ reserve tokens and 20,000 kept tokens; configuration syntax, validation, and
+ disable behavior are documented and tested.
+- [ ] With a known effective window, the strict `>` threshold is evaluated on
+ every prospective Lead Generation, including continuations after tool
+ results. With an unknown window, threshold compaction is skipped but typed
+ overflow recovery remains available.
+- [ ] `contextTokens` uses one documented conservative estimator over the
+ actual prospective Projection. Provider usage may calibrate it but stale
+ usage from a prior request is never treated as the exact next-request size.
+- [ ] The cut planner normally retains whole recent Turns up to the 20,000-token
+ target and never starts retained context with a tool-result message or leaves
+ a tool call without its result.
+- [ ] A Turn larger than the keep target is split only at a valid User or
+ Assistant boundary. Its early prefix receives a distinct Turn-context
+ summary that is merged with the history summary before the valid suffix.
+- [ ] Compaction can replace a prefix while the transaction is idle or active
+ without committing, abandoning, duplicating, or losing pending Turn state.
+- [ ] The compact journal record durably identifies or contains the retained
+ suffix, and resume reconstructs exactly the same provider-valid Projection.
+- [ ] Provider adapters/API IPC expose a bounded typed context-overflow
+ category based on HTTP status and provider error type/code—not broad English
+ substring matching. Unknown errors retain today's failure behavior.
+- [ ] An overflow-rejected Generation with no accepted content compacts and is
+ retried once. A second overflow, any post-content failure, cancellation, or
+ compaction failure ends the Turn conspicuously with no further retry.
+- [ ] Tool side effects occur at most once across recovery; tests cover an
+ overflow after a completed tool-result batch and prove tools are not rerun.
+- [ ] Prompt-cache receipts reset after a cut, usage remains additive and
+ nonduplicated, and the UI distinguishes summarization from the retry.
+- [ ] Manual `/compact` remains available and shares the serializer, summary
+ policy, durable state, and safety checks rather than maintaining a second
+ implementation.
+- [ ] `docs/compaction.md`, `fugu(1)`, `fugu.conf(5)`, behavior, design, and
+ verification docs define threshold, cuts, split Turns, overflow recovery,
+ unknown-window behavior, and the exactly-once limit.
+
+## Code-cost analysis
+
+Expected size: **medium-large and high-risk, about 1,800-3,000 lines including
+tests and documentation, across 7-11 reviewable commits**. The value is high,
+but this is not a one-condition change: the cost is transactional correctness
+and durable replay.
+
+| Seam | Likely work | Cost/risk |
+| --- | --- | --- |
+| Token accounting and cut planner | Prospective Projection estimator, effective-window policy, Turn/tool-pair boundary planner, split-Turn preparation | 220-380 production + 300-500 test lines; medium algorithmic risk |
+| Shared compactor | Reuse ticket 01 serialization, summarize history/prefix, merge prior summary/details, enforce no-tools and bounded output | 140-260 production + 180-300 test lines |
+| Turn transaction and Journal | Prefix replacement while idle/active, retained-suffix durability, replay, cache receipt reset, clear/abandon behavior | 220-380 production + 350-550 test lines; high I14 risk |
+| Provider error typing | Preserve provider error code/type and HTTP classification through `fugu-api` → Generation → Turn result | 120-220 production + 180-300 test lines; medium protocol risk |
+| Retry orchestration | Re-enter only the rejected Generation, cap at one, preserve usage and tool exactly-once semantics | 160-300 production + 250-450 integration-test lines; high side-effect risk |
+| Config/UI/docs | Defaults, parser/man page, summarize/retry notices, `docs/compaction.md` and verification matrix | 120-220 implementation/doc lines + config tests |
+
+The estimate assumes tickets 01 and 02 land first. Without them, add roughly
+700-1,100 lines and two to four commits. No new runtime dependency or privilege
+is required; the security boundary stays the same, but the coordinator and
+Journal state machines gain new states that require adversarial crash/resume
+tests.
+
+## Blocked by
+
+- [01 — Serialize conversation history for summarization](./01-serialize-conversation-for-summarization.md)
+- [02 — Carry cumulative file tracking through compaction summaries](./02-cumulative-file-tracking-in-summaries.md)
+
+## Triage decisions
+
+1. Pin the configuration names and whether model-specific `context_limit`
+ remains an override for auto-compaction.
+2. Choose the durable suffix representation: stable Journal entry references
+ or an explicit retained-message snapshot in the compact record.
+3. Pin provider-specific overflow codes/statuses from captured primary-source
+ fixtures before implementing the typed classifier.
+
+## Comments
+
blob - /dev/null
blob + 1a957c882c49b2701f331f8084e42e1fafef59fd (mode 644)
--- /dev/null
+++ .scratch/context-epochs/issues/01-evaluate-immutable-context-epochs.md
+# Evaluate immutable Context Epochs and chronological context deltas
+
+Status: needs-triage
+Type: research
+
+## Research question
+
+Should fugu stop rebuilding its privileged system block when context changes
+and instead persist an immutable Context Epoch plus chronological, model-visible
+“context changed” deltas?
+
+OpenCode V2's
+[`specs/v2/session.md`](https://github.com/anomalyco/opencode/blob/dev/specs/v2/session.md)
+persists the exact privileged System Context shown to the model as an immutable
+baseline. At safe provider-turn boundaries it observes context sources and, if
+one changed, atomically journals one chronological System message and advances
+the epoch snapshot. A completed compaction or Session move starts a fresh
+baseline. This preserves history semantics and allows an unchanged provider
+cache prefix to remain valid.
+
+Fugu currently composes `sysfull` from the base prompt, personal context,
+project context, and compaction notes. It journals personal/project bytes and
+the summary, rebuilds after startup, `/clear`, and `/compact`, and deliberately
+starts cache state cold after `/clear`, `/compact`, `/model`, and resume. Its
+canonical conversation has only User and Assistant roles, so chronological
+System deltas would be a real model/journal/provider change rather than a
+different representation of existing state.
+
+## What to investigate
+
+1. Pin the OpenCode source revision and describe Context Epoch admission,
+ source observation, atomic delta publication, compaction reset, unavailable
+ source behavior, and prompt-cache intent.
+2. Inventory every fugu system-context source and every time it can actually
+ change during a Session: configured/built-in system prompt, personal and
+ project context, `#` capture, `/clear`, `/compact`, model/provider selection,
+ skills, environment/date facts, and resume under changed config/files.
+3. Verify or reject the premise that fugu silently rewrites prior context.
+ `journal_context()` already snapshots project/personal bytes for replay;
+ determine exactly which baseline inputs remain unjournaled and which live
+ changes are not re-observed at all.
+4. Compare three designs:
+ - keep full-system rebuilds and document their cache/replay semantics;
+ - journal full immutable Context Epoch snapshots but still rebuild on an
+ explicit epoch boundary; or
+ - add canonical chronological System-delta messages at safe boundaries.
+5. For each design, analyze Anthropic and Claude Subscription cache breakpoint
+ stability, OpenAI behavior, provider request validity, context growth,
+ compaction interaction, prompt-injection presentation, and crash/resume
+ fidelity under invariant I14.
+6. Measure or estimate whether cache savings justify adding a System role and
+ context-observation state machine in a one-Owner local process.
+
+## Deliverable
+
+A checked-in research note with:
+
+- a source-by-source current-state and replay table;
+- a timeline comparison for startup → context edit → next Turn → compaction →
+ resume;
+- explicit findings for semantic fidelity and provider-cache validity;
+- a recommendation to retain, partially adopt, or fully adopt Context Epochs;
+- an ADR proposal if the canonical conversation or Journal contract should
+ change; and
+- separately scoped implementation tickets with code-cost estimates only if
+ adoption is recommended.
+
+## Acceptance criteria
+
+- [ ] The mutable OpenCode `dev` source is pinned to a commit.
+- [ ] Claims about current fugu behavior cite code, behavior/design docs, and
+ journal/cache regressions; the ticket's premise is tested, not assumed.
+- [ ] The analysis distinguishes a context-source snapshot, a system baseline,
+ a chronological delta, a compaction summary, and prompt-cache bookkeeping.
+- [ ] `/clear`, `/compact`, `#` capture, model switch, changed files before
+ resume, and changed config before resume each receive an explicit verdict.
+- [ ] Any proposal preserves fugu's provider-neutral Projection validity and
+ append-only crash/replay invariants.
+- [ ] The recommendation quantifies implementation complexity and expected
+ cache benefit well enough to decide whether to build it.
+
+## Research cost
+
+Expected size: **medium, roughly 250-500 lines of evidence and design analysis**.
+The research is inexpensive; full adoption would likely be medium-to-large
+because it touches the canonical message roles, request builders, Journal
+replay, context observation, compaction, and all provider cache tests.
+
+## Blocked by
+
+None - can start immediately. Coordinate conclusions with the context
+compaction tickets because compaction is an epoch boundary in both designs.
+
+## Comments
+
blob - /dev/null
blob + e61328040cf5009fee5be117039fc35f923c5639 (mode 644)
--- /dev/null
+++ .scratch/durable-tool-output/issues/01-retain-bounded-tool-output-artifacts.md
+# Retain full tool output as private, bounded artifacts
+
+Status: needs-triage
+Type: task
+
+## What to build
+
+Treat output bounding as a settlement boundary. The model still receives a
+small bounded preview, but output beyond that preview is retained—up to a
+larger hard artifact cap—in Owner-only managed storage. The result names an
+opaque output ID, retained byte count, truncation state, and the paged tool the
+model can use to inspect another range without rerunning the command.
+
+Start with foreground `shell`, `grep`, `find`, and `ls`, whose subprocess
+capture currently stops at 256 KiB, and `shell_output`, whose 256 KiB ring drops
+overflow. Decide explicitly whether brokered web output belongs in the first
+slice or a follow-up.
+
+Do **not** add OpenCode's stale-registration rejection. Fugu's tool definitions
+are static C objects selected from startup configuration/skills; it has no
+runtime plugin registry, registration generation, or stale executor handle to
+reject. Revisit that rule only if dynamic tools/MCP/plugins are introduced.
+
+## Why the safe version is not just a larger buffer
+
+- Foreground subprocesses are killed when capture exceeds 256 KiB today, so
+ bytes beyond the marker do not exist to recover.
+- Under `allow_write no`, `fugu-tool` has no `wpath`/`cpath`; creating a file in
+ the project would weaken the kernel-enforced read-only mode.
+- The coordinator can create private 0600 files only inside its existing
+ session unveil, while the tool worker cannot open that directory.
+- A raw session path is not useful to the model because normal file tools
+ cannot read it, and placing generated logs in the project risks accidental
+ commits and secret disclosure.
+- Truly unbounded retention permits disk exhaustion. “Full” must mean complete
+ up to a documented per-artifact and aggregate-session quota, followed by a
+ second conspicuous truncation boundary.
+
+The preferred architecture is therefore a coordinator-owned artifact store.
+The coordinator creates a private file and passes only a write descriptor to
+the executing worker; the worker streams to it while constructing the preview.
+A provider-visible retrieval tool addresses the artifact by unpredictable ID
+and returns bounded offset/limit windows. No role receives a wider path unveil.
+
+## Acceptance criteria
+
+- [ ] Tool results return a bounded, useful preview and structured metadata:
+ opaque ID, total bytes observed, retained bytes, whether the preview was
+ abbreviated, and whether the artifact hard cap was reached.
+- [ ] Preview policy is pinned (for example useful head and tail regions) and
+ remains within the existing per-tool/Turn model-context bounds.
+- [ ] A new paged retrieval tool reads an artifact by ID with byte or line
+ offset/limit, reports the next cursor and total retained size, and never
+ accepts a filesystem path.
+- [ ] Artifact IDs are unguessable within the process, length-checked, bound to
+ the current Session, and cannot traverse or alias another Owner file.
+- [ ] Files and directories are Owner-only (0600/0700), created atomically
+ under the existing session authority, and never placed in the project tree.
+- [ ] The executor receives only pre-opened descriptors. `allow_write no`,
+ subagent read-only confinement, API/web no-file authority, and every current
+ unveil set remain unchanged and are proved by sandbox regressions.
+- [ ] Foreground commands continue after the 256 KiB preview boundary but are
+ killed at timeout or the larger artifact cap. Disk writes and IPC draining
+ are bounded so a fast producer cannot exhaust memory or starve the event
+ loop.
+- [ ] Background jobs spool incrementally instead of silently dropping ring
+ overflow; polling remains bounded and does not replay bytes already returned.
+- [ ] Artifact creation/finalization and the corresponding tool-result
+ settlement have defined crash behavior. A failed or abandoned Turn cannot
+ leave a journaled ID that points to another Turn's data.
+- [ ] Resume either preserves journaled artifact IDs for paged retrieval or
+ marks them durably unavailable; the chosen retention and cleanup policy is
+ explicit and tested.
+- [ ] Per-artifact, per-Session, and global cleanup/quota behavior prevents disk
+ exhaustion and handles a full filesystem conspicuously without losing the
+ bounded model result.
+- [ ] Tests cover exact preview boundaries, multi-megabyte output, binary/NUL
+ bytes, invalid IDs/cursors, timeout, hard-cap termination, cancellation,
+ crash/resume, read-only mode, concurrent subagents/jobs, and cleanup.
+- [ ] Behavior, security caveats, tool descriptions, manuals, and the
+ verification matrix explain that retained output may contain secrets and is
+ private Session state.
+
+## Code-cost analysis
+
+Expected size: **medium-large, roughly 1,000-1,800 lines including tests and
+documentation, across 6-9 commits**. A project-local tempfile would be much
+smaller, but would fail under `allow_write no`, pollute the working tree, and
+weaken fugu's authority model.
+
+| Seam | Likely work | Cost/risk |
+| --- | --- | --- |
+| Artifact store | ID generation, private files, quotas, finalize/reopen/cleanup, crash behavior | 180-320 production lines; high disk/state risk |
+| Descriptor protocol | Coordinator creates/passes fds; tool worker streams without new unveil rights | 100-200 production lines; medium IPC risk |
+| Capture and jobs | Tee subprocess output to artifact while retaining bounded preview; replace lossy job ring semantics | 180-320 production lines; high backpressure/process risk |
+| Retrieval tool | Definition, argument validation, bounded paging, Session lookup | 120-220 production lines |
+| Tests/docs | Pure store tests, tool/Turn integration, sandbox/ktrace, resume/quota adversarial cases, manuals | 400-700 lines |
+
+## Blocked by
+
+None - can start after triage, but storage lifetime and quota choices must be
+pinned before implementation.
+
+## Triage decisions
+
+1. Per-artifact and per-Session hard caps, and whether hitting the hard cap
+ kills the producer or continues while discarding.
+2. Preview shape: head only, head+tail, or tool-specific.
+3. Artifact lifetime across successful, failed, abandoned, and resumed Turns.
+4. Whether the first slice covers web responses and subagent-internal tools or
+ only Lead local subprocess tools/background jobs.
+5. Whether Owner-facing output exposes the private path while the model sees
+ only the opaque ID.
+
+## Comments
+
blob - /dev/null
blob + 40b2055ad61274f3a3f3f657a596f1e53de365a7 (mode 644)
--- /dev/null
+++ .scratch/model-thinking-effort/issues/01-configurable-thinking-effort.md
+# Configurable model thinking effort
+
+Status: needs-triage
+Type: task
+
+## Problem
+
+fugu has no way to select a model's thinking or reasoning effort. The
+configuration carries the model, output-token cap, and context-window override,
+but no effort setting. The Anthropic, OpenAI, and experimental Claude
+Subscription request builders therefore rely entirely on provider defaults.
+
+This is especially visible with models that expose named levels such as
+`low`, `medium`, `high`, and `xhigh`: changing models may silently change the
+amount of reasoning because fugu cannot preserve an explicit Owner choice.
+
+## Evidence
+
+- `struct fugu_conf` and the parser have no effort field or directive.
+- `anthropic_build_request()`, `openai_build_request()`, and
+ `claude_sub_build_request()` emit no thinking/reasoning effort parameter.
+- The experimental Claude Subscription compatibility profile advertises an
+ effort beta, but its request adapter does not currently use it.
+
+## Desired behavior
+
+Add an optional symbolic effort setting that can express the levels supported
+by the selected model/provider. Leaving it unset must preserve today's
+provider-default behavior.
+
+The selected value must reach every Generation consistently, including tool
+continuations and compaction. The design must explicitly decide whether
+subagents inherit the Lead setting or receive a separate override.
+
+## Acceptance criteria
+
+- Configuration accepts and validates an effort directive with a documented
+ set of symbolic values.
+- `fugu -n` prints the effective nonsecret value.
+- Each supported provider adapter maps the symbolic value to its native wire
+ field; an unsupported provider/model combination fails conspicuously or has
+ a documented fallback rather than silently selecting a different level.
+- The value survives model selection and every Generation within a Turn.
+- Subagent inheritance or override behavior is explicit and covered by tests.
+- Request-builder regressions assert the exact wire shape for every supported
+ provider, and config regressions cover valid, invalid, default, and match
+ precedence behavior.
+- `fugu.conf(5)` documents values, defaults, provider limitations, and examples.
+
+## Triage decisions needed
+
+1. Directive name: `effort`, `thinking_effort`, or another term.
+2. Canonical value set and how provider-specific names such as `xhigh`/`max`
+ map without pretending unsupported levels are equivalent.
+3. Whether subagents inherit the Lead value or gain `subagent_effort`.
+4. Whether unsupported model/provider combinations are rejected locally or
+ passed through for the provider to reject.
+
+## Comments
blob - /dev/null
blob + b1cbc5ef5c62127cf30b88365543dacc7803da60 (mode 644)
--- /dev/null
+++ .scratch/prompt-admission-semantics/issues/01-compare-steer-and-queue-promotion-rules.md
+# Compare steer and queue promotion rules with OpenCode V2
+
+Status: needs-triage
+Type: research
+
+## Research question
+
+Does fugu's specification completely define when queued and steered User input
+becomes model-visible, especially when multiple inputs arrive around tool
+continuations and the per-Turn tool allowance?
+
+OpenCode V2 separates durable admission (`PromptAdmitted`) from atomic
+promotion into model-visible history (`Prompted`). Fugu already has a cleaner
+single-process analogue: the tty queue is pending interface state, steers enter
+the active Turn transaction only at a safe tool-result boundary, and the
+Pending Projection becomes durable only when the Turn commits. There is no
+structural inbox architecture to adopt.
+
+The useful comparison is OpenCode's more explicit promotion policy in
+[`specs/v2/session.md`](https://github.com/anomalyco/opencode/blob/dev/specs/v2/session.md):
+
+- steers promote at the next safe provider-turn boundary;
+- queues remain FIFO while the current drain requires continuation;
+- when execution would become idle, promote exactly one queued input and then
+ reevaluate continuation before promoting another; and
+- multiple steers promoted at one boundary reset the provider-turn allowance
+ once, not once per steer.
+
+## What to investigate
+
+1. Pin the OpenCode source revision and restate its `PromptAdmitted` →
+ `Prompted`, steer, queue, and allowance rules as a small state machine.
+2. Trace fugu's actual behavior through `fugu-tty` queue ownership,
+ `UI_STEER`, `coord.c`'s aggregated `steer` buffer, `turn_mechanics`, the
+ Turn transaction, Journal commit/abandon, and resume.
+3. Compare specification, implementation, and regressions for at least:
+ multiple steers before one tool-result boundary; a steer after the final
+ provider response; two or more FIFO queue entries; a queued prompt whose
+ Turn itself continues through tools; interleaved queue/steer input;
+ failure/cancellation/session switch; and resume.
+4. Determine what “reset the Turn allowance once” should mean in fugu, whose
+ 50-step tool limit currently belongs to one canonical Turn. Do not assume
+ OpenCode's counter has identical semantics.
+5. Identify specification gaps separately from implementation bugs. Do not
+ propose a durable inbox or multi-process event architecture unless a
+ concrete fugu requirement needs one.
+
+## Deliverable
+
+A checked-in research note containing:
+
+- an OpenCode-to-fugu state/transition table;
+- exact evidence links and fugu file/line references;
+- a verdict for each OpenCode rule: already guaranteed, implemented but
+ underspecified, missing test, or behavior change needed;
+- proposed replacement wording for `handoff/behavior.md` and
+ `handoff/verification.md` where precision is missing; and
+- separate implementation tickets only for confirmed code changes.
+
+## Acceptance criteria
+
+- [ ] The OpenCode commit is pinned; mutable `dev` documentation is not treated
+ as timeless evidence.
+- [ ] The analysis distinguishes admission, promotion, provider dispatch,
+ Journal acceptance, Turn commit, and UI dequeue.
+- [ ] “Promote one queue entry, then reevaluate” and “one allowance reset per
+ steer batch” each receive an explicit fugu verdict.
+- [ ] Existing regressions are mapped to the edge cases they prove, and missing
+ cases are named with proposed fixtures/assertions.
+- [ ] The recommendation preserves fugu's one-Owner, one-process queue design
+ unless evidence demonstrates a real durability requirement.
+
+## Research cost
+
+Expected size: **small, one focused research pass and roughly 150-300 lines of
+notes/spec proposals**. If the implementation matches but the contract is
+ambiguous, the follow-up should be docs plus targeted regression cases rather
+than a runtime refactor.
+
+## Blocked by
+
+None - can start immediately.
+
+## Comments
+
blob - /dev/null
blob + 81466145e17b8abac8081fd5ac06ed70c54b801b (mode 644)
--- /dev/null
+++ .scratch/user-message-background/issues/01-grey-background-for-user-transcript-messages.md
+# Render transcript User messages on a grey background
+
+Status: needs-triage
+Type: task
+
+## What to build
+
+Give each User message in the curses transcript a grey-background block so it
+is visually distinct from light-grey Assistant prose and indented cyan tool
+activity. Reuse the same palette/treatment intended for the User input area so
+composing and reviewing the Owner's words feel like one visual language.
+
+The block must include the `> ` prefix, wrapped continuation lines, and padding
+to a deliberate edge. It must not recolor Assistant output or change line and
+print modes.
+
+## Acceptance criteria
+
+- [ ] Color terminals render every `IT_USER` transcript item with the chosen
+ grey background and readable foreground contrast.
+- [ ] All visual rows belonging to one wrapped or multiline User message have
+ coherent background fill; no unpainted gaps appear between the prefix, text,
+ continuation indent, and chosen right edge.
+- [ ] The treatment shares a named color-pair/palette helper with the input
+ area rather than duplicating numeric curses attributes.
+- [ ] Monochrome terminals retain a clear, readable fallback (for example
+ bold/reverse) without relying on color.
+- [ ] Narrow terminals, wide/combining UTF-8, `ascii yes`, resize/reflow,
+ scrollback, and the conversation drawer preserve correct geometry.
+- [ ] Headless renderer tests pin attributes and wrapping, and the pty
+ regression proves terminal startup/teardown remains clean.
+- [ ] `handoff/behavior.md`, `docs/design/m7-curses.md`, and `fugu(1)` describe
+ User transcript styling without pinning an implementation-only color number.
+
+## Code-cost analysis
+
+Expected size: **small, about 120-240 lines including tests/docs, in 2-3
+commits**. The text attribute is easy; the real cost is painting padding across
+every wrapped display row without bleeding the background into adjacent
+transcript items.
+
+Likely touch points are `src/fugu-tty/ui.c`, a render-focused regression (or a
+small extraction that makes span attributes testable), the curses pty check,
+and the three interface documents.
+
+## Blocked by
+
+None - can start immediately.
+
+## Triage decisions
+
+1. Should the block fill the transcript width, the content width plus padding,
+ or a fixed percentage/maximum width?
+2. Is “grey box used for User input” already a desired-but-unimplemented style
+ (the current `draw_input()` has no background pair), in which case this
+ ticket should style both surfaces together?
+3. Pick foreground/background values for 8-color terminals and define the
+ monochrome fallback.
+
+## Comments
+
blob - /dev/null
blob + 2af38effe39ffa1982e46c542826e56f3d56f05c (mode 644)
--- /dev/null
+++ docs/research/pi-supply-chain-posture.md
+# Pi supply-chain posture: what fugu should adopt
+
+Research date: 2026-07-16
+
+Pi source inspected: the current canonical `earendil-works/pi` repository at
+commit
+[`216e672`](https://github.com/earendil-works/pi/tree/216e672e7c9fc65682553394b74e483c0c9e47f7).
+The former `badlogic/pi-mono` URL redirects there.
+
+## Recommendation
+
+Do **not** copy Pi's npm controls wholesale. They are good controls for Pi's
+large, registry-resolved Node dependency graph, but fugu has deliberately
+removed that attack surface: it links only OpenBSD base libraries, has no
+language-registry dependency resolution step, and carries one small source
+dependency in the repository.
+
+Adopt two ideas in forms native to fugu:
+
+1. **Adapt exact pinning into explicit vendored-source provenance.** Record the
+ upstream URL, commit, and digest for `src/common/jsmn.h`, and optionally
+ verify the digest in `make check`. The file is already fixed by the fugu Git
+ commit; the missing part is an auditable upstream identity.
+2. **Adopt the outside-the-repository artifact test in `make live-check`.**
+ Fugu already does most of this more strongly than Pi by building an OpenBSD
+ package and installing it on a clean host. Make the artifact-only property
+ explicit by running the installed binary from a temporary project outside
+ the checkout, with development worker/config overrides unavailable.
+
+Reject `min-release-age`, `--ignore-scripts`, and a lifecycle-script allowlist
+for the current tree. They control registry selection and package-manager
+execution that do not occur in fugu. Reconsider them only if fugu later adds a
+registry dependency, ports library dependency, language runtime, generated
+foreign build step, or plugin/package ecosystem.
+
+## Decision matrix
+
+| Pi control | Decision for fugu | Reason | Estimated cost |
+|---|---|---|---|
+| Exact-pin dependencies | **Adapt; mostly satisfied already** | Committed vendored source and checksummed release distfiles are immutable inputs, but jsmn's upstream commit/digest is not recorded | Low: documentation alone is under an hour; an enforced digest/provenance regression is roughly 20-60 lines and a few hours including tests |
+| Minimum release age | **Reject now** | No registry resolver selects fresh releases during a fugu build or install | No implementation cost; a custom age mechanism would add maintenance without covering a current path |
+| `--ignore-scripts` everywhere | **Reject as Node-specific** | No npm lifecycle scripts execute; BSD make and ports hooks are first-party, reviewed build instructions and cannot simply be disabled | No implementation cost; preserve the existing no-runtime/no-ports-dependency charter |
+| Lifecycle-script allowlist | **Reject now; add only with such dependencies** | There is no lifecycle-script metadata or dependency graph to inspect | No implementation cost today; a premature checker would be policy theatre |
+| Release install outside the repo | **Adopt and strengthen `make live-check`** | Detects source-tree fallback, missing package files, wrong installed paths, and environment leakage | Low-medium: about half to one day for shell changes, regression coverage, documentation, and one clean-host validation; no new runtime dependency |
+
+The estimates assume the current OpenBSD-only architecture and include tests
+and documentation, but not release-machine provisioning.
+
+## What Pi actually implements
+
+Pi's stated policy is that npm dependency changes are reviewed code changes.
+Its current README lists exact direct dependency versions, a lockfile and
+published shrinkwrap, a two-day release-age gate, lifecycle-script suppression,
+a lifecycle-script review list, audits, and isolated local release installs.
+See the
+[`Supply-chain hardening` section](https://github.com/earendil-works/pi/blob/216e672e7c9fc65682553394b74e483c0c9e47f7/README.md#L61-L73).
+
+### Exact direct and transitive versions
+
+Pi sets `save-exact=true` and checks every external entry in
+`dependencies`, `devDependencies`, and `optionalDependencies` against an exact
+semantic-version pattern. Workspace and non-registry references are treated
+separately. The enforcement is source-visible in
+[`check-pinned-deps.mjs`](https://github.com/earendil-works/pi/blob/216e672e7c9fc65682553394b74e483c0c9e47f7/scripts/check-pinned-deps.mjs#L1-L63),
+not just a README convention.
+
+The root lockfile fixes development resolution, while the published coding
+agent carries a generated `npm-shrinkwrap.json` for its transitive production
+closure. This matters for Pi because an exact direct dependency can still
+declare ranged transitive dependencies. Fugu has no corresponding resolver or
+transitive package graph.
+
+### Two-day release cooldown
+
+Pi's
+[`.npmrc`](https://github.com/earendil-works/pi/blob/216e672e7c9fc65682553394b74e483c0c9e47f7/.npmrc#L1-L2)
+sets `min-release-age=2`. npm defines that value in days: versions newer than
+the window are excluded from resolution. npm also documents the tradeoff that
+the gate can temporarily block a newly published security fix, requiring an
+exception or a relaxed window; see the official
+[`min-release-age` configuration](https://docs.npmjs.com/cli/v11/commands/npm-install/#min-release-age).
+
+This is a quarantine control, not integrity verification. It gives registry
+and community detection systems time to react to a malicious new publication,
+but it neither proves that an older version is safe nor protects a version
+already fixed in a lockfile from a compromised download channel.
+
+### Lifecycle scripts: suppression plus review
+
+Pi's development, CI, local-release install, publish, and self-update paths use
+`--ignore-scripts` where supported. The policy is visible in the
+[CI install](https://github.com/earendil-works/pi/blob/216e672e7c9fc65682553394b74e483c0c9e47f7/.github/workflows/ci.yml#L32-L39),
+[publish path](https://github.com/earendil-works/pi/blob/216e672e7c9fc65682553394b74e483c0c9e47f7/scripts/publish.mjs#L52-L53),
+and
+[self-update construction](https://github.com/earendil-works/pi/blob/216e672e7c9fc65682553394b74e483c0c9e47f7/packages/coding-agent/src/config.ts#L115-L182),
+as well as the local-release source below. npm defines that flag as suppressing
+scripts from package manifests while still allowing an explicitly requested
+`npm run` command; see npm's
+[`ignore-scripts` semantics](https://docs.npmjs.com/cli/v11/commands/npm-install/#ignore-scripts).
+This removes the install-time code-execution path used by malicious
+`preinstall`, `install`, and `postinstall` scripts.
+
+Pi also inspects the generated production shrinkwrap for packages marked
+`hasInstallScript`. The current allowlist names two exact package versions and
+records why their scripts are considered harmless; any new marked package
+fails generation until reviewed. See the
+[`allowedInstallScriptPackages` list](https://github.com/earendil-works/pi/blob/216e672e7c9fc65682553394b74e483c0c9e47f7/scripts/generate-coding-agent-shrinkwrap.mjs#L7-L16)
+and the
+[`hasInstallScript` validation](https://github.com/earendil-works/pi/blob/216e672e7c9fc65682553394b74e483c0c9e47f7/scripts/generate-coding-agent-shrinkwrap.mjs#L224-L260).
+In Pi this is primarily an audit gate: normal install paths still suppress the
+scripts. It ensures that a dependency cannot silently acquire script-bearing
+metadata without review.
+
+### The outside-repository release check
+
+Pi's `release:local` script requires its output directory to be outside the
+repository, runs checks and tests, rebuilds publishable packages, packs them,
+and creates fresh npm and Bun production installs from those tarballs. See
+[`prepareOutputDirectory`](https://github.com/earendil-works/pi/blob/216e672e7c9fc65682553394b74e483c0c9e47f7/scripts/local-release.mjs#L107-L132)
+and the
+[`pack and isolated-install sequence`](https://github.com/earendil-works/pi/blob/216e672e7c9fc65682553394b74e483c0c9e47f7/scripts/local-release.mjs#L197-L255).
+
+One nuance matters when copying the pattern: the current script creates the
+isolated installs and prints outside-repo `pi --help` commands, but does not
+itself execute those commands. See its
+[`final smoke instructions`](https://github.com/earendil-works/pi/blob/216e672e7c9fc65682553394b74e483c0c9e47f7/scripts/local-release.mjs#L259-L283).
+The useful control is therefore artifact isolation; fugu can and should make
+the actual invocation automatic.
+
+## Fugu's current posture
+
+### Dependency surface
+
+Fugu's [dependency charter](../../handoff/charter.md) permits only OpenBSD base
+libraries in linked binaries and permits a small, permissively licensed,
+single-file vendored component only where base has no safe equivalent. The
+[port Makefile](../../port/productivity/fugu/Makefile) declares no
+`BUILD_DEPENDS`, `RUN_DEPENDS`, `LIB_DEPENDS`, `TEST_DEPENDS`, or `MODULES`; its
+only dependency declaration is the base-library `WANTLIB` set
+`c curses event tls util`. The
+[deployment regression](../../regress/deploy/deploy_test.sh) derives each
+binary's actual ELF dependencies and checks that their union exactly matches
+that set.
+
+The sole external source component is `src/common/jsmn.h`, documented in the
+[README](../../README) and isolated behind `src/common/json.c`. It is committed
+source, so every fugu commit already pins its exact bytes. At the research
+snapshot its SHA-256 is
+`c04533e9181e1e33baceb0f55ac449b05145bb936e8c68cc77dfe0d8277514fb`,
+and it is byte-identical to upstream jsmn at
+[`25647e6`](https://github.com/zserge/jsmn/blob/25647e692c7906b96ffd2b05ca54c097948e879c/jsmn.h).
+Neither the upstream commit nor digest is currently recorded in the tree. That
+is a traceability gap, not dynamic dependency drift.
+
+### Source and package integrity
+
+[`make local-port`](../../scripts/local-port) refuses a dirty worktree, archives
+the exact current Git commit, stages the maintained port under `mystuff`, and
+runs the ports framework's `makesum` and `checksum` targets. OpenBSD specifies
+that `checksum` compares every fetched distfile and patch against the SHA-256
+values in `distinfo`; see
+[`bsd.port.mk(5)`](https://man.openbsd.org/OpenBSD-7.9/bsd.port.mk.5#checksum).
+The OpenBSD porting guide also requires visible source versions rather than a
+moving `latest` file and recorded checksums for retrieved sources; see the
+official [Porting Guide](https://www.openbsd.org/faq/ports/guide.html#PortsChecklist).
+
+This is the relevant equivalent of lockfile integrity for fugu's own release
+distfile. It does not authenticate a malicious commit accepted into the fugu
+repository, but neither do Pi's exact version strings; source review and
+repository trust remain separate controls.
+
+### Build and release isolation
+
+[`make check`](../../scripts/check) removes prior build products, creates fresh
+object directories, compiles with warnings treated as failures, and runs every
+regression suite. The ports `package` target then builds from the checksummed
+archive through extraction, fake installation, and package creation. OpenBSD's
+framework verifies declared dependencies and `WANTLIB` before building and
+can check the final package's library declarations; see
+[`bsd.port.mk(5)` dependency and package targets](https://man.openbsd.org/OpenBSD-7.9/bsd.port.mk.5#prepare).
+The official testing guide additionally warns against accidentally detecting
+undeclared software already present on the build host; see the
+[OpenBSD Ports Testing Guide](https://www.openbsd.org/faq/ports/testing.html#Testing).
+
+Fugu's [`make live-check-privileged`](../../scripts/live-check) is already
+stronger than Pi's isolated install step in several respects. On a disposable
+host it rejects a pre-existing fugu package, binary, group, or configuration;
+snapshots and hashes the package; requires a hostname-plus-digest confirmation;
+installs with `pkg_add`; verifies package registration, ownership, modes, and
+files; and invokes the installed `fugu -n` as an ordinary user. The companion
+[release procedure](../release-live-checks.md) keeps credentialed model tests
+separate from privileged package installation.
+
+The remaining isolation gap is small but real: the privileged invocation
+inherits the directory from which the make target was launched, while the
+credentialed target defaults some invocations back to the fugu repository.
+Several tool and subagent checks already use a temporary project, but the
+artifact-only property is not a single enforced invariant. A packaged release
+could therefore be insufficiently tested for a source-tree fallback or an
+undeclared runtime file.
+
+OpenBSD package signing is a separate, ecosystem-native concern. Official
+packages are signed with `signify`, and OpenBSD documents how maintainers can
+sign their own packages in the
+[Package Signatures guide](https://www.openbsd.org/faq/ports/ports.html#PkgSig).
+Fugu's current clean-host check binds installation to an operator-confirmed
+package SHA-256, which is suitable for the present local one-Owner flow. If
+packages are later distributed across trust boundaries, signing them would be
+more relevant than importing npm release-age or lifecycle controls.
+
+## Threat and cost analysis
+
+Pi's controls address three high-probability Node threats:
+
+- semver or transitive resolution selecting an attacker-controlled registry
+ publication;
+- package lifecycle code executing before the dependency is reviewed; and
+- a published tarball working only because it can see monorepo files or a
+ hoisted development dependency.
+
+For fugu, the first two paths are absent. The comparable current threats are:
+
+- an unreviewed change to the committed jsmn bytes or loss of its upstream
+ provenance;
+- a release archive differing from the reviewed commit;
+- a package omitting a worker, manual, sample, permission, or other runtime
+ resource; and
+- an installed binary accidentally relying on the source checkout, development
+ environment variables, or build-host state.
+
+The current Git, ports checksum, package, ELF dependency, and clean-host checks
+already cover most of these. That is why adding npm-shaped controls would add
+more policy surface than protection.
+
+The two recommended adaptations have favorable cost-to-value ratios:
+
+- **Vendored provenance:** no runtime or build dependency, negligible ongoing
+ cost, and a direct audit trail for the only imported source. A digest check
+ should fail only when that file is intentionally reviewed and updated.
+- **Artifact-only live smoke:** reuses the existing temporary project,
+ installed package, hostile development-override checks, and clean-host
+ harness. Its release-time cost is seconds for `-n`/startup probes, plus any
+ already-authorized provider quota if the credentialed phase is included. It
+ catches a class of packaging errors that source regressions cannot.
+
+By contrast, a minimum-age database for one manually updated header would need
+timestamps and exception policy while providing no download-time enforcement.
+An ignore/allow-script layer would have nothing to inspect and could encourage
+confusing normal, reviewed OpenBSD port hooks with third-party package
+lifecycle execution. Both should remain explicit reconsideration triggers,
+not current implementation work.
+
+## Concrete release-policy shape
+
+Without adding a new dependency system, the eventual fugu policy should be:
+
+1. Every vendored source file names its upstream repository, immutable commit,
+ license, and digest; an intentional update changes those facts in the same
+ reviewed commit.
+2. Release source is produced only from clean committed Git state and accepted
+ by the ports checksum target.
+3. The package is built through the ports fake/package stages and its actual
+ linkage and packing list are checked.
+4. `make live-check` runs the installed front end from a newly created project
+ outside the source checkout, with development path overrides absent or
+ deliberately poisoned, and fails if any checkout resource is required.
+5. The privileged clean-host and ordinary-user credentialed phases remain
+ separate; this artifact smoke does not move network credentials into a root
+ process.
+6. Registry cooldowns and lifecycle-script policy become mandatory design
+ questions only if a future change introduces a registry resolver or foreign
+ install-time executable code.
+
+This preserves fugu's smaller supply chain instead of recreating Pi's larger
+one merely to apply Pi's mitigations.