@yaag/cli 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.
@@ -0,0 +1,29 @@
1
+ import type { EventSink } from "../events.ts";
2
+ import type { AgentTransport, CompactionResult, Connection } from "../transport/index.ts";
3
+ import type { AgentUsage } from "./agent-usage.ts";
4
+ /** One compaction of one Agent's context (ADR-0043). */
5
+ export interface CompactionExchange {
6
+ readonly agent: string;
7
+ readonly connection: Connection;
8
+ readonly transport: AgentTransport;
9
+ /** Monotonic per Agent, from 0. */
10
+ readonly index: number;
11
+ /** Settled Asks of this Agent at the compaction point. */
12
+ readonly afterAsks: number;
13
+ /** pi `customInstructions`; the text never rides the wire (ticket 09). */
14
+ readonly instructions?: string;
15
+ readonly emit: EventSink;
16
+ readonly usage: AgentUsage;
17
+ }
18
+ /**
19
+ * Compacts one Agent's context and reports what the summary call cost.
20
+ *
21
+ * Playback answers from the Cassette and emits the same event, but folds no
22
+ * usage: a replayed Run never reports live accounting.
23
+ */
24
+ export declare function compactAgent(exchange: CompactionExchange): Promise<CompactionResult>;
25
+ /**
26
+ * Identity of one compaction: where it sits in the Agent's conversation, and
27
+ * whether the program steered it. The instruction text is hashed, never stored.
28
+ */
29
+ export declare function compactionHash(afterAsks: number, instructions: string | undefined): string;
@@ -13,4 +13,12 @@ export declare class AgentUsage {
13
13
  constructor(report: (snapshot: AgentUsageSnapshot) => void);
14
14
  /** Observes one raw Agent frame and reports after each valid assistant completion. */
15
15
  observe(frame: Frame): void;
16
+ /**
17
+ * Folds one completion yaag observed outside the frame stream, and reports.
18
+ *
19
+ * A compaction's summary call is the only such completion today: pi answers
20
+ * it on the `compact` response instead of an assistant `message_end`, so the
21
+ * live accounting would lose that spend (ADR-0043).
22
+ */
23
+ add(usage: AgentUsageSnapshot): void;
16
24
  }
@@ -1,8 +1,9 @@
1
1
  import type { Static, TSchema } from "typebox";
2
2
  import type { EventSink } from "../events.ts";
3
3
  import type { ModelErrorHistory, ModelResolution } from "../model/index.ts";
4
- import type { AgentStats, AgentTransport } from "../transport/index.ts";
5
- import type { AskOptions, Handle, ResolvedSpawnOptions, StructuredAskOptions } from "../types.ts";
4
+ import type { AgentStats, AgentTransport, CompactionResult } from "../transport/index.ts";
5
+ import type { AskOptions, ForkOptions, Handle, ResolvedSpawnOptions, StructuredAskOptions } from "../types.ts";
6
+ import type { ForkSpawner } from "./fork.ts";
6
7
  /** The mid-Ask Model Resolution one Agent's spawn handed it (ADR-0038). */
7
8
  export interface AgentModelFallback {
8
9
  readonly resolution: ModelResolution;
@@ -24,6 +25,10 @@ export interface AgentOptions {
24
25
  * Absent when the spawn named no candidate: there is nothing to fall back from.
25
26
  */
26
27
  readonly modelFallback?: AgentModelFallback;
28
+ /** pi's session file for this Agent; a fork copies it. Absent during playback. */
29
+ readonly sessionFile?: string;
30
+ /** Opens a fork of this Agent through the Run's spawn gate (ADR-0044). */
31
+ readonly fork?: ForkSpawner;
27
32
  /** Definition-owned defaults merged below explicit per-Ask options. */
28
33
  readonly askDefaults?: AskOptions;
29
34
  /** Definition identity recorded on its Asks, outside replay identity. */
@@ -46,6 +51,10 @@ export declare class Agent implements Handle {
46
51
  get model(): string;
47
52
  ask<Schema extends TSchema>(prompt: string, options: StructuredAskOptions<Schema>): Promise<Static<Schema>>;
48
53
  ask(prompt: string, options?: AskOptions): Promise<string>;
54
+ /** Replaces this Agent's context with a summary of it (ADR-0043). */
55
+ compact(instructions?: string): Promise<CompactionResult>;
56
+ /** Spawns a new Agent from a copy of this Agent's session (ADR-0044). */
57
+ fork(overrides?: ForkOptions): Promise<Handle>;
49
58
  /** True when the Agent was killed mid-Ask, so its cost is a floor (ADR-0012). */
50
59
  get incomplete(): boolean;
51
60
  /** Shuts the Agent down and reports its cost. Idempotent. */
@@ -0,0 +1,43 @@
1
+ import type { WorktreeResolution } from "../transport/index.ts";
2
+ import type { ForkOptions, Handle, ResolvedSpawnOptions, SpawnOptions } from "../types.ts";
3
+ /** What one Agent hands the spawn gate when it is forked (ADR-0044). */
4
+ export interface ForkSource {
5
+ readonly name: string;
6
+ /** The options the source settled on; the verbatim inheritance base. */
7
+ readonly spawnOptions: ResolvedSpawnOptions;
8
+ /** pi's session file for the source; a fork without one is refused. */
9
+ readonly sessionFile: string | undefined;
10
+ readonly worktree: WorktreeResolution | undefined;
11
+ /** Settled Asks of the source at the fork point. */
12
+ readonly settledAsks: number;
13
+ /** The source Handle itself, which becomes the fork's default Parent Link. */
14
+ readonly handle: Handle;
15
+ }
16
+ /** Opens one fork. The spawn gate owns it, so a fork walks the normal spawn path. */
17
+ export type ForkSpawner = (source: ForkSource, overrides?: ForkOptions) => Promise<Handle>;
18
+ /** Spawn identity and launch facts that only a fork carries. */
19
+ export interface ForkIdentity {
20
+ readonly forkOf: string;
21
+ readonly forkAsks: number;
22
+ /** The session pi copies with `--fork`; machine-specific, never identity. */
23
+ readonly forkSession: string;
24
+ readonly worktreeFrom?: WorktreeResolution;
25
+ }
26
+ /** One fork request: what to spawn, whose child it is, and whether to compact it. */
27
+ export interface ForkRequest {
28
+ /** Inherited options with the overrides applied; never carries `name`. */
29
+ readonly spawnOptions: SpawnOptions;
30
+ /** The raw Parent Link, defaulting to the fork source. */
31
+ readonly parent: unknown;
32
+ readonly fork: ForkIdentity;
33
+ readonly compact: boolean | string | undefined;
34
+ }
35
+ /**
36
+ * Merges a fork source with explicit overrides into one spawn request.
37
+ *
38
+ * Inheritance is verbatim, with two deliberate exceptions: `name` is dropped,
39
+ * so the child gets a fresh auto-name unless the overrides give it one, and
40
+ * `compact` never reaches the spawn options, because it is a fork verb rather
41
+ * than a property of the Agent.
42
+ */
43
+ export declare function forkRequest(source: ForkSource, overrides?: ForkOptions): ForkRequest;
@@ -4,4 +4,5 @@
4
4
  */
5
5
  export { Agent } from "./agent.ts";
6
6
  export { type AgentConfig, type AgentDefinition, agentDefinitionConfig, defineAgent, isAgentDefinition, } from "./define-agent.ts";
7
+ export { type ForkSource, type ForkSpawner, forkRequest } from "./fork.ts";
7
8
  export { makeSpawn } from "./spawn.ts";
@@ -0,0 +1,31 @@
1
+ import type { AskOptions, Handle, SpawnOptions } from "../types.ts";
2
+ import type { ForkIdentity, ForkSpawner } from "./fork.ts";
3
+ import type { SpawnDependencies } from "./spawn.ts";
4
+ /** One request the open path serves: a plain spawn, or a fork of another Agent. */
5
+ export interface OpenAgentRequest {
6
+ /** Never carries `parent`: a Handle must not reach the recorded options. */
7
+ readonly spawnOptions: SpawnOptions;
8
+ /** The raw Parent Link option, validated by `resolveParent`. */
9
+ readonly parent: unknown;
10
+ readonly askDefaults: AskOptions | undefined;
11
+ /** Definition identity stays separate from a topology-overridden Agent name. */
12
+ readonly definitionName: string | undefined;
13
+ /** Present only for a fork; adds `origin: "fork"` and the `--fork` source. */
14
+ readonly fork?: ForkIdentity;
15
+ }
16
+ /** What the open path needs beyond the Run's spawn dependencies. */
17
+ export interface OpenAgentContext extends SpawnDependencies {
18
+ /** Agent names already allocated in this Run. */
19
+ readonly taken: Set<string>;
20
+ /** How an opened Agent forks itself later. */
21
+ readonly forkSpawner: ForkSpawner;
22
+ }
23
+ /**
24
+ * Opens one Agent: resolves extensions and a model, starts its transport, and
25
+ * emits `agent_spawn`.
26
+ *
27
+ * A fork walks this very path, so the Tool Contract probe, Model Resolution,
28
+ * the worktree wrapper and the Cassette identity cannot drift between a spawn
29
+ * and a fork (ADR-0044).
30
+ */
31
+ export declare function openAgent(deps: OpenAgentContext, request: OpenAgentRequest): Promise<Handle>;
@@ -1,5 +1,5 @@
1
1
  import type { ModelSelection } from "../model/index.ts";
2
- import type { OpenOptions } from "../transport/index.ts";
2
+ import type { OpenOptions, WorktreeResolution } from "../transport/index.ts";
3
3
  import type { ResolvedSpawnOptions, SpawnOptions } from "../types.ts";
4
4
  /** Everything one spawn needs to build its open request, before a model settles. */
5
5
  export interface OpenRequestOptions {
@@ -10,6 +10,12 @@ export interface OpenRequestOptions {
10
10
  readonly declaredExtensions?: readonly string[];
11
11
  /** Resolved parent Agent name (Parent Link); spawn identity only, never argv. */
12
12
  readonly parent?: string;
13
+ /** Fork facts (ADR-0044); the first three are spawn identity, the rest are not. */
14
+ readonly origin?: "fork";
15
+ readonly forkOf?: string;
16
+ readonly forkAsks?: number;
17
+ readonly forkSession?: string;
18
+ readonly worktreeFrom?: WorktreeResolution;
13
19
  readonly sessionDir: string | undefined;
14
20
  }
15
21
  /**
@@ -2,7 +2,7 @@ import type { ConfigExtension } from "../config/index.ts";
2
2
  import type { EventSink } from "../events.ts";
3
3
  import type { RunContext } from "../run/index.ts";
4
4
  import type { TransportFactory } from "../transport/index.ts";
5
- import { Agent } from "./agent.ts";
5
+ import type { Agent } from "./agent.ts";
6
6
  /** Dependencies for one Run's Agent-spawn gate. */
7
7
  export interface SpawnDependencies {
8
8
  readonly factory: TransportFactory;
@@ -1,4 +1,4 @@
1
- import type { AgentStats, AskMarker, AskPlayback, Frame } from "../transport/index.ts";
1
+ import type { AgentStats, AskMarker, AskPlayback, CompactionMarker, CompactionPlayback, Frame } from "../transport/index.ts";
2
2
  import type { CassetteAgent } from "./cassette.ts";
3
3
  /**
4
4
  * Replays one Cassette Agent's frame streams at the transport seam.
@@ -19,6 +19,14 @@ export declare class CassetteReplay {
19
19
  frames(): AsyncIterable<Frame>;
20
20
  /** Moves playback to an Ask that its caller has already identity-checked. */
21
21
  beginAsk(marker: AskMarker): AskPlayback;
22
+ /**
23
+ * Moves playback to a compaction its caller has already identity-checked.
24
+ *
25
+ * A compaction lives between two Asks, and `beginAsk` never switches the
26
+ * cursors back to the Agent-level streams, so the recorded compaction owns
27
+ * its own streams (ADR-0043).
28
+ */
29
+ beginCompaction(marker: CompactionMarker): CompactionPlayback;
22
30
  /** Ends the stream cleanly, allowing a caller to switch to another source. */
23
31
  finish(): void;
24
32
  }
@@ -32,6 +32,9 @@ export declare const CassetteSchema: Type.TObject<{
32
32
  worktree: Type.TOptional<Type.TLiteral<true>>;
33
33
  declaredExtensions: Type.TOptional<Type.TArray<Type.TString>>;
34
34
  parent: Type.TOptional<Type.TString>;
35
+ origin: Type.TOptional<Type.TLiteral<"fork">>;
36
+ forkOf: Type.TOptional<Type.TString>;
37
+ forkAsks: Type.TOptional<Type.TNumber>;
35
38
  }>;
36
39
  model: Type.TString;
37
40
  sessionFile: Type.TOptional<Type.TString>;
@@ -100,6 +103,25 @@ export declare const CassetteSchema: Type.TObject<{
100
103
  }>>;
101
104
  recovered: Type.TOptional<Type.TLiteral<true>>;
102
105
  }>>;
106
+ compactions: Type.TOptional<Type.TArray<Type.TObject<{
107
+ index: Type.TNumber;
108
+ hash: Type.TString;
109
+ afterAsks: Type.TNumber;
110
+ result: Type.TOptional<Type.TObject<{
111
+ tokensBefore: Type.TUnion<[Type.TNull, Type.TNumber]>;
112
+ tokensAfter: Type.TUnion<[Type.TNull, Type.TNumber]>;
113
+ tokens: Type.TUnion<[Type.TNull, Type.TObject<{
114
+ input: Type.TNumber;
115
+ output: Type.TNumber;
116
+ cacheRead: Type.TNumber;
117
+ cacheWrite: Type.TNumber;
118
+ total: Type.TNumber;
119
+ }>]>;
120
+ cost: Type.TUnion<[Type.TNull, Type.TNumber]>;
121
+ }>>;
122
+ sentFrames: Type.TArray<Type.TUnsafe<Frame>>;
123
+ receivedFrames: Type.TArray<Type.TUnsafe<Frame>>;
124
+ }>>>;
103
125
  stats: Type.TObject<{
104
126
  tokens: Type.TUnion<[Type.TNull, Type.TObject<{
105
127
  input: Type.TNumber;
@@ -1,7 +1,7 @@
1
1
  import type { CanonicalJsonObject } from "../ask-contract/index.ts";
2
2
  import type { AskLimitOutcome, AskStalledOutcome } from "../errors.ts";
3
3
  import type { RunOutcome } from "../events.ts";
4
- import type { AgentStats, AskCompletion, AskInvalidOutputPlayback, AskMarker, AskMarkerContext, Frame, OpenOptions, WorktreeResolution } from "../transport/index.ts";
4
+ import type { AgentStats, AskCompletion, AskInvalidOutputPlayback, AskMarker, AskMarkerContext, CompactionMarker, CompactionResult, Frame, OpenOptions, WorktreeResolution } from "../transport/index.ts";
5
5
  import type { ThinkingLevel } from "../types.ts";
6
6
  /** The Cassette format this runtime writes; the loader also reads version 1. */
7
7
  export declare const CASSETTE_VERSION: 2;
@@ -45,8 +45,21 @@ export interface CassetteAgent {
45
45
  readonly sentFrames: readonly Frame[];
46
46
  readonly receivedFrames: readonly Frame[];
47
47
  readonly asks: readonly CassetteAsk[];
48
+ /** Compactions between this Agent's Asks; absent for an Agent that compacted none. */
49
+ readonly compactions?: readonly CassetteCompaction[];
48
50
  readonly stats: AgentStats;
49
51
  }
52
+ /** The frames attributed to one compaction marker (ADR-0043). */
53
+ export interface CassetteCompaction {
54
+ readonly index: number;
55
+ readonly hash: string;
56
+ /** Settled Asks of this Agent at the compaction point. */
57
+ readonly afterAsks: number;
58
+ /** What the recorded compaction reported; absent when the exchange failed. */
59
+ readonly result?: CompactionResult;
60
+ readonly sentFrames: readonly Frame[];
61
+ readonly receivedFrames: readonly Frame[];
62
+ }
50
63
  /** Behavioural identity supplied when opening a recorded Agent. */
51
64
  export interface CassetteSpawn {
52
65
  readonly name: string;
@@ -77,6 +90,12 @@ export interface CassetteSpawn {
77
90
  * Part of spawn identity; absent for a root Agent and for older Cassettes.
78
91
  */
79
92
  readonly parent?: string;
93
+ /** How the Agent came to be; absent means an ordinary spawn. Spawn identity. */
94
+ readonly origin?: "fork";
95
+ /** Name of the Agent this one was forked from. Spawn identity. */
96
+ readonly forkOf?: string;
97
+ /** Settled Asks of the fork source at the fork point. Spawn identity. */
98
+ readonly forkAsks?: number;
80
99
  }
81
100
  /** The frames attributed to one Ask marker. */
82
101
  export interface CassetteAsk {
@@ -122,6 +141,8 @@ export interface CassetteRecorder {
122
141
  received(frame: Frame): void;
123
142
  beginAsk(marker: AskMarker): void;
124
143
  finishAsk(completion: AskCompletion): void;
144
+ beginCompaction(marker: CompactionMarker): void;
145
+ finishCompaction(result: CompactionResult | undefined): void;
125
146
  closed(stats: AgentStats): void;
126
147
  }
127
148
  /** Collects a Cassette in memory; `executeRun` serializes it at Run settlement. */
@@ -2,7 +2,7 @@
2
2
  * Public surface of the `cassette/` module: record, replay, checkpoint, and resume.
3
3
  * Files inside this directory import each other directly.
4
4
  */
5
- export { CASSETTE_VERSION, type Cassette, type CassetteAgent, type CassetteArtifact, type CassetteAsk, CassetteCollector, type CassetteGit, type CassetteRun, type CassetteSink, type CassetteSpawn, } from "./cassette.ts";
5
+ export { CASSETTE_VERSION, type Cassette, type CassetteAgent, type CassetteArtifact, type CassetteAsk, CassetteCollector, type CassetteCompaction, type CassetteGit, type CassetteRun, type CassetteSink, type CassetteSpawn, } from "./cassette.ts";
6
6
  export { assertReplayable, interruptedResumeWarning, loadCassette } from "./cassette-loader.ts";
7
7
  export { publishCassette } from "./cassette-publish.ts";
8
8
  export { checkpointFileName, cleanStaleCheckpointTemp, resolveCheckpointDirectory, } from "./checkpoint-dir.ts";
@@ -1,8 +1,8 @@
1
- import type { AskMarker, OpenOptions, RecordedSpawnSelection } from "../transport/index.ts";
1
+ import type { AskMarker, CompactionMarker, OpenOptions, RecordedSpawnSelection } from "../transport/index.ts";
2
2
  import type { CassetteAgent, CassetteSpawn } from "./cassette.ts";
3
3
  /** A pure description of the first strict replay identity mismatch. */
4
4
  export interface ReplayMismatch {
5
- readonly kind: "unexpected-spawn" | "spawn-options" | "changed-ask" | "extra-ask";
5
+ readonly kind: "unexpected-spawn" | "spawn-options" | "changed-ask" | "extra-ask" | "changed-compaction" | "extra-compaction";
6
6
  readonly agent: string;
7
7
  readonly index?: number;
8
8
  readonly expectedHash: string;
@@ -14,6 +14,7 @@ export interface ReplayMismatch {
14
14
  export declare const replayMismatch: {
15
15
  spawn(expected: CassetteAgent | null, actual: OpenOptions): ReplayMismatch | null;
16
16
  ask(agent: CassetteAgent, cursor: number, actual: AskMarker): ReplayMismatch | null;
17
+ compaction(agent: CassetteAgent, cursor: number, actual: CompactionMarker): ReplayMismatch | null;
17
18
  };
18
19
  /** Converts a detected mismatch into strict replay's public failure. */
19
20
  export declare function strictReplay(mismatch: ReplayMismatch): never;
@@ -30,7 +30,7 @@ export interface ModelResolutionOutcome {
30
30
  readonly modelErrors: readonly ModelError[];
31
31
  }
32
32
  /** Why an Ask, spawn, or Run failed. Programs may branch on this; most won't. */
33
- export type YaagErrorCode = "AGENT_FAILED" | "AGENT_DIED" | "AGENT_BUSY" | "ASK_TIMEOUT" | "ASK_LIMIT" | "ASK_STALLED" | "ASK_INVALID_OUTPUT" | "ARGS_INVALID" | "CONFIG_INVALID" | "OPTIONS_CONFLICT" | "RECORD_PATH_UNUSABLE" | "REPLAY_DIVERGED" | "RESUME_REFUSED" | "RUN_CLOSED" | "RUN_STOPPED" | "WORKTREE_REFUSED" | "MODEL_RESOLUTION_FAILED" | "SPAWN_FAILED";
33
+ export type YaagErrorCode = "AGENT_FAILED" | "AGENT_DIED" | "AGENT_BUSY" | "ASK_TIMEOUT" | "ASK_LIMIT" | "ASK_STALLED" | "COMPACT_DURING_ASK" | "COMPACT_FAILED" | "FORK_DURING_ASK" | "FORK_REFUSED" | "ASK_INVALID_OUTPUT" | "ARGS_INVALID" | "CONFIG_INVALID" | "OPTIONS_CONFLICT" | "RECORD_PATH_UNUSABLE" | "REPLAY_DIVERGED" | "RESUME_REFUSED" | "RUN_CLOSED" | "RUN_STOPPED" | "WORKTREE_REFUSED" | "MODEL_RESOLUTION_FAILED" | "SPAWN_FAILED";
34
34
  /** The single error class of the runtime (ADR-0003). */
35
35
  export declare class YaagError extends Error {
36
36
  readonly code: YaagErrorCode;
@@ -19,7 +19,7 @@ export type { ModelErrorReason } from "./model/index.ts";
19
19
  export type RunOutcome = "completed" | "failed" | "stopped" | "paused" | "interrupted";
20
20
  /**
21
21
  * How an Agent came to be. An absent value on an event means "spawn"; "fork"
22
- * arrives with the forking spec.
22
+ * marks an Agent created from a copy of another Agent's session (ADR-0044).
23
23
  */
24
24
  export type SpawnOrigin = "spawn" | "fork";
25
25
  /** The current, Ask-scoped observer projection derived from Agent frames. */
@@ -159,6 +159,30 @@ export type LifecycleEventBody = {
159
159
  readonly tokens: TokenBreakdown;
160
160
  /** Cumulative assistant-completion cost observed so far. */
161
161
  readonly cost: number;
162
+ } | {
163
+ /**
164
+ * One compaction of an Agent's context (ADR-0043).
165
+ *
166
+ * It carries the summary call's own spend, so a Run Summary does not
167
+ * silently lose it. `agent_exit` stays authoritative for the Agent's
168
+ * total: pi's `get_session_stats` already includes compaction spend.
169
+ * The custom instructions never ride the wire (ticket 09); only the
170
+ * `custom` flag says that the program supplied some.
171
+ */
172
+ readonly type: "agent_compaction";
173
+ readonly agent: string;
174
+ /** Monotonic per Agent, from 0. */
175
+ readonly index: number;
176
+ /** Context tokens before the summary replaced the transcript; null when unknown. */
177
+ readonly tokensBefore: number | null;
178
+ /** Estimated context tokens after the summary; null when unknown. */
179
+ readonly tokensAfter: number | null;
180
+ /** The summary call's own token breakdown; null when unknown. */
181
+ readonly tokens: TokenBreakdown | null;
182
+ /** The summary call's own cost; null when unknown. */
183
+ readonly cost: number | null;
184
+ /** The program supplied custom compaction instructions. */
185
+ readonly custom: boolean;
162
186
  } | {
163
187
  readonly type: "agent_exit";
164
188
  readonly agent: string;
@@ -1,6 +1,6 @@
1
1
  export type { AgentConfig, AgentDefinition } from "./agent/index.ts";
2
2
  export { agentDefinitionConfig, defineAgent, isAgentDefinition } from "./agent/index.ts";
3
- export type { Cassette, CassetteAgent, CassetteArtifact, CassetteAsk, CassetteGit, CassetteRun, CassetteSink, CassetteSpawn, } from "./cassette/index.ts";
3
+ export type { Cassette, CassetteAgent, CassetteArtifact, CassetteAsk, CassetteCompaction, CassetteGit, CassetteRun, CassetteSink, CassetteSpawn, } from "./cassette/index.ts";
4
4
  export { assertReplayable, CASSETTE_VERSION, loadCassette, recordingTransport, replayTransport, resumeTransport, } from "./cassette/index.ts";
5
5
  export type { ConfigEnvironment, ConfigExtension, ConfigLayerName, EffectiveConfig, EffectiveConfigRequest, } from "./config/index.ts";
6
6
  export { EMPTY_EFFECTIVE_CONFIG, loadEffectiveConfig, PROJECT_CONFIG_DIR, } from "./config/index.ts";
@@ -13,9 +13,9 @@ export { agentAskPath, childPath, DEFAULT_NODE_DECODERS, NodeTracker, sanitizeNo
13
13
  export { prompt, promptGist } from "./prompt/index.ts";
14
14
  export type { OrchestrationProgram, ProgramDefinition, RunContext, RunOptions, } from "./run/index.ts";
15
15
  export { defineRun, executeRun, isOrchestrationProgram, programDefinition } from "./run/index.ts";
16
- export type { AgentInfo, AgentState, AskingAgentInfo, EndedRunSummary, ExitedAgentInfo, IdleAgentInfo, ModelFallbackInfo, NodeInfo, RunningRunSummary, RunOutcome, RunState, RunSummary, } from "./summary/index.ts";
16
+ export type { AgentInfo, AgentState, AskingAgentInfo, CompactionInfo, EndedRunSummary, ExitedAgentInfo, IdleAgentInfo, ModelFallbackInfo, NodeInfo, RunningRunSummary, RunOutcome, RunState, RunSummary, } from "./summary/index.ts";
17
17
  export { applyEvent, initialSummary } from "./summary/index.ts";
18
- export type { AgentStats, AgentTransport, AskMarker, AskMarkerContext, AskPlayback, DiscoveredSkill, Frame, ReapPath, ReapTarget, SkillProbeFactory, TokenBreakdown, TransportFactory, TransportStartup, TransportStartupObserver, WorktreeResolution, } from "./transport/index.ts";
18
+ export type { AgentStats, AgentTransport, AskMarker, AskMarkerContext, AskPlayback, CompactionMarker, CompactionPlayback, CompactionResult, DiscoveredSkill, Frame, ReapPath, ReapTarget, SkillProbeFactory, TokenBreakdown, TransportFactory, TransportStartup, TransportStartupObserver, WorktreeResolution, } from "./transport/index.ts";
19
19
  export { readSystemPromptSidecar, reap, worktreeTransport } from "./transport/index.ts";
20
- export type { AskOptions, Handle, ResolvedSpawnOptions, SpawnOptions, SpawnOverrides, StructuredAskOptions, ThinkingLevel, } from "./types.ts";
20
+ export type { AskOptions, ForkOptions, Handle, ResolvedSpawnOptions, SpawnOptions, SpawnOverrides, StructuredAskOptions, ThinkingLevel, } from "./types.ts";
21
21
  export { AGENT_NODE_TABLE_MAX, ASK_OUTPUT_FLUSH_INTERVAL_MS, ASK_OUTPUT_MAX_BYTES, ASK_OUTPUT_TRUNCATION_MARKER, NODE_GIST_MAX_CHARS, PROMPT_GIST_MAX_CHARS, TOOL_ARGS_GIST_MAX_CHARS, } from "./wire-constants.ts";
@@ -3,4 +3,4 @@
3
3
  * Files inside this directory import each other directly.
4
4
  */
5
5
  export { applyEvent, type EndedRunSummary, initialSummary, type RunningRunSummary, type RunOutcome, type RunState, type RunSummary, } from "./summary.ts";
6
- export type { AgentInfo, AgentState, AskingAgentInfo, ExitedAgentInfo, IdleAgentInfo, ModelFallbackInfo, NodeInfo, SpawnOrigin, } from "./summary-agent.ts";
6
+ export type { AgentInfo, AgentState, AskingAgentInfo, CompactionInfo, ExitedAgentInfo, IdleAgentInfo, ModelFallbackInfo, NodeInfo, SpawnOrigin, } from "./summary-agent.ts";
@@ -5,6 +5,14 @@ import type { NodeInfo } from "./summary-nodes.ts";
5
5
  export type { AgentActivity, SpawnOrigin } from "../events.ts";
6
6
  export type { ModelFallbackInfo } from "./summary-fallbacks.ts";
7
7
  export type { NodeInfo } from "./summary-nodes.ts";
8
+ /** What one compaction of an Agent's context reported (ADR-0043). */
9
+ export interface CompactionInfo {
10
+ readonly tokensBefore: number | null;
11
+ readonly tokensAfter: number | null;
12
+ /** The summary call's own cost, already included in `agent_exit` accounting. */
13
+ readonly cost: number | null;
14
+ readonly at: number | null;
15
+ }
8
16
  /** The observer-facing lifecycle state of an Agent. */
9
17
  export type AgentState = "idle" | "asking" | "exited";
10
18
  /** Identity and accounting facts that apply in every observer Agent state. */
@@ -31,6 +39,10 @@ interface AgentInfoBase {
31
39
  /** Timestamp ordering only cumulative live accounting, never lifecycle state. */
32
40
  readonly usageUpdatedAt: number | null;
33
41
  readonly askStartedAt: number | null;
42
+ /** Compactions of this Agent's context so far (ADR-0043). */
43
+ readonly compactions: number;
44
+ /** What the newest compaction reported, or null when the Agent compacted none. */
45
+ readonly lastCompaction: CompactionInfo | null;
34
46
  /** This Agent's bounded Nested Node table, in first-seen order (spec §3). */
35
47
  readonly nodes: readonly NodeInfo[];
36
48
  /** Exited Nested Nodes dropped to keep the table bounded. */
@@ -129,6 +141,15 @@ export declare function endAsk(current: AgentRecord | undefined, settlement: Ask
129
141
  * stream order while older stamped observations are ignored.
130
142
  */
131
143
  export declare function setUsage(current: AgentRecord | undefined, observation: AgentUsageObservation, at: number | null): AgentRecord;
144
+ /**
145
+ * Counts one compaction and keeps what it reported.
146
+ *
147
+ * It folds no cost: the summary call's spend reaches the Summary through
148
+ * `agent_usage`, and `agent_exit` stays authoritative (ADR-0043). A terminal
149
+ * Agent still counts a late compaction, because the count is a fact about the
150
+ * conversation rather than a lifecycle state.
151
+ */
152
+ export declare function compactAgent(current: AgentRecord | undefined, observation: CompactionInfo, at: number | null): AgentRecord;
132
153
  /**
133
154
  * Folds authoritative shutdown accounting into the terminal Agent state.
134
155
  *
@@ -2,7 +2,7 @@ import type { LifecycleEvent, LifecycleEventBody, RunOutcome } from "../events.t
2
2
  import type { TokenBreakdown } from "../transport/index.ts";
3
3
  import type { AgentInfo } from "./summary-agent.ts";
4
4
  export type { NodeState, NodeUsage, RunOutcome } from "../events.ts";
5
- export type { AgentActivity, AgentInfo, AgentState, AskingAgentInfo, ExitedAgentInfo, IdleAgentInfo, ModelFallbackInfo, NodeInfo, SpawnOrigin, } from "./summary-agent.ts";
5
+ export type { AgentActivity, AgentInfo, AgentState, AskingAgentInfo, CompactionInfo, ExitedAgentInfo, IdleAgentInfo, ModelFallbackInfo, NodeInfo, SpawnOrigin, } from "./summary-agent.ts";
6
6
  /** The observer-facing lifecycle state of a Run. */
7
7
  export type RunState = "running" | "ended";
8
8
  /** Accounting and identity facts shared by all Run observer states. */
@@ -24,6 +24,8 @@ interface RunSummaryBase {
24
24
  readonly worstFrameGapMs: number;
25
25
  /** Run-wide Model Resolution fallbacks, pruned per-Agent entries included. */
26
26
  readonly modelFallbacks: number;
27
+ /** Compactions across every Agent of this Run (ADR-0043). */
28
+ readonly compactions: number;
27
29
  }
28
30
  /** A Run that has not settled; it has neither outcome nor compatibility result. */
29
31
  export interface RunningRunSummary extends RunSummaryBase {
@@ -1,5 +1,5 @@
1
1
  import type { AvailableModel } from "../model/index.ts";
2
- import type { AgentStats, AgentTransport, AskMarker, Frame } from "./transport.ts";
2
+ import type { AgentStats, AgentTransport, AskMarker, CompactionMarker, Frame } from "./transport.ts";
3
3
  /**
4
4
  * Scripted frame playback for one prompt sent through a FakeTransport.
5
5
  *
@@ -41,6 +41,10 @@ export interface FakeTransportOptions extends FakePromptScript {
41
41
  readonly steerError?: string;
42
42
  /** Makes an abort RPC response fail without embedding a limit decision in playback. */
43
43
  readonly abortError?: string;
44
+ /** Payload of a `compact` command response; omission answers a bare success. */
45
+ readonly compaction?: Record<string, unknown>;
46
+ /** Makes a `compact` command response fail, as pi does when it cannot summarize. */
47
+ readonly compactError?: string;
44
48
  /** Makes the private report_result schema command fail. */
45
49
  readonly schemaCommandError?: string;
46
50
  /** The snapshot answered to `get_available_models`; defaults to this fake's own model. */
@@ -73,11 +77,14 @@ export declare class FakeTransport implements AgentTransport {
73
77
  /** Everything the runtime wrote, in order. */
74
78
  readonly sent: Frame[];
75
79
  readonly asks: AskMarker[];
80
+ readonly compactions: CompactionMarker[];
76
81
  constructor(options?: FakeTransportOptions);
77
82
  send(frame: Frame): void;
78
83
  frames(): AsyncIterable<Frame>;
79
84
  beginAsk(marker: AskMarker): undefined;
80
85
  finishAsk(): void;
86
+ beginCompaction(marker: CompactionMarker): undefined;
87
+ finishCompaction(): void;
81
88
  /** True once the runtime shut this Agent down. */
82
89
  get closed(): boolean;
83
90
  /** Number of times this fake actually began close work. */
@@ -9,11 +9,11 @@ export { FrameGapTracker } from "./frame-gap.ts";
9
9
  export { FrameQueue } from "./frame-queue.ts";
10
10
  export { decodeFrames } from "./jsonl.ts";
11
11
  export { liveTransport } from "./live-transport.ts";
12
- export { type AgentProgress, piCommand, readAgentProgress } from "./pi-state.ts";
12
+ export { type AgentProgress, piCommand, readAgentProgress, readCompaction, } from "./pi-state.ts";
13
13
  export { type ReapPath, type ReapTarget, reap } from "./reap.ts";
14
14
  export { type DiscoveredSkill, liveSkillProbe, type SkillProbeFactory } from "./skill-probe.ts";
15
15
  export { skillRestrictionTransport } from "./skill-restriction-transport.ts";
16
16
  export { readSystemPromptSidecar } from "./system-prompt-recorder.ts";
17
17
  export { SYSTEM_PROMPT_SIDECAR_SUFFIX, systemPromptSidecarPath, } from "./system-prompt-recorder-extension.ts";
18
- export type { AgentStats, AgentTransport, AskCompletion, AskInvalidOutputPlayback, AskMarker, AskMarkerContext, AskPlayback, Frame, OpenOptions, TokenBreakdown, TransportFactory, TransportStartup, TransportStartupObserver, WorktreeResolution, } from "./transport.ts";
18
+ export type { AgentStats, AgentTransport, AskCompletion, AskInvalidOutputPlayback, AskMarker, AskMarkerContext, AskPlayback, CompactionMarker, CompactionPlayback, CompactionResult, Frame, OpenOptions, TokenBreakdown, TransportFactory, TransportStartup, TransportStartupObserver, WorktreeResolution, } from "./transport.ts";
19
19
  export { worktreeTransport } from "./worktree-transport.ts";
@@ -1,4 +1,4 @@
1
- import type { AgentStats, Frame, OpenOptions } from "./transport.ts";
1
+ import type { AgentStats, CompactionResult, Frame, OpenOptions } from "./transport.ts";
2
2
  /** What the Agent reported about its own work, read from a `get_state` probe. */
3
3
  export interface AgentProgress {
4
4
  /** True while pi streams a completion, compacts its context, or holds a queued prompt. */
@@ -49,3 +49,10 @@ export declare function readSessionFile(response: Frame): string | null;
49
49
  * distinguishable (ADR-0012); a partial breakdown is treated as unknown.
50
50
  */
51
51
  export declare function readStats(response: Frame): AgentStats;
52
+ /**
53
+ * What a `compact` response reports (ADR-0043).
54
+ *
55
+ * A missing field becomes null rather than 0, so "unknown" and "free" stay
56
+ * distinguishable: a custom compaction handler may answer without usage.
57
+ */
58
+ export declare function readCompaction(response: Frame): CompactionResult;
@@ -29,6 +29,22 @@ export interface AgentStats {
29
29
  readonly tokens: TokenBreakdown | null;
30
30
  readonly cost: number | null;
31
31
  }
32
+ /**
33
+ * What one compaction of an Agent's context reported (ADR-0043).
34
+ *
35
+ * Every field is null when pi reported nothing for it: a custom compaction
36
+ * handler may answer without usage, and "unknown" must stay distinct from 0.
37
+ */
38
+ export interface CompactionResult {
39
+ /** Context tokens before the summary replaced the transcript. */
40
+ readonly tokensBefore: number | null;
41
+ /** Estimated context tokens after the summary replaced the transcript. */
42
+ readonly tokensAfter: number | null;
43
+ /** Token breakdown of the summary call itself. */
44
+ readonly tokens: TokenBreakdown | null;
45
+ /** Cost of the summary call itself. */
46
+ readonly cost: number | null;
47
+ }
32
48
  /** Recorded inputs used to explain an Ask-hash mismatch without changing identity. */
33
49
  export interface AskMarkerContext {
34
50
  readonly prompt: string;
@@ -72,6 +88,25 @@ export interface AskCompletion {
72
88
  /** The Stall Watchdog settled this Ask from observed state (ADR-0029). */
73
89
  readonly recovered?: true;
74
90
  }
91
+ /**
92
+ * Opaque marker naming one compaction of an Agent's context.
93
+ *
94
+ * A compaction sits between two Asks, so it cannot ride the Agent-level frame
95
+ * streams: a Cassette replay switches those cursors to an Ask and never
96
+ * switches back. The marker gives the exchange its own recorded streams.
97
+ */
98
+ export interface CompactionMarker {
99
+ /** Monotonic per Agent, from 0. */
100
+ readonly index: number;
101
+ /** sha256 of the settled-Ask count and the custom instructions, when given. */
102
+ readonly hash: string;
103
+ /** Settled Asks of this Agent at the compaction point. */
104
+ readonly afterAsks: number;
105
+ }
106
+ /** Presence identifies Cassette playback of one compaction. */
107
+ export interface CompactionPlayback {
108
+ readonly result: CompactionResult;
109
+ }
75
110
  /** Presence identifies Cassette playback, including recorded successful Asks. */
76
111
  export interface AskPlayback {
77
112
  /** Limit outcome recorded for this Ask, if it rejected with ASK_LIMIT. */
@@ -116,6 +151,13 @@ export interface AgentTransport {
116
151
  beginAsk(marker: AskMarker): AskPlayback | undefined;
117
152
  /** Reports surfaced live outcomes; replay ignores completion. */
118
153
  finishAsk(completion: AskCompletion): void;
154
+ /**
155
+ * Begins one compaction. Live transports return undefined; replay transports
156
+ * return the recorded result.
157
+ */
158
+ beginCompaction(marker: CompactionMarker): CompactionPlayback | undefined;
159
+ /** Ends the compaction exchange, so later frames belong to the Agent again. */
160
+ finishCompaction(result: CompactionResult | undefined): void;
119
161
  /**
120
162
  * The extraction policy recorded for the Ask at `index`, when a Cassette
121
163
  * backs it. An Ask recorded under an older policy keeps that policy, so its
@@ -171,6 +213,22 @@ export interface OpenOptions {
171
213
  readonly sessionDir?: string;
172
214
  /** Resumes an existing pi session, translated to `--session <path>`. */
173
215
  readonly sessionFile?: string;
216
+ /** How the Agent came to be; absent means an ordinary spawn. Spawn identity. */
217
+ readonly origin?: "fork";
218
+ /** Name of the Agent this one was forked from. Spawn identity; never argv. */
219
+ readonly forkOf?: string;
220
+ /** Settled Asks of the fork source at the fork point. Spawn identity. */
221
+ readonly forkAsks?: number;
222
+ /**
223
+ * The fork source's session file, translated to `pi --fork <path>`.
224
+ * Never spawn identity: the path is machine-specific.
225
+ */
226
+ readonly forkSession?: string;
227
+ /**
228
+ * The fork source's worktree, so the fork branches from it instead of from a
229
+ * clean base tree. Never spawn identity.
230
+ */
231
+ readonly worktreeFrom?: WorktreeResolution;
174
232
  }
175
233
  /** Nondeterministic worktree identity resolved below the transport seam. */
176
234
  export interface WorktreeResolution {
@@ -1,6 +1,8 @@
1
1
  import type { Static, TSchema } from "typebox";
2
2
  import type { ModelSpec, ThinkingLevel, ThinkingSpec } from "./model/index.ts";
3
+ import type { CompactionResult } from "./transport/index.ts";
3
4
  export type { ThinkingLevel } from "./model/index.ts";
5
+ export type { CompactionResult } from "./transport/index.ts";
4
6
  /** Options for spawning one Agent (ADR-0001, ADR-0009, ADR-0026). */
5
7
  export interface SpawnOptions {
6
8
  /** Working directory. Defaults to the Orchestrator's cwd. */
@@ -84,6 +86,24 @@ export interface ResolvedSpawnOptions extends Omit<SpawnOptions, "model" | "thin
84
86
  readonly model?: string;
85
87
  readonly thinking?: ThinkingLevel;
86
88
  }
89
+ /**
90
+ * What a fork may change about the Agent it copies (ADR-0044).
91
+ *
92
+ * A fork inherits the source's resolved spawn options verbatim: model,
93
+ * thinking, tools, skills, system prompt, extensions and Ask defaults. `name`
94
+ * is the exception — the child always gets a fresh auto-name unless this object
95
+ * names one. The fork source becomes the child's default `parent`.
96
+ *
97
+ * The inherited model is the concrete one the source settled on, so the child
98
+ * never re-runs the source's fallback list; `fork({ model })` is the way out.
99
+ * A fork of a Worktree Agent gets its own fresh worktree, branched from the
100
+ * source's branch, so only committed state transfers. The child re-runs the
101
+ * ADR-0026 Tool Contract probe against its own effective options.
102
+ */
103
+ export interface ForkOptions extends SpawnOptions {
104
+ /** true = pi's default compaction; a string = pi custom instructions. */
105
+ readonly compact?: boolean | string;
106
+ }
87
107
  /** Topology-only fields that may change when spawning an Agent Definition. */
88
108
  export interface SpawnOverrides {
89
109
  /** Log and event label. Defaults to the definition's name; duplicates are suffixed. */
@@ -190,4 +210,23 @@ export interface Handle {
190
210
  */
191
211
  ask<Schema extends TSchema>(prompt: string, options: StructuredAskOptions<Schema>): Promise<Static<Schema>>;
192
212
  ask(prompt: string, options?: AskOptions): Promise<string>;
213
+ /**
214
+ * Spawns a new Agent from a copy of this Agent's session (ADR-0044).
215
+ *
216
+ * The fork point is a settled Ask boundary: a call while an Ask is in flight
217
+ * rejects with `FORK_DURING_ASK`. An Agent that died mid-Ask, or that holds
218
+ * no session file, rejects with `FORK_REFUSED`. An Agent that exited cleanly
219
+ * stays forkable, so a program can build context, let the Agent exit, then
220
+ * fork its final state many times.
221
+ */
222
+ fork(overrides?: ForkOptions): Promise<Handle>;
223
+ /**
224
+ * Replaces this Agent's context with a summary of it (ADR-0043).
225
+ *
226
+ * `instructions` becomes pi's `customInstructions`. A call while an Ask is in
227
+ * flight rejects with `COMPACT_DURING_ASK`; a refusal from pi rejects with
228
+ * `COMPACT_FAILED`. The result reports what the summary call cost; each field
229
+ * is null when pi reported nothing for it.
230
+ */
231
+ compact(instructions?: string): Promise<CompactionResult>;
193
232
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yaag/cli",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -21,8 +21,8 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@earendil-works/pi-tui": "^0.84.0",
24
- "@yaag/runtime": "0.10.0",
25
- "@yaag/tui": "0.10.0",
24
+ "@yaag/runtime": "0.11.0",
25
+ "@yaag/tui": "0.11.0",
26
26
  "typebox": "1.3.7"
27
27
  }
28
28
  }
@@ -24,7 +24,7 @@ function renderPlainEvent(event: LifecycleEventBody, mode: PlainRenderMode): str
24
24
  case "run_start":
25
25
  return `[yaag] run ${event.program}`;
26
26
  case "agent_spawn":
27
- return `[${event.agent}] spawned ${event.model} in ${event.cwd}${event.parent === undefined ? "" : ` under ${event.parent}`}`;
27
+ return `[${event.agent}] ${event.origin === "fork" ? "forked" : "spawned"} ${event.model} in ${event.cwd}${event.parent === undefined ? "" : ` under ${event.parent}`}`;
28
28
  case "ask_start":
29
29
  return `[${event.agent}] ask #${event.index + 1}${event.replayed === true ? " (replayed)" : ""}${event.promptGist === "" ? "" : ` — ${event.promptGist}`}`;
30
30
  case "ask_activity":
@@ -43,6 +43,8 @@ function renderPlainEvent(event: LifecycleEventBody, mode: PlainRenderMode): str
43
43
  return `[${event.agent}] model ${event.failedModel} ${event.reason} — falling back to ${event.resolvedModel}`;
44
44
  case "agent_model":
45
45
  return `[${event.agent}] model \u2014 now running ${event.model}`;
46
+ case "agent_compaction":
47
+ return `[${event.agent}] compacted ${contextTokens(event.tokensBefore)} → ${contextTokens(event.tokensAfter)}${event.custom ? " (custom)" : ""} ${cost(event.cost)}`;
46
48
  case "agent_exit":
47
49
  return `[${event.agent}] exit ${tokens(event.tokens)} ${cost(event.cost)}${event.incomplete ? " (killed mid-ask, cost incomplete)" : ""}`;
48
50
  case "run_end": {
@@ -64,6 +66,7 @@ function minimalEvent(type: LifecycleEventBody["type"]): boolean {
64
66
  type === "ask_end" ||
65
67
  type === "model_fallback" ||
66
68
  type === "agent_model" ||
69
+ type === "agent_compaction" ||
67
70
  type === "agent_exit" ||
68
71
  type === "run_end"
69
72
  );
@@ -97,6 +100,11 @@ function tokens(value: TokenBreakdown | null): string {
97
100
  return value === null ? "tokens unknown" : `${value.total} tokens`;
98
101
  }
99
102
 
103
+ /** Context size at one end of a compaction; a compaction may report neither. */
104
+ function contextTokens(value: number | null): string {
105
+ return value === null ? "?" : `${value} tokens`;
106
+ }
107
+
100
108
  function cost(value: number | null): string {
101
109
  return value === null ? "cost unknown" : `$${value.toFixed(4)}`;
102
110
  }