@yaag/extension 0.9.0 → 0.10.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 CHANGED
@@ -113,3 +113,27 @@ rules. ADR-0022 plans the human-in-the-loop verbs; they are not available yet.
113
113
  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
+
117
+ ## Lineage
118
+
119
+ `parent` names the Agent a spawn belongs under. It is data only: it sets the
120
+ Agent's place in the Run tree, so the TUI and the Run Summary draw the child
121
+ under its parent. It opens no channel between the two Agents, and it ends no
122
+ lifetime — every Agent still dies with the Run.
123
+
124
+ `parent` takes a Handle a previous spawn in this Run returned, and it works the
125
+ same way as an override on a definition: `ctx.spawn(reviewer, { parent })`. A
126
+ parent that already exited stays a valid parent.
127
+
128
+ An Agent cannot spawn. It can *ask* for a helper through `outputSchema`, and
129
+ the program decides:
130
+
131
+ <!-- embed: docs/examples/06-lineage.ts -->
132
+
133
+ ```ts
134
+ for (const helper of wish.helpers) {
135
+ if (!ALLOWED_ROLES.has(helper.role)) continue;
136
+ const agent = await ctx.spawn({ name: helper.role, parent: implementer });
137
+ reports.push(await agent.ask(prompt`Review this report: ${wish.report}`));
138
+ }
139
+ ```
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Mediated autonomy. The implementer reports which helpers it wants; the
3
+ * program is the gate that decides, spawns them, and places them in the tree
4
+ * under the implementer with `parent`.
5
+ */
6
+ import { defineRun, prompt } from "@yaag/runtime";
7
+ import { Type } from "typebox";
8
+
9
+ const ALLOWED_ROLES = new Set(["reviewer", "summarizer"]);
10
+
11
+ const Wish = Type.Object({
12
+ report: Type.String(),
13
+ helpers: Type.Array(Type.Object({ role: Type.String(), reason: Type.String() })),
14
+ });
15
+
16
+ export default defineRun({
17
+ name: "lineage",
18
+ description: "Spawns the helpers an implementer asks for, under the implementer.",
19
+ async run(ctx) {
20
+ const implementer = await ctx.spawn({ name: "implementer" });
21
+ const wish = await implementer.ask(
22
+ prompt`Report one paragraph about this repository, and the helper roles you want.`,
23
+ { outputSchema: Wish },
24
+ );
25
+ const reports: string[] = [];
26
+ for (const helper of wish.helpers) {
27
+ if (!ALLOWED_ROLES.has(helper.role)) continue;
28
+ const agent = await ctx.spawn({ name: helper.role, parent: implementer });
29
+ reports.push(await agent.ask(prompt`Review this report: ${wish.report}`));
30
+ }
31
+ return [wish.report, ...reports].join("\n\n");
32
+ },
33
+ });
package/docs/examples.md CHANGED
@@ -100,3 +100,20 @@ holds it.
100
100
  Full file: [`examples/05-record-resume.ts`](examples/05-record-resume.ts). Run
101
101
  it with `yaag run examples/05-record-resume.ts --record run.json`, then resume
102
102
  it with `yaag run examples/05-record-resume.ts --resume run.json`.
103
+
104
+ ## 06 — lineage
105
+
106
+ `parent` places an Agent under another Agent in the Run tree. Here the
107
+ implementer reports the helper roles it wants, and the program decides which of
108
+ them to spawn. The helpers become a subtree of the implementer, and no Agent
109
+ ever spawns anything itself.
110
+
111
+ <!-- embed: docs/examples/06-lineage.ts -->
112
+
113
+ ```ts
114
+ const agent = await ctx.spawn({ name: helper.role, parent: implementer });
115
+ reports.push(await agent.ask(prompt`Review this report: ${wish.report}`));
116
+ ```
117
+
118
+ Full file: [`examples/06-lineage.ts`](examples/06-lineage.ts). Run it with
119
+ `yaag run examples/06-lineage.ts`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yaag/extension",
3
- "version": "0.9.0",
3
+ "version": "0.10.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.9.0",
29
- "@yaag/runtime": "0.9.0",
30
- "@yaag/tui": "0.9.0",
28
+ "@yaag/cli": "0.10.0",
29
+ "@yaag/runtime": "0.10.0",
30
+ "@yaag/tui": "0.10.0",
31
31
  "nanoid": "^6.0.1"
32
32
  },
33
33
  "peerDependencies": {
@@ -18,13 +18,14 @@ export const DOC_PAGES = [
18
18
  "troubleshooting.md",
19
19
  ] as const;
20
20
 
21
- /** The five shipped example programs, in reading order. */
21
+ /** The shipped example programs, in reading order. */
22
22
  export const EXAMPLE_FILES = [
23
23
  "01-minimal.ts",
24
24
  "02-args.ts",
25
25
  "03-fan-out.ts",
26
26
  "04-controlled-ask.ts",
27
27
  "05-record-resume.ts",
28
+ "06-lineage.ts",
28
29
  ] as const;
29
30
 
30
31
  /** Absolute path of one shipped doc file, named relative to the docs root. */
@@ -150,6 +150,10 @@ function parseAgent(value: unknown): AgentInfo | null {
150
150
  nodes,
151
151
  finishedNodesPruned: stored.finishedNodesPruned,
152
152
  modelFallbacks: fallbacks,
153
+ // Lineage is tolerant for the same reason as the counters: a record written
154
+ // before the Parent Link carries neither field.
155
+ parent: isNullableString(stored.parent) ? stored.parent : null,
156
+ origin: stored.origin === "fork" ? ("fork" as const) : ("spawn" as const),
153
157
  // Tolerant for the same reason as the Run-level counter.
154
158
  modelFallbacksPruned:
155
159
  typeof stored.modelFallbacksPruned === "number" ? stored.modelFallbacksPruned : 0,
@@ -82,6 +82,8 @@ function normalizeAgents(value: RunSummary): RunSummary {
82
82
  finishedNodesPruned: agent.finishedNodesPruned ?? 0,
83
83
  modelFallbacks: agent.modelFallbacks ?? [],
84
84
  modelFallbacksPruned: agent.modelFallbacksPruned ?? 0,
85
+ parent: agent.parent ?? null,
86
+ origin: normalizeOrigin(agent.origin),
85
87
  };
86
88
  }
87
89
  return { ...value, agents, modelFallbacks: value.modelFallbacks ?? 0 };
@@ -166,10 +168,23 @@ function isAgentBase(value: Record<string, unknown>): boolean {
166
168
  optionalNodes(value.nodes) &&
167
169
  (value.finishedNodesPruned === undefined || natural(value.finishedNodesPruned)) &&
168
170
  optionalFallbacks(value.modelFallbacks) &&
169
- (value.modelFallbacksPruned === undefined || natural(value.modelFallbacksPruned))
171
+ (value.modelFallbacksPruned === undefined || natural(value.modelFallbacksPruned)) &&
172
+ // Lineage is tolerated as absent: a details blob persisted before the
173
+ // Parent Link carries neither field.
174
+ (value.parent === undefined || nullableString(value.parent)) &&
175
+ (value.origin === undefined || typeof value.origin === "string")
170
176
  );
171
177
  }
172
178
 
179
+ /**
180
+ * An origin a newer CLI may not have shipped yet reads as "spawn", the same way
181
+ * a stored Run record treats it. A value outside the contract must never drop
182
+ * the whole details blob and blank the Run view.
183
+ */
184
+ function normalizeOrigin(value: unknown): "spawn" | "fork" {
185
+ return value === "fork" ? "fork" : "spawn";
186
+ }
187
+
173
188
  function optionalEvent(value: unknown): value is LifecycleEvent | undefined {
174
189
  return value === undefined || isEvent(value);
175
190
  }
@@ -183,7 +198,9 @@ function isEvent(value: unknown): value is LifecycleEvent {
183
198
  return (
184
199
  strings(value.agent, value.model, value.cwd) &&
185
200
  optionalString(value.branch) &&
186
- optionalString(value.sessionFile)
201
+ optionalString(value.sessionFile) &&
202
+ optionalString(value.parent) &&
203
+ (value.origin === undefined || typeof value.origin === "string")
187
204
  );
188
205
  case "ask_start":
189
206
  return (