@yaag/runtime 0.1.2 → 0.2.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.
Files changed (44) hide show
  1. package/package.json +3 -3
  2. package/src/agent.ts +11 -3
  3. package/src/ask-contract-identity.ts +23 -5
  4. package/src/ask-exchange-events.ts +14 -1
  5. package/src/ask-exchange-options.ts +7 -2
  6. package/src/ask-exchange.ts +198 -26
  7. package/src/ask-hash.ts +7 -3
  8. package/src/ask-limit.ts +27 -11
  9. package/src/ask-output-steering.ts +1 -3
  10. package/src/ask-output.ts +20 -4
  11. package/src/ask-turn.ts +11 -0
  12. package/src/cassette-loader.ts +24 -0
  13. package/src/cassette-replay.ts +76 -7
  14. package/src/cassette-schema.ts +17 -2
  15. package/src/cassette.ts +10 -9
  16. package/src/checkpoint-flush.ts +101 -0
  17. package/src/connection.ts +20 -0
  18. package/src/define-agent.ts +11 -5
  19. package/src/errors.ts +11 -2
  20. package/src/events.ts +29 -3
  21. package/src/fake-transport.ts +55 -0
  22. package/src/frame-queue.ts +5 -0
  23. package/src/index.ts +11 -1
  24. package/src/model-resolution.ts +119 -0
  25. package/src/model-suffix.ts +24 -0
  26. package/src/pi-state.ts +63 -8
  27. package/src/recording-transport.ts +8 -8
  28. package/src/replay-divergence.ts +1 -0
  29. package/src/replay-transport.ts +4 -0
  30. package/src/report-result-extension.ts +128 -0
  31. package/src/report-result-output.ts +73 -0
  32. package/src/report-result-steering.ts +83 -0
  33. package/src/report-result.ts +122 -0
  34. package/src/resume-transport.ts +26 -8
  35. package/src/run-checkpoint.ts +93 -41
  36. package/src/run.ts +86 -32
  37. package/src/spawn.ts +29 -4
  38. package/src/stall-watchdog.ts +193 -0
  39. package/src/summary-agent.ts +11 -2
  40. package/src/summary.ts +23 -2
  41. package/src/thinking-level.ts +32 -0
  42. package/src/transport.ts +43 -8
  43. package/src/types.ts +50 -14
  44. package/src/validation-errors.ts +10 -4
package/src/transport.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { CanonicalJsonObject } from "./ask-contract-identity.ts";
2
2
  import type { AskLimitOutcome, AskStalledOutcome } from "./errors.ts";
3
- import type { AskOptions, SpawnOptions, ThinkingLevel } from "./types.ts";
3
+ import type { ReportedResult } from "./report-result.ts";
4
+ import type { AskOptions, ResolvedSpawnOptions, ThinkingLevel } from "./types.ts";
4
5
 
5
6
  /**
6
7
  * The one seam of the runtime (ticket 04). An AgentTransport represents a whole
@@ -34,7 +35,7 @@ export interface AgentStats {
34
35
  export interface AskMarkerContext {
35
36
  readonly prompt: string;
36
37
  readonly spawn: Pick<
37
- SpawnOptions,
38
+ ResolvedSpawnOptions,
38
39
  | "cwd"
39
40
  | "model"
40
41
  | "systemPrompt"
@@ -48,7 +49,7 @@ export interface AskMarkerContext {
48
49
  >;
49
50
  readonly ask: Pick<
50
51
  AskOptions,
51
- "maxTurns" | "maxToolCalls" | "maxDurationMs" | "idleMs" | "wrapUpPrompt"
52
+ "maxTurns" | "maxToolCalls" | "maxDurationMs" | "idleMs" | "stallMs" | "wrapUpPrompt"
52
53
  > & {
53
54
  readonly outputSchema?: CanonicalJsonObject;
54
55
  readonly maxSteers?: number;
@@ -80,6 +81,18 @@ export interface AskInvalidOutputPlayback {
80
81
  readonly steeringEfforts: number;
81
82
  }
82
83
 
84
+ /** What one live Ask produced, reported to its transport when the Ask finishes. */
85
+ export interface AskCompletion {
86
+ /** Limit outcome, present only when the Ask rejected with ASK_LIMIT. */
87
+ readonly limit?: AskLimitOutcome;
88
+ /** Stalled outcome, present only when the Ask rejected with ASK_STALLED. */
89
+ readonly stalled?: AskStalledOutcome;
90
+ /** Invalid structured-output result, present only when it surfaced. */
91
+ readonly invalidOutput?: AskInvalidOutputPlayback;
92
+ /** The Stall Watchdog settled this Ask from observed state (ADR-0029). */
93
+ readonly recovered?: true;
94
+ }
95
+
83
96
  /** Presence identifies Cassette playback, including recorded successful Asks. */
84
97
  export interface AskPlayback {
85
98
  /** Limit outcome recorded for this Ask, if it rejected with ASK_LIMIT. */
@@ -88,8 +101,26 @@ export interface AskPlayback {
88
101
  readonly stalled?: AskStalledOutcome;
89
102
  /** Invalid structured-output result recorded for this Ask, if it surfaced. */
90
103
  readonly outcome?: AskInvalidOutputPlayback;
104
+ /**
105
+ * The result the recorded Ask reported through `report_result`, read from its
106
+ * frames when the Cassette holds one. Reading it here keeps playback a pure
107
+ * function of the Cassette instead of a race with the replayed frame stream.
108
+ */
109
+ readonly reported?: ReportedResult;
91
110
  /** Whether recorded correction history has an abort settlement after final text. */
92
111
  readonly awaitsAbortSettlement?: true;
112
+ /**
113
+ * The recorded Ask settled through Stall Watchdog recovery, so no
114
+ * `agent_settled` frame exists: playback settles on the terminal assistant
115
+ * `message_end` instead.
116
+ */
117
+ readonly recovered?: true;
118
+ /**
119
+ * False when the recorded Ask holds no `agent_settled` frame. Playback then
120
+ * surfaces the recorded outcome instead of waiting for a settlement that the
121
+ * Cassette does not contain.
122
+ */
123
+ readonly settles?: false;
93
124
  }
94
125
 
95
126
  /** One Agent, as the layer above sees it. */
@@ -110,11 +141,15 @@ export interface AgentTransport {
110
141
  beginAsk(marker: AskMarker): AskPlayback | undefined;
111
142
 
112
143
  /** Reports surfaced live outcomes; replay ignores completion. */
113
- finishAsk(
114
- outcome?: AskLimitOutcome,
115
- stalled?: AskStalledOutcome,
116
- invalidOutput?: AskInvalidOutputPlayback,
117
- ): void;
144
+ finishAsk(completion: AskCompletion): void;
145
+
146
+ /**
147
+ * The extraction policy recorded for the Ask at `index`, when a Cassette
148
+ * backs it. An Ask recorded under an older policy keeps that policy, so its
149
+ * identity and its settlement behavior stay the ones it was recorded with
150
+ * (ADR-0032). Live transports have nothing recorded and omit this method.
151
+ */
152
+ recordedExtractionPolicy?(index: number): string | undefined;
118
153
 
119
154
  /**
120
155
  * Shut the Agent down and report its cost. Idempotent.
package/src/types.ts CHANGED
@@ -1,18 +1,29 @@
1
1
  import type { Static, TSchema } from "typebox";
2
+ import type { ModelSpec, ThinkingSpec } from "./model-resolution.ts";
3
+ import type { ThinkingLevel } from "./thinking-level.ts";
2
4
 
3
- /** pi's supported thinking levels. */
4
- export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
5
+ export type { ThinkingLevel } from "./thinking-level.ts";
5
6
 
6
7
  /** Options for spawning one Agent (ADR-0001, ADR-0009, ADR-0026). */
7
8
  export interface SpawnOptions {
8
9
  /** Working directory. Defaults to the Orchestrator's cwd. */
9
10
  readonly cwd?: string;
10
- /** Model pattern, e.g. "anthropic/claude-haiku-4". Unset inherits the user's default. */
11
- readonly model?: string;
11
+ /**
12
+ * Model pattern, e.g. "anthropic/claude-haiku-4". Unset inherits the user's default.
13
+ *
14
+ * An array is an ordered fallback list and a function picks the next candidate
15
+ * from the failures so far. Any resolved pattern may carry an inline thinking
16
+ * suffix (`"opus-5:medium"`), which wins over `thinking`.
17
+ */
18
+ readonly model?: ModelSpec;
12
19
  /** Replaces the default system prompt (`pi --system-prompt`). */
13
20
  readonly systemPrompt?: string;
14
- /** Sets pi's thinking level; omission preserves pi's default. */
15
- readonly thinking?: ThinkingLevel;
21
+ /**
22
+ * Sets pi's thinking level; omission preserves pi's default. A function picks
23
+ * the level from the settled model, and is not consulted for a model pattern
24
+ * that carries an inline thinking suffix.
25
+ */
26
+ readonly thinking?: ThinkingSpec;
16
27
  /** Appends text to pi's system prompt (`pi --append-system-prompt`). */
17
28
  readonly appendSystemPrompt?: string;
18
29
  /**
@@ -49,6 +60,12 @@ export interface SpawnOptions {
49
60
  readonly worktree?: boolean;
50
61
  }
51
62
 
63
+ /** Spawn options after Model Resolution has settled one model and thinking level. */
64
+ export interface ResolvedSpawnOptions extends Omit<SpawnOptions, "model" | "thinking"> {
65
+ readonly model?: string;
66
+ readonly thinking?: ThinkingLevel;
67
+ }
68
+
52
69
  /** Topology-only fields that may change when spawning an Agent Definition. */
53
70
  export interface SpawnOverrides {
54
71
  /** Log and event label. Defaults to the definition's name; duplicates are suffixed. */
@@ -85,15 +102,32 @@ export interface AskOptions {
85
102
  * idle detection (ADR-0020).
86
103
  */
87
104
  readonly idleMs?: number;
105
+ /**
106
+ * Silence budget in milliseconds for the Stall Watchdog, which guards every
107
+ * live Ask by default (ADR-0029). On expiry yaag probes the Agent and either
108
+ * recovers a missed settlement or rejects with `ASK_STALLED`. Omission uses
109
+ * the 10-minute default. `false`, and any value that is not above zero,
110
+ * disable the watchdog for this Ask.
111
+ */
112
+ readonly stallMs?: number | false;
88
113
  /** Per-Ask replacement for the runtime's wrap-up steering message. */
89
114
  readonly wrapUpPrompt?: string;
90
115
  }
91
116
 
92
117
  /** Ask options that require a schema and preserve its inferred successful result. */
93
118
  export interface StructuredAskOptions<Schema extends TSchema> extends AskOptions {
94
- /** TypeBox schema that changes successful Ask resolution from text to a parsed value. */
119
+ /**
120
+ * TypeBox object schema the Agent reports its result against, instead of text.
121
+ *
122
+ * It becomes the input schema of one internal tool, so it must describe an
123
+ * object (ADR-0032).
124
+ */
95
125
  readonly outputSchema: Schema;
96
- /** Maximum corrective steering efforts for this Ask; defaults to 3 when omitted. */
126
+ /**
127
+ * Maximum settlements without a reported result that this Ask corrects;
128
+ * defaults to 3 when omitted. Argument repair inside a turn is the Agent's
129
+ * own loop and costs nothing here.
130
+ */
97
131
  readonly maxSteers?: number;
98
132
  }
99
133
 
@@ -115,12 +149,14 @@ export interface Handle {
115
149
  * Sends a prompt and resolves after the Agent's turn settles.
116
150
  *
117
151
  * Schema-free calls resolve with the exact final assistant text. Calls with
118
- * `outputSchema` resolve with the extracted, TypeBox-validated value. Rejects
119
- * with a YaagError for a failed or empty turn, timeout, `ASK_LIMIT`, or dead
120
- * Agent; invalid structured output rejects recoverably with
121
- * `ASK_INVALID_OUTPUT` after at most `maxSteers` (default 3) in-Ask
122
- * correction efforts and an abort settlement. These recoverable outcomes
123
- * leave the Handle reusable. A concurrent call rejects with `AGENT_BUSY`.
152
+ * `outputSchema` resolve with the TypeBox-validated value the Agent reports
153
+ * through one internal tool call (ADR-0032). Rejects with a YaagError for a
154
+ * failed or empty turn, timeout, `ASK_LIMIT`, or dead Agent. An Ask that
155
+ * settles without a reported result is corrected at most `maxSteers` times
156
+ * (default 3) and then rejects recoverably with `ASK_INVALID_OUTPUT` after an
157
+ * abort settlement; a reported value the schema rejects fails the same way
158
+ * without a correction. These recoverable outcomes leave the Handle reusable.
159
+ * A concurrent call rejects with `AGENT_BUSY`.
124
160
  */
125
161
  ask<Schema extends TSchema>(
126
162
  prompt: string,
@@ -8,18 +8,23 @@ export function formatValidationErrors(
8
8
  return Array.from(errors).flatMap((error) => formatError(value, error));
9
9
  }
10
10
 
11
+ /**
12
+ * A missing property is reported at the object that lacks it, never at the path
13
+ * the property would occupy: the absent path cannot be pointed at, and a pointer
14
+ * that contradicts its own message steers a model into the wrong shape (ADR-0032).
15
+ */
11
16
  function formatError(value: unknown, error: TLocalizedValidationError): readonly string[] {
17
+ const path = toJsonPath(value, error.instancePath);
12
18
  if (error.keyword === "required") {
13
19
  return error.params.requiredProperties.map(
14
- (property) => `${toJsonPath(value, error.instancePath, property)}: ${error.message}`,
20
+ (property) => `${path}: must have required property ${JSON.stringify(property)}`,
15
21
  );
16
22
  }
17
- return [`${toJsonPath(value, error.instancePath)}: ${error.message}`];
23
+ return [`${path}: ${error.message}`];
18
24
  }
19
25
 
20
- function toJsonPath(value: unknown, instancePath: string, property?: string): string {
26
+ function toJsonPath(value: unknown, instancePath: string): string {
21
27
  const segments = reconstructSegments(value, instancePath);
22
- if (property !== undefined) segments.push(property);
23
28
  return segments.reduce((path, segment) => `${path}${formatSegment(segment)}`, "$");
24
29
  }
25
30
 
@@ -66,5 +71,6 @@ function isArrayIndex(value: string): boolean {
66
71
  }
67
72
 
68
73
  function formatSegment(segment: string): string {
74
+ if (isArrayIndex(segment)) return `[${segment}]`;
69
75
  return /^[A-Za-z_$][\w$]*$/.test(segment) ? `.${segment}` : `[${JSON.stringify(segment)}]`;
70
76
  }