pi-fabric 0.22.2 → 0.22.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -13
- package/docs/agents.md +291 -0
- package/docs/architecture.md +73 -0
- package/docs/audit-trace.md +112 -0
- package/docs/certification.md +134 -0
- package/docs/compaction.md +177 -0
- package/docs/configuration.md +245 -0
- package/docs/interface.md +87 -0
- package/docs/memory-recall.md +347 -0
- package/docs/programmatic-compaction.md +208 -0
- package/docs/providers.md +50 -0
- package/docs/schema-enforcement.md +165 -0
- package/docs/skills.md +45 -0
- package/docs/state-layer.md +197 -0
- package/package.json +2 -1
- package/skills/fabric-advisor/SKILL.md +6 -2
- package/skills/fabric-ambient/SKILL.md +8 -4
- package/skills/fabric-ambient/references/setup.md +37 -18
- package/skills/fabric-council/SKILL.md +79 -13
- package/skills/fabric-exec/SKILL.md +2 -2
- package/skills/fabric-exec/references/agents.md +1 -1
- package/skills/fabric-fusion/SKILL.md +130 -62
- package/skills/fabric-guide/SKILL.md +27 -0
- package/skills/fabric-rlm/SKILL.md +243 -24
- package/skills/fabric-schema/SKILL.md +33 -6
- package/skills/fabric-supervisor/SKILL.md +6 -2
- package/skills/fabric-workflow/SKILL.md +92 -21
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Interface & commands
|
|
2
|
+
|
|
3
|
+
Fabric's UI is built on the public `pi-code-previews` cooperative shell and a general-purpose, theme-aware activity surface. Users do not need to install `pi-code-previews` separately.
|
|
4
|
+
|
|
5
|
+
## Code previews
|
|
6
|
+
|
|
7
|
+
`fabric_exec` inherits the user's border/background mode, collapsed-result behavior, error styling, and tool-call timing without taking ownership of Pi's built-in tool renderers. Its renderer adds a numbered TypeScript preview, live phase/call activity, and compact phase/nested-call summaries. Fabric refreshes these settings at session start and applies the project-local layer only when Pi marks the project as trusted.
|
|
8
|
+
|
|
9
|
+
Nested `pi.read`/`pi.bash`/`pi.grep`/`pi.find`/`pi.ls`/`pi.write`/`pi.edit` calls use the same preview settings and presentation vocabulary as `pi-code-previews`: offset-aware read gutters, syntax and secret warnings, grouped/highlighted grep matches, iconized path trees, bash warnings and output metadata, and proposed/applied edit or overwrite diffs with compact context, line gutters, full-row backgrounds, and word-level emphasis. Large inputs fall back to escaped plain text with an explicit notice. The same layer renders in-memory Activity call details. Rich call data is retained only in current-session renderer/activity state; durable traces keep their existing bounded confidentiality projection.
|
|
10
|
+
|
|
11
|
+
Fabric keeps its orchestration-specific behavior around those previews. Multi-call rows remain compact, live write composition and phase/call progress remain visible, and agent/actor audits can expose their current child tools directly in the parent card with owner, runner, and run metadata. One execution-wide debounce coalesces parallel nested-call updates before they reach Pi. Long ANSI rows are bounded without clearing the enclosing tool background, and the widget lease plus result-to-source row transfer continue stabilizing completion height. The highlighter initializes lazily and falls back to plain text until ready. Collapsed previews use the configured expand keybinding (for example `Ctrl-O`).
|
|
12
|
+
|
|
13
|
+
## Activity surface
|
|
14
|
+
|
|
15
|
+
Fabric owns a general-purpose, theme-aware activity surface for any agent setup:
|
|
16
|
+
|
|
17
|
+
- A compact widget above the chat (like `pi-supervisor`) follows the current phase in its header and lists active/completed one-shot agents plus active actor workers. Their recent or running child tools are nested beneath the owner with compact edit/write change lines and owner metadata. Extensions, custom items, and shared tasks/state remain summarized by the header or available in the dashboard instead of occupying widget rows. The widget retains completed agent rows and a per-run high-water height so tool finalization does not pull the chat upward, resets that lease when a newer run starts, and keeps the latest completed summary visible until that newer run replaces it. When a collapsed Fabric result becomes shorter at completion, the card reveals a bounded number of otherwise-hidden TypeScript source lines to replace those rows without blank padding; any residual deficit shrinks naturally.
|
|
18
|
+
- `/fabric` (or `/fabric dashboard`) opens a responsive interactive overlay with two top-level views: **Activity** and **Topology**. Topology contains **Run** and **Project mesh** child views. `1` opens Activity, `2` opens Topology on Run, `3` opens Topology on Project mesh, and `r` toggles Activity and Topology · Run.
|
|
19
|
+
- **Activity** places the workflow phase sidebar on the left and the selected phase's activity on the right. The user-facing Pi agent is always present as **Main**, including when no Fabric child exists and regardless of the status filter, so it remains an actionable queue target. The right pane orders entities under type headings for agents, actors, tools, extensions, tasks, custom items, and shared state, with a blank row between groups. Selectable and rendered group order are identical, while one-shot agents remain stable in creation order; attention-priority work keeps default focus instead of Main stealing it.
|
|
20
|
+
- **Topology · Run** is rooted at **Main**, even before a retained run exists, followed by the full-width, phase-grouped one-shot agent hierarchy for the selected run. It connects recursive children to their parents and keeps completed recursive leaves from a bounded in-memory status snapshot after nested run directories are cleaned, until the parent is cleaned up or the Fabric session shuts down. Large runs stay centered on the selected agent and replace omitted regions with directional summaries of hidden, active, blocked, and failed nodes while preserving phase and ancestor context.
|
|
21
|
+
- **Topology · Project mesh** is independent of retained run selection. It uses selectable **Main** (with the exact session identity used for routing) as the live project root, then maps persistent actors and their host-event/delivery configuration, transient agents observed in mesh traffic, project topics and subscriber edges, shared-state ownership, and normalized recent event routes. Route rows aggregate repeated traffic by source, target, topic, and kind; this is a bounded live window, not an audit-history replacement. The two-row recent mesh-event feed remains reserved above the footer, including when it has fewer events, so switching views does not collapse the dashboard. Global actor templates are intentionally absent because they are not live project nodes. Topics and routes are selectable and have read-only detail views; actors retain their normal controls.
|
|
22
|
+
- **Inspection and control** are shared across views. Agent detail includes Markdown-rendered task/results, highlighted YAML values, model, current tool, usage, worktree, and attach metadata. Tool-call details show highlighted command/file/edit inputs and Markdown or YAML outputs; persisted bash calls retain their command text. Space peeks at an agent or actor transcript and `t` toggles summary/transcript detail. The initial bounded tail is paged directly from Pi RPC, Pi session, and Claude stream-JSON logs; `k`/Up at the loaded top fetches one older page, `g` loads and jumps to the true beginning, and `G` jumps to the growing tail and resumes follow mode. Assistant text uses native Markdown, while tool calls retain structured arguments/results for the same read/bash/search/edit/write previews used by the parent card. Credential/token redaction applies before rendering. Main and active one-shot agents can be steered or queued a follow-up; one-shot agents can also be safely stopped. Persistent actors accept `s` mailbox messages and expose model (`m`), thinking (`e`), host events (`v`), instructions (`i`), mailbox clearing (`c`), and export (`x`) where configured. Observed remote mesh agents accept best-effort `s`/`u` routing by exact identity. Global templates remain available from Activity for import (`p`), instruction editing (`i`), and deletion (`d`).
|
|
23
|
+
- `/fabric settings` opens an inline settings view that mirrors Pi core's `/settings` (top and bottom borders, fuzzy search, section submenus) and writes changes to `fabric.json`. Trusted projects write to `<project>/.pi/fabric.json`; untrusted sessions write to the global `~/.pi/agent/fabric.json`. Full code mode, capture, executor, approvals, and UI changes apply immediately; mesh, subagent, and MCP changes persist and take effect on the next `/fabric reload`. The UI section includes a default-on **Nested tool calls** toggle and a global **Nested tool debounce** (`0`/Off through `1000ms`, default `100ms`); both apply immediately. The Subagents section selects the default runner and keeps independent Pi and runtime-enumerated Claude model pickers. List editors for `subagents.defaultTools` and `capture.keepVisible` toggle known tools on and off; `keepVisible` candidates include `fabric_exec` plus every captured extension tool.
|
|
24
|
+
|
|
25
|
+
### Keybindings
|
|
26
|
+
|
|
27
|
+
- `1` opens Activity; `2` and `3` enter the Topology parent on Run and Project mesh respectively. `r` toggles Activity and Topology · Run. Inside Topology, `←`/`→`, `h`/`l`, or Tab switches its Run/Project mesh child view; in Activity those keys switch panes. `↑`/`↓` or `j`/`k` select, `g`/`G` jump to the first/last selectable node, Enter focuses or inspects, `f` cycles status filters, `[`/`]` move through retained runs in Activity or Run topology, and `?` opens contextual help.
|
|
28
|
+
- On Main, `s` sends a user-authored message/steer and `u` queues a user-authored follow-up through Pi's host queue.
|
|
29
|
+
- On agents and actors, Space peeks at the transcript and `t` toggles transcript/summary detail; inside a transcript, `g` loads/jumps to the true top and `G` jumps to the bottom and follows new output. On active one-shot agents, `s` opens a steer editor, `u` queues a follow-up, and pressing `x` twice stops the run. Observed remote mesh agents expose best-effort `s`/`u` delivery.
|
|
30
|
+
- On actors, `s` queues a serial mailbox message, `m` changes the model, `e` thinking, `v` host events, and `i` instructions; `x` exports to global, `p` imports a global template, `d` deletes one, and Esc backs out or closes.
|
|
31
|
+
|
|
32
|
+
## Data-driven activity
|
|
33
|
+
|
|
34
|
+
The surface is data-driven. Fabric automatically instruments nested provider calls, subagents, persistent actors, and task-shaped mesh entries. A workflow can add domain-specific labels and arbitrary progress without adding extension UI code:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
await workflow.configure({ name: "Release train", description: "Build, verify, and publish" });
|
|
38
|
+
await phase("Build", { total: packages.length });
|
|
39
|
+
await workflow.item({
|
|
40
|
+
id: "docs",
|
|
41
|
+
label: "Documentation",
|
|
42
|
+
status: "running",
|
|
43
|
+
completed: 2,
|
|
44
|
+
total: 5,
|
|
45
|
+
});
|
|
46
|
+
await workflow.event({ message: "Canary passed", level: "success" });
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
External Fabric providers can emit structured `context.activity()` updates for an entity, progress message, or metrics. This keeps the TUI generic while allowing a virtual provider to expose richer live state.
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
async invoke(actionName, args, context) {
|
|
53
|
+
context.activity?.({ type: "entity", id: job.id, kind: "custom", name: job.name });
|
|
54
|
+
context.activity?.({ type: "progress", message: "Indexing package 3/12" });
|
|
55
|
+
context.activity?.({ type: "metrics", tokens: 4200, toolCalls: 9 });
|
|
56
|
+
return job.result;
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Commands
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
/fabric status
|
|
64
|
+
/fabric dashboard
|
|
65
|
+
/fabric settings
|
|
66
|
+
/fabric reload
|
|
67
|
+
/fabric providers
|
|
68
|
+
/fabric captured [query]
|
|
69
|
+
/fabric agents
|
|
70
|
+
/fabric actors
|
|
71
|
+
/fabric messages <actor-id>
|
|
72
|
+
/fabric attach <subagent-id>
|
|
73
|
+
/fabric stop <actor-or-subagent-id>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Actor slash commands mirror the [global template API](agents.md#global-actor-templates): `/fabric global` lists templates, `/fabric import <name> [as <new>]` stamps one into the project, and `/fabric export <id> [--overwrite]` promotes a project actor. `/fabric log <id>` previews an actor or run transcript; `/fabric export-log <id> [path]` writes the raw `session.jsonl` plus retained `runs/` to disk.
|
|
77
|
+
|
|
78
|
+
## Headless focused agents
|
|
79
|
+
|
|
80
|
+
Pi already runs one-shot, non-interactive agents with `pi -p` (`--print`), and it reads piped stdin as part of the prompt — so a focused agent composes with pipes, cron, git hooks, and CI like a Unix program, with no wrapper needed:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
git diff | pi -p --no-session -t read,grep "Review this diff for concrete defects."
|
|
84
|
+
pi -p --no-session --mode json -e <path-to-pi-fabric> "Map the persistence layer."
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`--no-session` keeps the run ephemeral, `-t` restricts the tool allowlist, `--mode json` emits a structured event stream for scripting (`| jq`), and `-e <pi-fabric>` loads Fabric so the agent can use `fabric_exec`. The process exits non-zero on failure. See `pi --help` for the full flag list.
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# Memory & Recall
|
|
2
|
+
|
|
3
|
+
Pi Fabric's `memory` provider searches Pi session JSONL files. Session JSONL is
|
|
4
|
+
the source of truth; the memory index is derived, disposable state.
|
|
5
|
+
|
|
6
|
+
The index uses structural extraction only. It does not classify goals,
|
|
7
|
+
preferences, errors, or other prose concepts with regexes. Roles, tool names,
|
|
8
|
+
timestamps, entry IDs, operation addresses, tool errors, and tool argument
|
|
9
|
+
paths come from typed session fields.
|
|
10
|
+
|
|
11
|
+
## Active branches
|
|
12
|
+
|
|
13
|
+
`memory.recall`, `memory.sessions`, and `memory.expand` accept
|
|
14
|
+
`branches: "active" | "all"`. The default is `"active"` for every scope,
|
|
15
|
+
including `project`, `global`, and explicit `session:<id-or-path>` scopes.
|
|
16
|
+
Only records carried on each session's active parent-linked path contribute to
|
|
17
|
+
hot text, cold vocabulary, structural addresses, segments, and entry counts.
|
|
18
|
+
An abandoned sibling therefore cannot match by default. `branches: "all"` is
|
|
19
|
+
an explicit request to include every branch; responses, segments, cold
|
|
20
|
+
pointers, and session rows identify their branch mode.
|
|
21
|
+
|
|
22
|
+
For the current live session, Fabric calls the extension context's live
|
|
23
|
+
`SessionManager.getBranch()` and `getLeafId()` getters for every memory action.
|
|
24
|
+
This observes `/tree` navigation even when no new record has been appended. For
|
|
25
|
+
another persisted session, Fabric matches Pi 0.80.6's persisted semantics: the
|
|
26
|
+
last persisted non-header entry is the leaf, duplicate IDs resolve to the last
|
|
27
|
+
record in the ID map, and `parentId` links are followed from that leaf to a
|
|
28
|
+
root. Append order is never treated as one transcript. A parent cycle is
|
|
29
|
+
stopped defensively and makes coverage incomplete with
|
|
30
|
+
`invalid_parent_graph`.
|
|
31
|
+
|
|
32
|
+
Each derived record carries `branches` and a SHA-256 `lineageFingerprint` over
|
|
33
|
+
the selected branch mode, leaf, and active path IDs. Active and all caches have
|
|
34
|
+
separate filenames. Cache policy includes branch mode, lineage fingerprint,
|
|
35
|
+
and privacy settings, so navigation, append, or policy changes rebuild the
|
|
36
|
+
relevant derived record without contaminating the other mode. `sourceHash`
|
|
37
|
+
continues to cover the complete JSONL source, including off-lineage records.
|
|
38
|
+
|
|
39
|
+
## Cache V5
|
|
40
|
+
|
|
41
|
+
Cache records use `cacheVersion: 5`. Older or malformed records are removed and
|
|
42
|
+
rebuilt from source; they are never migrated or interpreted as V5. Refresh also
|
|
43
|
+
removes orphan records, records whose encoded cache path does not match their
|
|
44
|
+
source identity, and records for deleted source sessions.
|
|
45
|
+
|
|
46
|
+
Every cache record contains the exact session file path, branch mode,
|
|
47
|
+
lineage fingerprint, privacy policy, and a SHA-256 `sourceHash`, in addition to
|
|
48
|
+
source mtime and size. A same-size rewrite with a preserved mtime therefore
|
|
49
|
+
invalidates the record. Cache directories and files are created with `0700`
|
|
50
|
+
and `0600` permissions on a best-effort basis.
|
|
51
|
+
|
|
52
|
+
A hot shard contains bounded normalized entry text plus `indexCoverage`. A cold
|
|
53
|
+
digest contains:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
{
|
|
57
|
+
cacheVersion: 5,
|
|
58
|
+
kind: "digest",
|
|
59
|
+
sessionId, file, cwd,
|
|
60
|
+
mtime, size, sourceHash,
|
|
61
|
+
branches, lineageFingerprint, policy,
|
|
62
|
+
firstTs, lastTs, entryCount,
|
|
63
|
+
filesTouched, toolHistogram, errorCount,
|
|
64
|
+
vocabulary, // sorted unique canonical strings; no posting lists
|
|
65
|
+
addresses, // structural entry/operation identities, stored separately
|
|
66
|
+
indexCoverage,
|
|
67
|
+
cacheBytes, cacheSourceRatio
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Cold vocabulary maps an exact lexical term only to the containing session. It
|
|
72
|
+
does not retain a per-term list of entry indices. Consequently a cold result is
|
|
73
|
+
a session pointer with exact `sessionFile` and `sourceHash`, never an inferred
|
|
74
|
+
entry range. Exact entry IDs, indices, and Fabric operation addresses are
|
|
75
|
+
returned after explicit hydration.
|
|
76
|
+
|
|
77
|
+
`maxColdVocabularyBytes` bounds vocabulary construction for each session and
|
|
78
|
+
`maxColdCacheBytes` is a hard per-session persisted-cache bound. If either cap
|
|
79
|
+
is reached, `indexCoverage.complete` is false and contains an explicit reason.
|
|
80
|
+
Structural addresses or vocabulary may be retained only as exact prefixes when
|
|
81
|
+
the cache-size cap requires it; this is always reported as
|
|
82
|
+
`max_cold_cache_bytes`, never silently treated as complete. `cacheSourceRatio`
|
|
83
|
+
reports persisted cache bytes divided by source bytes.
|
|
84
|
+
|
|
85
|
+
## Exact lexical queries
|
|
86
|
+
|
|
87
|
+
`queryMode` is explicit:
|
|
88
|
+
|
|
89
|
+
- `"literal"` is the default;
|
|
90
|
+
- `"regex"` is opt-in.
|
|
91
|
+
|
|
92
|
+
Literal mode never inspects punctuation to guess whether input looks like a
|
|
93
|
+
regular expression and never compiles the input with `RegExp`. A path such as
|
|
94
|
+
`src/foo.ts` is therefore literal input.
|
|
95
|
+
|
|
96
|
+
`tokenize.ts` is the single canonical tokenizer used for literal queries, hot
|
|
97
|
+
BM25 scoring, and cold vocabulary creation. It applies Unicode NFKC
|
|
98
|
+
normalization, extracts Unicode letters, numbers, and underscores, then
|
|
99
|
+
lowercases. Literal terms use exact canonical-token equality. Matching is
|
|
100
|
+
lexical OR across the unique query terms; there is no stemming, synonym
|
|
101
|
+
expansion, phrase inference, or semantic regex classification.
|
|
102
|
+
|
|
103
|
+
For a cold session whose coverage is complete, every unique canonical token in
|
|
104
|
+
normalized source text occurs exactly once in the sorted vocabulary. Rare terms
|
|
105
|
+
remain exactly discoverable as long as the configured vocabulary and cache
|
|
106
|
+
bounds are not exceeded. If a bound is exceeded, an empty result is explicitly
|
|
107
|
+
non-authoritative.
|
|
108
|
+
|
|
109
|
+
Hot text is limited by Unicode scalar count, not raw UTF-16 code units. The cut
|
|
110
|
+
therefore cannot split a surrogate pair and always remains valid UTF-8. Because
|
|
111
|
+
hot shards do not retain a separate complete tail vocabulary, truncating any
|
|
112
|
+
normalized entry sets shard `indexCoverage.complete: false` with reason
|
|
113
|
+
`max_entry_chars`. A token occurring only after the cut cannot produce an
|
|
114
|
+
authoritative no-match; recall says `No indexed matches` and includes that
|
|
115
|
+
reason. Expansion still re-reads the complete source record.
|
|
116
|
+
|
|
117
|
+
## Bounded regular expressions
|
|
118
|
+
|
|
119
|
+
Regex mode runs JavaScript regex only in a disposable worker thread. The host
|
|
120
|
+
never evaluates an untrusted pattern. The worker is forcibly terminated at the
|
|
121
|
+
hard timeout, so catastrophic backtracking cannot continue on the host thread.
|
|
122
|
+
Regex execution is bounded by:
|
|
123
|
+
|
|
124
|
+
- UTF-8 pattern bytes;
|
|
125
|
+
- haystack item count;
|
|
126
|
+
- aggregate UTF-8 haystack bytes;
|
|
127
|
+
- wall-clock worker timeout.
|
|
128
|
+
|
|
129
|
+
Hot haystacks are normalized entry text. Cold haystacks are individual bounded
|
|
130
|
+
canonical vocabulary terms, not transcript prose. Invalid patterns, oversized
|
|
131
|
+
patterns, haystack truncation, worker failures, and timeouts return structured
|
|
132
|
+
query coverage. A timeout, for example, returns
|
|
133
|
+
`coverage.complete: false`, reason `regex_timeout`, and a structured
|
|
134
|
+
`coverage.error`. No incomplete regex result is presented as an authoritative
|
|
135
|
+
no-match.
|
|
136
|
+
|
|
137
|
+
## Tiers, refresh, and work budgets
|
|
138
|
+
|
|
139
|
+
The `memory.hotSessions` most recently modified sessions are hot. Older sessions
|
|
140
|
+
are cold. A session crossing the boundary loses its old derived tier record
|
|
141
|
+
after the replacement is built. Explicit hydration re-reads source without
|
|
142
|
+
promoting a cold session.
|
|
143
|
+
|
|
144
|
+
Cache synchronization is bounded by session count and aggregate source bytes.
|
|
145
|
+
Cache cleanup is bounded by inspected cache files and aggregate cache bytes
|
|
146
|
+
(the byte budget is shared with `maxSyncSourceBytes`). Reaching a work budget stops
|
|
147
|
+
additional indexing and sets `coverage.complete: false`; all eligible sessions
|
|
148
|
+
remain counted. There is no unbounded background job or database dependency.
|
|
149
|
+
|
|
150
|
+
Recall discovers all eligible sessions for query and no-query browse modes.
|
|
151
|
+
`memory.maxSessions` limits session listing only. Search materialization has
|
|
152
|
+
explicit deterministic per-call budgets: 50,000 filtered hot entry candidates,
|
|
153
|
+
10,000 cold digest candidates, and 10,000 grouped result items. Reaching one
|
|
154
|
+
sets coverage incomplete with `candidate_entry_budget`,
|
|
155
|
+
`candidate_digest_budget`, or `candidate_item_budget`. Totals then describe the
|
|
156
|
+
retained deterministic candidate set, not unknown omitted candidates. Coverage
|
|
157
|
+
reports:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
coverage: {
|
|
161
|
+
complete: boolean,
|
|
162
|
+
indexedSessions: number,
|
|
163
|
+
eligibleSessions: number,
|
|
164
|
+
staleSessions: number,
|
|
165
|
+
incompleteSessions: number,
|
|
166
|
+
reasons: string[],
|
|
167
|
+
error?: { code: string, message: string }
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`No matches` is authoritative only when both cache/index coverage and query
|
|
172
|
+
execution coverage are complete. Otherwise the response says
|
|
173
|
+
`No indexed matches` and reports reasons such as source unavailability,
|
|
174
|
+
`max_entry_chars`, duplicate identities, vocabulary/cache caps, candidate or
|
|
175
|
+
synchronization budgets, or regex limits.
|
|
176
|
+
|
|
177
|
+
## Scopes
|
|
178
|
+
|
|
179
|
+
| Scope | Meaning |
|
|
180
|
+
| --- | --- |
|
|
181
|
+
| `session` | Current session, or newest session for the current cwd. |
|
|
182
|
+
| `project` | Sessions in the current cwd's Pi session directory. |
|
|
183
|
+
| `global` | Sessions under the agent directory. This is explicit, never the default. |
|
|
184
|
+
| `session:<id-or-path>` | One source session, explicitly hydrated without promotion. |
|
|
185
|
+
|
|
186
|
+
Duplicate session IDs are ambiguous. `session:<id>` and `memory.expand` refuse
|
|
187
|
+
an ambiguous ID with `ambiguous_session` and list candidate paths. Use the exact
|
|
188
|
+
session file path from the cold pointer. Duplicate normalized entry IDs and
|
|
189
|
+
operation addresses also make index coverage incomplete (`duplicate_entry_id`
|
|
190
|
+
or `duplicate_operation_address`). Stable-address expansion requires exactly
|
|
191
|
+
one record: zero matches return `address_not_found`, and multiple matches return
|
|
192
|
+
`ambiguous_address`; neither case returns source records.
|
|
193
|
+
|
|
194
|
+
## Pointers, hydration, and expansion
|
|
195
|
+
|
|
196
|
+
A cold result has session identity only:
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
{
|
|
200
|
+
tier: "cold",
|
|
201
|
+
sessionId,
|
|
202
|
+
sessionFile,
|
|
203
|
+
sourceHash,
|
|
204
|
+
branches,
|
|
205
|
+
lineageFingerprint,
|
|
206
|
+
matchedTerms
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
It does not collapse disjoint term occurrences into a misleading inclusive
|
|
211
|
+
range and does not truncate a hidden list of exact matches. Hydrate the exact
|
|
212
|
+
path and pass the pointer hash:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
memory.recall({
|
|
216
|
+
scope: `session:${pointer.sessionFile}`,
|
|
217
|
+
branches: pointer.branches,
|
|
218
|
+
expectedSourceHash: pointer.sourceHash,
|
|
219
|
+
expectedLineageFingerprint: pointer.lineageFingerprint,
|
|
220
|
+
query: "rare_token"
|
|
221
|
+
})
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Hydrated/hot segments include `exactMatches` with exact normalized entry index,
|
|
225
|
+
entry ID, and operation address. Recall first groups all retained hot matches
|
|
226
|
+
into entry segments, combines those segments with cold pointers, globally ranks
|
|
227
|
+
the combined item stream, and only then applies `page`/`pageSize`. Thus several
|
|
228
|
+
matches in one segment consume one item, not several pages. Responses expose
|
|
229
|
+
stable `totalItems`, `totalMatches`, and `hasNext` for that retained stream.
|
|
230
|
+
No-query browse follows the same pagination path and has no earlier 25-entry or
|
|
231
|
+
`maxSessions` cap. An optional inclusive `entryRange` may bound
|
|
232
|
+
hydration, but both endpoints must be valid session indices. Out-of-range or
|
|
233
|
+
negative addresses return structured `index_out_of_bounds` errors rather than
|
|
234
|
+
being clamped or silently dropped.
|
|
235
|
+
|
|
236
|
+
`memory.expand` re-reads full, untruncated source text and accepts indices,
|
|
237
|
+
stable entry IDs, operation addresses, or an inclusive range:
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
memory.expand({
|
|
241
|
+
session: pointer.sessionFile,
|
|
242
|
+
branches: pointer.branches,
|
|
243
|
+
expectedSourceHash: pointer.sourceHash,
|
|
244
|
+
expectedLineageFingerprint: pointer.lineageFingerprint,
|
|
245
|
+
indices: [12, 14]
|
|
246
|
+
})
|
|
247
|
+
memory.expand({ session: pointer.sessionFile, entryIds: ["entry-uuid"] })
|
|
248
|
+
memory.expand({ session: pointer.sessionFile, operationAddresses: ["entry-uuid/7"] })
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Hydration and expansion compare `expectedSourceHash` with the current source
|
|
252
|
+
and `expectedLineageFingerprint` with the selected live or persisted lineage.
|
|
253
|
+
A rewrite, append, or active-leaf navigation returns structured
|
|
254
|
+
`stale_pointer` and no source content when it changes an expected binding.
|
|
255
|
+
Results include source hash, branch mode, and lineage fingerprint so callers
|
|
256
|
+
can retain pointer integrity. Under active mode an off-lineage stable address
|
|
257
|
+
returns `address_not_found`; the same address can be expanded only by an
|
|
258
|
+
explicit `branches: "all"` request.
|
|
259
|
+
|
|
260
|
+
A valid `FabricExecutionTraceV1` on an outer `fabric_exec` result emits one child
|
|
261
|
+
record per operation immediately after the outer normalized entry. Each child
|
|
262
|
+
keeps `parentEntryId`, `operationAddress`, exact `toolName`, `ref`, `provider`,
|
|
263
|
+
`action`, typed `filesTouched`, `outcome`, and a bounded structured `operation`
|
|
264
|
+
object. Expansion re-reads and re-normalizes source rather than reconstructing
|
|
265
|
+
operations from output prose.
|
|
266
|
+
|
|
267
|
+
Valid `FabricBranchSummaryDetailsV1` envelopes emit typed child records for
|
|
268
|
+
user, phase, and operation facts. Children preserve the original fact address,
|
|
269
|
+
ref/provider/action/tool/outcome/arguments and structurally derived paths, plus
|
|
270
|
+
`carrierEntryId`, `carrierParentId`, and `carrierFromId`. Operation facts expand
|
|
271
|
+
by their original operation address; user and phase facts use that address as
|
|
272
|
+
their stable entry ID. Repeated nested summaries are deduplicated by exact fact
|
|
273
|
+
address in source order, so the earliest carrier is deterministic. Addresses
|
|
274
|
+
must be unique within each consumed details envelope; a duplicate rejects that
|
|
275
|
+
envelope and marks coverage incomplete. Unknown or malformed details and all
|
|
276
|
+
branch-summary prose remain non-semantic.
|
|
277
|
+
|
|
278
|
+
## Local-cache privacy and deletion
|
|
279
|
+
|
|
280
|
+
The memory index is local derived state, not an encryption or semantic-privacy
|
|
281
|
+
boundary. Depending on configuration and tier, cache JSON can retain plaintext
|
|
282
|
+
lexical vocabulary, cwd and file paths, source pointers and hashes, structural
|
|
283
|
+
tool metadata, selected user/assistant/custom-message content, tool arguments,
|
|
284
|
+
and (with the default `indexToolOutput: true`) selected tool output. Cold
|
|
285
|
+
vocabulary alone can reveal exact words even though it has no posting lists.
|
|
286
|
+
Thinking text is excluded by default but is plaintext when explicitly enabled.
|
|
287
|
+
No secret scanning is attempted; privacy is structural inclusion or exclusion,
|
|
288
|
+
not regex classification.
|
|
289
|
+
|
|
290
|
+
Fabric requests `0700` for cache directories and `0600` for cache files, but
|
|
291
|
+
this is best effort and inherits the host filesystem, account, backup, and
|
|
292
|
+
administrative trust model. The cache is not encrypted. Project configuration
|
|
293
|
+
is read only for a project Pi has marked trusted; otherwise only global Fabric
|
|
294
|
+
configuration applies. A `global` memory search is an explicit scope and is
|
|
295
|
+
not the default, but global indexing still creates local derived records for
|
|
296
|
+
sessions selected by that explicit call.
|
|
297
|
+
|
|
298
|
+
Deleting a Pi session removes the source of truth. A later memory refresh
|
|
299
|
+
best-effort removes its orphaned cache records; delete the configured
|
|
300
|
+
`memory.indexDir` as well when immediate cache removal is required. Removing
|
|
301
|
+
that directory is safe because all records are disposable and rebuilt from
|
|
302
|
+
remaining session JSONL. Deleting caches does not delete source sessions,
|
|
303
|
+
filesystem backups, or copies outside the index directory.
|
|
304
|
+
|
|
305
|
+
## Configuration
|
|
306
|
+
|
|
307
|
+
```json
|
|
308
|
+
{
|
|
309
|
+
"memory": {
|
|
310
|
+
"enabled": true,
|
|
311
|
+
"indexDir": "~/.pi/agent/fabric/memory-index",
|
|
312
|
+
"maxSessions": 500,
|
|
313
|
+
"maxEntryChars": 2000,
|
|
314
|
+
"indexThinking": false,
|
|
315
|
+
"indexToolOutput": true,
|
|
316
|
+
"hotSessions": 50,
|
|
317
|
+
"maxColdVocabularyBytes": 524288,
|
|
318
|
+
"maxColdCacheBytes": 1048576,
|
|
319
|
+
"maxSyncSessions": 10000,
|
|
320
|
+
"maxSyncSourceBytes": 536870912,
|
|
321
|
+
"maxCacheCleanupFiles": 100000,
|
|
322
|
+
"regexMaxPatternBytes": 1024,
|
|
323
|
+
"regexMaxHaystackTerms": 20000,
|
|
324
|
+
"regexMaxHaystackBytes": 2097152,
|
|
325
|
+
"regexTimeoutMs": 250
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
- `maxSessions`: session-list discovery budget only; recall uses candidate and
|
|
331
|
+
indexing budgets instead.
|
|
332
|
+
- `maxEntryChars`: persisted hot entry-text Unicode-scalar limit; any cut marks
|
|
333
|
+
lexical coverage incomplete, while expand re-reads full source.
|
|
334
|
+
- `indexThinking`: assistant thinking blocks enter normalized text and lexical
|
|
335
|
+
vocabulary only when true. Default: false.
|
|
336
|
+
- `indexToolOutput`: tool-result bodies, bash output, and typed Fabric operation
|
|
337
|
+
results enter derived text only when true. Default: true, deliberately chosen
|
|
338
|
+
for coding recall. When false, typed tool name/ref/action, error/outcome, and
|
|
339
|
+
structurally extracted path metadata remain searchable; output bodies do not.
|
|
340
|
+
- `hotSessions`: globally newest sessions retaining hot shards.
|
|
341
|
+
- `maxColdVocabularyBytes`: per-session canonical vocabulary bound.
|
|
342
|
+
- `maxColdCacheBytes`: hard per-session cold cache-file bound.
|
|
343
|
+
- `maxSyncSessions` / `maxSyncSourceBytes`: synchronous indexing work budgets.
|
|
344
|
+
- `maxCacheCleanupFiles`: synchronous cache-file count budget; cleanup bytes use
|
|
345
|
+
`maxSyncSourceBytes`.
|
|
346
|
+
- `regexMaxPatternBytes`, `regexMaxHaystackTerms`,
|
|
347
|
+
`regexMaxHaystackBytes`, `regexTimeoutMs`: isolated regex execution bounds.
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Programmatic compaction
|
|
2
|
+
|
|
3
|
+
Programmatic compaction lets the model (or a skill, or a peer) **ask** the host
|
|
4
|
+
to compact its own context — or a running subagent's context — at a safe
|
|
5
|
+
boundary, instead of compaction being only a token-threshold reflex. It is the
|
|
6
|
+
distillation of two proven ideas into first-principles primitives native to
|
|
7
|
+
pi-fabric:
|
|
8
|
+
|
|
9
|
+
- **Harness enforcement** motivates a single gated channel from thought to
|
|
10
|
+
action, with the host — not the model — deciding when it is safe to act.
|
|
11
|
+
- **Deterministic compaction** avoids adding another model call to a context
|
|
12
|
+
transition and makes repeated results testable.
|
|
13
|
+
|
|
14
|
+
Both point at the same primitive: compaction should be a **deliberate,
|
|
15
|
+
labeled transition** of the agent's own context, requested by the model
|
|
16
|
+
(**advisory**) and committed by the host only at a safe boundary
|
|
17
|
+
(**committed**).
|
|
18
|
+
|
|
19
|
+
## Why advisory-then-committed
|
|
20
|
+
|
|
21
|
+
The model runs inside the context it would compact. If it could compact the
|
|
22
|
+
running context directly, it would race with its own in-flight turn: tool calls
|
|
23
|
+
mid-execution, partial plans, and unresolved steering. Pi Fabric avoids that
|
|
24
|
+
race with a typed, validated write path and an open status path.
|
|
25
|
+
|
|
26
|
+
Pi Fabric therefore exposes compaction as **two separable acts**:
|
|
27
|
+
|
|
28
|
+
1. **Advisory** — `compact.request` (host) or `agents.compact` (child) only
|
|
29
|
+
*records an intent*. It never touches the context. It is a write-risk,
|
|
30
|
+
schema-validated declaration: "I think this context should be
|
|
31
|
+
compacted, with these instructions, for this reason."
|
|
32
|
+
|
|
33
|
+
2. **Committed** — the host, at a boundary it knows to be safe, forwards the
|
|
34
|
+
intent to `ExtensionContext.compact()` (host) or to the child pi's `compact`
|
|
35
|
+
RPC frame (child). For the host, that boundary is `agent_settled`: the
|
|
36
|
+
handler awaits Pi's callback completion or error before Pi publishes its
|
|
37
|
+
public settled event. For a child, the worker waits for the child's own
|
|
38
|
+
`agent_settled`, then sends a correlated compact request and keeps the RPC
|
|
39
|
+
channel open until it observes both the response and `compaction_end`. It
|
|
40
|
+
never sends compact while the child turn is active.
|
|
41
|
+
|
|
42
|
+
There is exactly one write path from intent to action. The model cannot
|
|
43
|
+
compact the running context directly; it can only ask. The ask is a single
|
|
44
|
+
replaceable slot — a new request replaces the pending one, keeping the latest
|
|
45
|
+
instructions.
|
|
46
|
+
|
|
47
|
+
## First principles, mapped
|
|
48
|
+
|
|
49
|
+
| Principle | How programmatic compaction realizes it |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| The context is a cache, not the store. | Compaction is an explicit, labeled transition of the cache, not a silent eviction. The intent and the last commit are recorded outside the context (`status()`) and survive it. |
|
|
52
|
+
| Derived views are pure functions of the log. | `CompactStatus` is a pure snapshot of the controller's recorded intents and commits — never of the compacted context itself. |
|
|
53
|
+
| Enforcement in the harness beats discipline in the prompt. | The model only *requests*; the host *commits* at `agent_settled`. The gate is in the harness (`maybeCommit` + the `agent_settled` handler), not in the prompt. |
|
|
54
|
+
| Compaction is a deliberate, advisory-then-committed act at a model-chosen task boundary. | The model chooses *when to ask* and *with what instructions*; the host chooses *when to commit* (a safe boundary). A token-threshold reflex still exists in pi core; this adds a deliberate path on top of it. |
|
|
55
|
+
|
|
56
|
+
## API surface
|
|
57
|
+
|
|
58
|
+
### Host session — the `compact` provider
|
|
59
|
+
|
|
60
|
+
Always available (no config guard). Exposed through `fabric_exec` as
|
|
61
|
+
`compact.request`, `compact.status`, `compact.cancel`.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// Record an advisory intent. Replaces any pending one. Returns immediately;
|
|
65
|
+
// the host commits it at the next agent_settled boundary.
|
|
66
|
+
await compact.request({
|
|
67
|
+
reason: "the file map and the failing test are the only live state",
|
|
68
|
+
instructions: "Keep the failing test name and the file map; drop the rest.",
|
|
69
|
+
preserve: ["Auth regression is still open", "tests/auth.test.ts"], // optional
|
|
70
|
+
requestedBy: "model", // optional; default "model"
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Read the pending intent and the last committed/failed compaction info.
|
|
74
|
+
const status = await compact.status();
|
|
75
|
+
// { pending?: { reason?, instructions?, preserve?, requestedBy, requestedAt },
|
|
76
|
+
// last?: { at, requestedBy, status: "committed"|"failed",
|
|
77
|
+
// summary?, tokensBefore?, estimatedTokensAfter?, error? } }
|
|
78
|
+
|
|
79
|
+
// Clear a pending intent before the host commits it.
|
|
80
|
+
await compact.cancel();
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Risk classes: `request` is `write` (it mutates host session state); `status`
|
|
84
|
+
and `cancel` are `read`.
|
|
85
|
+
|
|
86
|
+
`instructions` alone is forwarded as ordinary Pi `customInstructions`, so manual
|
|
87
|
+
`/compact` text and programmatic requests have the same Fabric rendering. When
|
|
88
|
+
`preserve` is present, the controller encodes `{version: 1, instructions?,
|
|
89
|
+
preserve}` behind an exact versioned prefix plus JSON. The compaction and branch
|
|
90
|
+
hooks strictly decode that shape and render valid bounded values under
|
|
91
|
+
`[Compaction Request]`. On tree navigation, Pi's explicit
|
|
92
|
+
`replaceInstructions: true` mode is delegated to Pi/default summarization:
|
|
93
|
+
Fabric cannot execute an arbitrary replacement summarizer prompt and therefore
|
|
94
|
+
produces no typed Fabric branch details in that mode.
|
|
95
|
+
|
|
96
|
+
The prefix is reserved: malformed JSON/scalars, duplicate decoded protocol
|
|
97
|
+
keys (including escaped aliases), unknown fields or versions, invalid types,
|
|
98
|
+
unpaired UTF-16 surrogates, excessive structure, or exceeded limits return a
|
|
99
|
+
structured decode error and cancel instead of falling back to prose. A bounded
|
|
100
|
+
structural parser performs these checks before canonicalization; it does not
|
|
101
|
+
recover protocol data with regex. The rejected payload is never rendered. A
|
|
102
|
+
context with UI/RPC notification support receives a bounded error. The exact
|
|
103
|
+
`__pi_vcc__` value retains compaction-routing precedence and has no special
|
|
104
|
+
effect on the tree hook.
|
|
105
|
+
|
|
106
|
+
`compact.request` uses a bounded TypeBox schema before argument mapping.
|
|
107
|
+
Instructions are limited to 8192 characters and 8192 UTF-8 bytes; `preserve` to
|
|
108
|
+
16 items; each item to 2048 characters and 2048 UTF-8 bytes; and the complete
|
|
109
|
+
encoded prefix-plus-JSON request to 16 KiB. The decoder checks aggregate source
|
|
110
|
+
bytes before structural parsing, validates duplicate keys/scalars/surrogate
|
|
111
|
+
pairing while parsing, and checks preserve count before iterating or
|
|
112
|
+
canonicalizing items. Ordinary manual/Pi instructions remain bounded explicit
|
|
113
|
+
text rather than typed protocol input.
|
|
114
|
+
|
|
115
|
+
#### Commit semantics
|
|
116
|
+
|
|
117
|
+
- `maybeCommit(context)` is awaited from the host's `agent_settled` handler —
|
|
118
|
+
never mid-turn, never while a turn is in flight. The returned Promise settles
|
|
119
|
+
on `onComplete`, `onError`, a synchronous startup throw, or a pre-start abort.
|
|
120
|
+
- It is a no-op when nothing is pending. Reentrant calls share the in-flight
|
|
121
|
+
Promise rather than starting a second compaction.
|
|
122
|
+
- A new `request()` while a commit is in flight is allowed: it replaces the
|
|
123
|
+
pending intent. The in-flight commit proceeds with the intent it captured; on
|
|
124
|
+
completion it clears *that* intent (by identity), leaving any newer intent for
|
|
125
|
+
the next settled boundary.
|
|
126
|
+
- On pi's `onComplete`: the intent is cleared and `last` records
|
|
127
|
+
`status: "committed"` with the summary and token counts.
|
|
128
|
+
- On pi's `onError` with `"Compaction cancelled"` or `"Already compacted"`:
|
|
129
|
+
the intent is cleared **quietly** — nothing to compact, no failure recorded.
|
|
130
|
+
- On any other error: the intent is cleared and `last` records
|
|
131
|
+
`status: "failed"` with the message. If `compact()` itself throws
|
|
132
|
+
synchronously, the same failure path applies.
|
|
133
|
+
|
|
134
|
+
### Subagent compaction — `agents.compact`
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
const handle = await agents.spawn({
|
|
138
|
+
task: "Audit auth flows.",
|
|
139
|
+
tools: ["read", "grep", "find", "ls"],
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Advisory: the worker queues this until the child is fully settled.
|
|
143
|
+
await agents.compact({ id: handle.id, instructions: "Keep the finding list." });
|
|
144
|
+
|
|
145
|
+
return await agents.wait({ id: handle.id });
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
- Appended to the same `<runDir>/steer.jsonl` channel as `agents.steer` — the
|
|
149
|
+
orchestrator (or any peer via the mesh relay) can request a child compaction
|
|
150
|
+
without stopping or respawning it, preserving the child's accumulated
|
|
151
|
+
context.
|
|
152
|
+
- The worker tails `steer.jsonl` but does not forward compact during an active
|
|
153
|
+
child turn. It waits for `agent_settled`, then sends
|
|
154
|
+
`{"id":"...","type":"compact","customInstructions":"..."}` (the
|
|
155
|
+
instructions field is omitted when absent). See pi's [RPC `compact`](https://github.com/earendil-works/pi-coding-agent/blob/main/docs/rpc.md).
|
|
156
|
+
- The worker correlates the compact response by `id`, observes the matching
|
|
157
|
+
compaction lifecycle through `compaction_end`, records queued/in-flight/
|
|
158
|
+
completed/failed status, and only then closes stdin for one-shot shutdown.
|
|
159
|
+
A rejected response, aborted compaction, or `compaction_end.errorMessage` is
|
|
160
|
+
recorded as failed without aborting the child turn.
|
|
161
|
+
- Multiple requests waiting before the boundary coalesce to one request with
|
|
162
|
+
the latest instructions. Requests arriving during an in-flight compaction
|
|
163
|
+
coalesce into one deterministic follow-on request before shutdown.
|
|
164
|
+
- **Claude-runner children are rejected** with a clear error: the official
|
|
165
|
+
Claude Code CLI exposes no compact RPC, so a fresh run is the only way to
|
|
166
|
+
reset a Claude child's context. Compaction is a Pi-runner primitive.
|
|
167
|
+
- Risk class: `agent`.
|
|
168
|
+
|
|
169
|
+
## Audit and observability
|
|
170
|
+
|
|
171
|
+
- **Activity surface**: `compact.request` and `agents.compact` emit
|
|
172
|
+
`context.activity` updates (entity + progress) inside the `fabric_exec` call
|
|
173
|
+
that issued them, following the existing provider pattern. Host commits and
|
|
174
|
+
child enqueues are therefore visible in the dashboard and widget.
|
|
175
|
+
- **Mesh**: when the mesh is enabled, the host controller publishes best-effort
|
|
176
|
+
events to the durable `fabric.compact` topic on each transition:
|
|
177
|
+
`kind: "requested"` when an intent is recorded, and
|
|
178
|
+
`kind: "committed" | "failed"` when the host commits. Pi's benign
|
|
179
|
+
`"Compaction cancelled"` / `"Already compacted"` outcomes clear quietly and
|
|
180
|
+
do not publish a commit event. Other
|
|
181
|
+
Fabric participants (persistent actors, peer sessions) can subscribe to
|
|
182
|
+
observe compaction transitions. Activity-only sessions (mesh disabled)
|
|
183
|
+
silently skip this.
|
|
184
|
+
- **Status query**: `compact.status()` is the context-independent, in-memory
|
|
185
|
+
record of the pending intent and the last commit for the current initialized
|
|
186
|
+
extension session. It survives compaction itself, but not extension reload,
|
|
187
|
+
session replacement, process restart, or shutdown.
|
|
188
|
+
|
|
189
|
+
## Configuration
|
|
190
|
+
|
|
191
|
+
None required. Programmatic compaction is a first-principles primitive and is
|
|
192
|
+
always available. There is intentionally no `compact` config block: the model
|
|
193
|
+
decides when and how to ask; the host decides when to commit; neither needs
|
|
194
|
+
configuration to be safe.
|
|
195
|
+
|
|
196
|
+
## Files
|
|
197
|
+
|
|
198
|
+
| File | Role |
|
|
199
|
+
| --- | --- |
|
|
200
|
+
| `src/core/compact-controller.ts` | Pending-intent controller: `request`, `cancel`, `status`, `maybeCommit`. Single replaceable slot; typed preserve encoding; in-flight guard; quiet-clear on cancelled/already-compacted. |
|
|
201
|
+
| `src/providers/compact-provider.ts` | Fabric provider exposing bounded TypeBox-validated `request` (write, including optional `preserve: string[]`), `status` (read), `cancel` (read). Always registered; activity audit. |
|
|
202
|
+
| `src/fabric-state.ts` | Constructs the controller with mesh-publish hooks; registers the provider; resets on re-init/shutdown. |
|
|
203
|
+
| `src/index.ts` | Invokes `state.compact.maybeCommit(context)` in the existing `agent_settled` handler. |
|
|
204
|
+
| `src/subagents/types.ts` | `SubagentSteerEntry["type"]` extended with `"compact"`; optional `instructions` field. |
|
|
205
|
+
| `src/subagents/manager.ts` | `compact(id, instructions?)` appends a compact entry through the steer channel; rejects Claude-runner children. |
|
|
206
|
+
| `src/worker.ts` | Feeds compact controls into the child boundary coordinator and observes Pi RPC lifecycle events. |
|
|
207
|
+
| `src/subagents/compact-control.ts` | Coalesces child requests, waits for `agent_settled`, correlates compact response + `compaction_end`, records outcome, and gates one-shot stdin close. |
|
|
208
|
+
| `src/providers/agents-provider.ts` | `agents.compact({id, instructions?})` action (risk: agent) with activity audit. |
|