@yaag/extension 0.10.0 → 0.11.0
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/docs/authoring.md +20 -0
- package/docs/examples/07-fork.ts +29 -0
- package/docs/examples.md +15 -0
- package/docs/troubleshooting.md +11 -0
- package/package.json +4 -4
- package/src/docs/docs-root.ts +1 -0
- package/src/record/run-summary-parse.ts +26 -0
- package/src/view/run-details.ts +9 -1
package/docs/authoring.md
CHANGED
|
@@ -114,6 +114,26 @@ program again and replays the recorded Asks. A resume needs the program too,
|
|
|
114
114
|
because a Cassette holds the history of a Run and never the program. See
|
|
115
115
|
[Examples](examples.md#05--record-and-resume).
|
|
116
116
|
|
|
117
|
+
## Fork and compaction
|
|
118
|
+
|
|
119
|
+
`handle.compact()` replaces an Agent's context with a summary of it, and
|
|
120
|
+
reports the context size before and after. `handle.fork()` spawns a new Agent
|
|
121
|
+
from a copy of this Agent's conversation: use it when one expensive Agent
|
|
122
|
+
builds context that several cheap Agents need. Both need a settled Ask
|
|
123
|
+
boundary, so a call during an Ask fails with `COMPACT_DURING_ASK` or
|
|
124
|
+
`FORK_DURING_ASK`.
|
|
125
|
+
|
|
126
|
+
- An Agent that exited cleanly stays forkable: build context, close the Agent,
|
|
127
|
+
then fork its final state. An Agent killed during an Ask fails `FORK_REFUSED`.
|
|
128
|
+
- The fork inherits every spawn option of its source, including the concrete
|
|
129
|
+
model it settled on, but not its name. Pass any spawn option to change that.
|
|
130
|
+
The source becomes the fork's parent; pass `parent` to place it elsewhere.
|
|
131
|
+
- `fork({ compact: true })` compacts the copy before you get the Handle; a
|
|
132
|
+
string becomes the compaction instructions.
|
|
133
|
+
- A fork of a Worktree Agent gets its own worktree, branched from the source's
|
|
134
|
+
branch, so only committed work transfers. Forking needs a pi that supports
|
|
135
|
+
`pi --fork`. See [Examples](examples.md#07--fork-and-compaction).
|
|
136
|
+
|
|
117
137
|
## Lineage
|
|
118
138
|
|
|
119
139
|
`parent` names the Agent a spawn belongs under. It is data only: it sets the
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build context once, then work many times. The builder reads the repository;
|
|
3
|
+
* each worker forks the builder's conversation, so it starts with that context
|
|
4
|
+
* without paying to read the repository again.
|
|
5
|
+
*/
|
|
6
|
+
import { defineRun, prompt } from "@yaag/runtime";
|
|
7
|
+
|
|
8
|
+
const TOPICS = ["error handling", "testing", "naming"] as const;
|
|
9
|
+
|
|
10
|
+
export default defineRun({
|
|
11
|
+
name: "fork",
|
|
12
|
+
description: "Forks one context-building Agent into several cheap workers.",
|
|
13
|
+
async run(ctx) {
|
|
14
|
+
const builder = await ctx.spawn({ name: "builder" });
|
|
15
|
+
await builder.ask(prompt`
|
|
16
|
+
Read this repository and describe how it is organized. Report one
|
|
17
|
+
paragraph.
|
|
18
|
+
`);
|
|
19
|
+
const notes = await Promise.all(
|
|
20
|
+
TOPICS.map(async (topic) => {
|
|
21
|
+
// The fork inherits the builder's options and its whole conversation.
|
|
22
|
+
// `compact` summarizes that conversation before the worker starts.
|
|
23
|
+
const worker = await builder.fork({ name: topic.split(" ")[0], compact: true });
|
|
24
|
+
return worker.ask(prompt`From what you already read, report on ${topic}.`);
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
27
|
+
return notes.join("\n\n");
|
|
28
|
+
},
|
|
29
|
+
});
|
package/docs/examples.md
CHANGED
|
@@ -117,3 +117,18 @@ ever spawns anything itself.
|
|
|
117
117
|
|
|
118
118
|
Full file: [`examples/06-lineage.ts`](examples/06-lineage.ts). Run it with
|
|
119
119
|
`yaag run examples/06-lineage.ts`.
|
|
120
|
+
## 07 — fork and compaction
|
|
121
|
+
|
|
122
|
+
One Agent reads the repository. Each worker forks that Agent, so it inherits
|
|
123
|
+
the whole conversation instead of reading the repository again. `compact: true`
|
|
124
|
+
summarizes the inherited context before the worker starts.
|
|
125
|
+
|
|
126
|
+
<!-- embed: docs/examples/07-fork.ts -->
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
const worker = await builder.fork({ name: topic.split(" ")[0], compact: true });
|
|
130
|
+
return worker.ask(prompt`From what you already read, report on ${topic}.`);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Full file: [`examples/07-fork.ts`](examples/07-fork.ts). Run it with
|
|
134
|
+
`yaag run examples/07-fork.ts`.
|
package/docs/troubleshooting.md
CHANGED
|
@@ -103,6 +103,17 @@ Cause: `ASK_LIMIT` is a tripped soft budget (`maxTurns`, `maxToolCalls`,
|
|
|
103
103
|
Fix: raise the budget, or add a `wrapUpPrompt`. After `ASK_LIMIT` the Handle is
|
|
104
104
|
alive, so the program can ask again.
|
|
105
105
|
|
|
106
|
+
## A fork or a compaction was refused
|
|
107
|
+
|
|
108
|
+
Cause: `COMPACT_DURING_ASK` and `FORK_DURING_ASK` mean an Ask of that Agent was
|
|
109
|
+
still in flight. Both operations need a settled Ask boundary. `FORK_REFUSED`
|
|
110
|
+
means the Agent cannot be forked at all: it was killed during an Ask, so the
|
|
111
|
+
end of its session is undefined, or it holds no session file, as a
|
|
112
|
+
Cassette-playback Agent does. `COMPACT_FAILED` means pi refused the summary.
|
|
113
|
+
|
|
114
|
+
Fix: await the Ask before you fork or compact. For `FORK_REFUSED`, fork an
|
|
115
|
+
Agent that settled its last Ask, or spawn a fresh Agent instead.
|
|
116
|
+
|
|
106
117
|
## describe executes the module top level
|
|
107
118
|
|
|
108
119
|
<!-- quote: @yaag/cli/src/argv.ts -->
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yaag/extension",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@earendil-works/pi-tui": "^0.84.0",
|
|
28
|
-
"@yaag/cli": "0.
|
|
29
|
-
"@yaag/runtime": "0.
|
|
30
|
-
"@yaag/tui": "0.
|
|
28
|
+
"@yaag/cli": "0.11.0",
|
|
29
|
+
"@yaag/runtime": "0.11.0",
|
|
30
|
+
"@yaag/tui": "0.11.0",
|
|
31
31
|
"nanoid": "^6.0.1"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
package/src/docs/docs-root.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
AgentActivity,
|
|
3
3
|
AgentInfo,
|
|
4
4
|
AgentState,
|
|
5
|
+
CompactionInfo,
|
|
5
6
|
ModelFallbackInfo,
|
|
6
7
|
NodeInfo,
|
|
7
8
|
NodeState,
|
|
@@ -58,6 +59,7 @@ interface SummaryBase {
|
|
|
58
59
|
readonly durationMs: number;
|
|
59
60
|
readonly worstFrameGapMs: number;
|
|
60
61
|
readonly modelFallbacks: number;
|
|
62
|
+
readonly compactions: number;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
function parseBase(stored: Record<string, unknown>): SummaryBase | null {
|
|
@@ -93,6 +95,7 @@ function parseBase(stored: Record<string, unknown>): SummaryBase | null {
|
|
|
93
95
|
// Tolerant, unlike the strict fields above: a record written before the
|
|
94
96
|
// fallback counter existed must still load instead of disappearing.
|
|
95
97
|
modelFallbacks: typeof stored.modelFallbacks === "number" ? stored.modelFallbacks : 0,
|
|
98
|
+
compactions: typeof stored.compactions === "number" ? stored.compactions : 0,
|
|
96
99
|
};
|
|
97
100
|
}
|
|
98
101
|
|
|
@@ -157,6 +160,9 @@ function parseAgent(value: unknown): AgentInfo | null {
|
|
|
157
160
|
// Tolerant for the same reason as the Run-level counter.
|
|
158
161
|
modelFallbacksPruned:
|
|
159
162
|
typeof stored.modelFallbacksPruned === "number" ? stored.modelFallbacksPruned : 0,
|
|
163
|
+
// Tolerant as well: a record written before compaction carries neither field.
|
|
164
|
+
compactions: typeof stored.compactions === "number" ? stored.compactions : 0,
|
|
165
|
+
lastCompaction: parseCompaction(stored.lastCompaction),
|
|
160
166
|
};
|
|
161
167
|
if (state === "asking") {
|
|
162
168
|
if (typeof stored.askIndex !== "number") return null;
|
|
@@ -175,6 +181,26 @@ function parseAgent(value: unknown): AgentInfo | null {
|
|
|
175
181
|
return { ...base, state, askIndex: stored.askIndex, promptGist: stored.promptGist };
|
|
176
182
|
}
|
|
177
183
|
|
|
184
|
+
/** The newest compaction of one Agent, or null when the record holds none. */
|
|
185
|
+
function parseCompaction(value: unknown): CompactionInfo | null {
|
|
186
|
+
const stored = asRecord(value);
|
|
187
|
+
if (stored === null) return null;
|
|
188
|
+
if (
|
|
189
|
+
!isNullableNumber(stored.tokensBefore) ||
|
|
190
|
+
!isNullableNumber(stored.tokensAfter) ||
|
|
191
|
+
!isNullableNumber(stored.cost) ||
|
|
192
|
+
!isNullableNumber(stored.at)
|
|
193
|
+
) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
tokensBefore: stored.tokensBefore,
|
|
198
|
+
tokensAfter: stored.tokensAfter,
|
|
199
|
+
cost: stored.cost,
|
|
200
|
+
at: stored.at,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
178
204
|
function parseNodes(value: unknown): NodeInfo[] | null {
|
|
179
205
|
if (!Array.isArray(value)) return null;
|
|
180
206
|
const nodes: NodeInfo[] = [];
|
package/src/view/run-details.ts
CHANGED
|
@@ -84,9 +84,17 @@ function normalizeAgents(value: RunSummary): RunSummary {
|
|
|
84
84
|
modelFallbacksPruned: agent.modelFallbacksPruned ?? 0,
|
|
85
85
|
parent: agent.parent ?? null,
|
|
86
86
|
origin: normalizeOrigin(agent.origin),
|
|
87
|
+
// Absent in every message written before compaction shipped.
|
|
88
|
+
compactions: agent.compactions ?? 0,
|
|
89
|
+
lastCompaction: agent.lastCompaction ?? null,
|
|
87
90
|
};
|
|
88
91
|
}
|
|
89
|
-
return {
|
|
92
|
+
return {
|
|
93
|
+
...value,
|
|
94
|
+
agents,
|
|
95
|
+
modelFallbacks: value.modelFallbacks ?? 0,
|
|
96
|
+
compactions: value.compactions ?? 0,
|
|
97
|
+
};
|
|
90
98
|
}
|
|
91
99
|
|
|
92
100
|
function optionalNodes(value: unknown): boolean {
|