@a-dray/aglib 0.1.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 (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -0
  3. package/dist/agent.d.ts +158 -0
  4. package/dist/agent.js +2 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/content.d.ts +45 -0
  7. package/dist/content.js +11 -0
  8. package/dist/content.js.map +1 -0
  9. package/dist/harness/adapters/acp/index.d.ts +92 -0
  10. package/dist/harness/adapters/acp/index.js +479 -0
  11. package/dist/harness/adapters/acp/index.js.map +1 -0
  12. package/dist/harness/adapters/acp/rpc.d.ts +23 -0
  13. package/dist/harness/adapters/acp/rpc.js +103 -0
  14. package/dist/harness/adapters/acp/rpc.js.map +1 -0
  15. package/dist/harness/adapters/native/compaction.d.ts +16 -0
  16. package/dist/harness/adapters/native/compaction.js +55 -0
  17. package/dist/harness/adapters/native/compaction.js.map +1 -0
  18. package/dist/harness/adapters/native/index.d.ts +2 -0
  19. package/dist/harness/adapters/native/index.js +2 -0
  20. package/dist/harness/adapters/native/index.js.map +1 -0
  21. package/dist/harness/adapters/native/loop.d.ts +25 -0
  22. package/dist/harness/adapters/native/loop.js +153 -0
  23. package/dist/harness/adapters/native/loop.js.map +1 -0
  24. package/dist/harness/harness.d.ts +129 -0
  25. package/dist/harness/harness.js +2 -0
  26. package/dist/harness/harness.js.map +1 -0
  27. package/dist/harness/index.d.ts +3 -0
  28. package/dist/harness/index.js +2 -0
  29. package/dist/harness/index.js.map +1 -0
  30. package/dist/index.d.ts +10 -0
  31. package/dist/index.js +5 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/json.d.ts +4 -0
  34. package/dist/json.js +2 -0
  35. package/dist/json.js.map +1 -0
  36. package/dist/model/adapters/anthropic/index.d.ts +34 -0
  37. package/dist/model/adapters/anthropic/index.js +271 -0
  38. package/dist/model/adapters/anthropic/index.js.map +1 -0
  39. package/dist/model/adapters/fake/index.d.ts +27 -0
  40. package/dist/model/adapters/fake/index.js +37 -0
  41. package/dist/model/adapters/fake/index.js.map +1 -0
  42. package/dist/model/adapters/openai-compatible/index.d.ts +29 -0
  43. package/dist/model/adapters/openai-compatible/index.js +226 -0
  44. package/dist/model/adapters/openai-compatible/index.js.map +1 -0
  45. package/dist/model/conformance.d.ts +210 -0
  46. package/dist/model/conformance.js +427 -0
  47. package/dist/model/conformance.js.map +1 -0
  48. package/dist/model/index.d.ts +2 -0
  49. package/dist/model/index.js +2 -0
  50. package/dist/model/index.js.map +1 -0
  51. package/dist/model/model.d.ts +85 -0
  52. package/dist/model/model.js +8 -0
  53. package/dist/model/model.js.map +1 -0
  54. package/dist/result.d.ts +23 -0
  55. package/dist/result.js +3 -0
  56. package/dist/result.js.map +1 -0
  57. package/dist/run.d.ts +10 -0
  58. package/dist/run.js +477 -0
  59. package/dist/run.js.map +1 -0
  60. package/dist/sandbox/adapters/docker.d.ts +22 -0
  61. package/dist/sandbox/adapters/docker.js +443 -0
  62. package/dist/sandbox/adapters/docker.js.map +1 -0
  63. package/dist/sandbox/adapters/local.d.ts +11 -0
  64. package/dist/sandbox/adapters/local.js +284 -0
  65. package/dist/sandbox/adapters/local.js.map +1 -0
  66. package/dist/sandbox/conformance.d.ts +73 -0
  67. package/dist/sandbox/conformance.js +309 -0
  68. package/dist/sandbox/conformance.js.map +1 -0
  69. package/dist/sandbox/index.d.ts +1 -0
  70. package/dist/sandbox/index.js +2 -0
  71. package/dist/sandbox/index.js.map +1 -0
  72. package/dist/sandbox/sandbox.d.ts +172 -0
  73. package/dist/sandbox/sandbox.js +2 -0
  74. package/dist/sandbox/sandbox.js.map +1 -0
  75. package/dist/session/entry.d.ts +196 -0
  76. package/dist/session/entry.js +2 -0
  77. package/dist/session/entry.js.map +1 -0
  78. package/dist/session/index.d.ts +2 -0
  79. package/dist/session/index.js +2 -0
  80. package/dist/session/index.js.map +1 -0
  81. package/dist/session/log.d.ts +20 -0
  82. package/dist/session/log.js +24 -0
  83. package/dist/session/log.js.map +1 -0
  84. package/dist/session/messages.d.ts +42 -0
  85. package/dist/session/messages.js +99 -0
  86. package/dist/session/messages.js.map +1 -0
  87. package/dist/store/adapters/sqlite.d.ts +20 -0
  88. package/dist/store/adapters/sqlite.js +365 -0
  89. package/dist/store/adapters/sqlite.js.map +1 -0
  90. package/dist/store/conformance.d.ts +66 -0
  91. package/dist/store/conformance.js +580 -0
  92. package/dist/store/conformance.js.map +1 -0
  93. package/dist/store/index.d.ts +1 -0
  94. package/dist/store/index.js +2 -0
  95. package/dist/store/index.js.map +1 -0
  96. package/dist/store/store.d.ts +244 -0
  97. package/dist/store/store.js +2 -0
  98. package/dist/store/store.js.map +1 -0
  99. package/dist/tools/execute.d.ts +24 -0
  100. package/dist/tools/execute.js +105 -0
  101. package/dist/tools/execute.js.map +1 -0
  102. package/dist/tools/index.d.ts +2 -0
  103. package/dist/tools/index.js +2 -0
  104. package/dist/tools/index.js.map +1 -0
  105. package/dist/tools/tool.d.ts +106 -0
  106. package/dist/tools/tool.js +34 -0
  107. package/dist/tools/tool.js.map +1 -0
  108. package/package.json +119 -0
@@ -0,0 +1,103 @@
1
+ import { err, ok } from "../../../result.js";
2
+ /**
3
+ * JSON-RPC 2.0 over one process's stdio, newline-delimited.
4
+ *
5
+ * Bidirectional on purpose: the protocol this speaks inverts control for files
6
+ * and terminals, so the agent is as much a caller as a callee and the two
7
+ * directions are the same machinery.
8
+ */
9
+ export function createRpc(child) {
10
+ let nextId = 1;
11
+ const pending = new Map();
12
+ const requests = new Map();
13
+ const notifications = new Map();
14
+ let open = true;
15
+ const write = (message) => {
16
+ if (open)
17
+ child.write(`${JSON.stringify(message)}\n`);
18
+ };
19
+ async function dispatch(message) {
20
+ if (message.method === undefined) {
21
+ const waiting = message.id === undefined ? undefined : pending.get(Number(message.id));
22
+ if (!waiting)
23
+ return;
24
+ pending.delete(Number(message.id));
25
+ waiting(message.error
26
+ ? err({ code: `rpc-${message.error.code}`, message: message.error.message, retryable: false })
27
+ : ok(message.result ?? null));
28
+ return;
29
+ }
30
+ if (message.id === undefined) {
31
+ notifications.get(message.method)?.(message.params ?? null);
32
+ return;
33
+ }
34
+ const handler = requests.get(message.method);
35
+ if (!handler) {
36
+ write({ jsonrpc: "2.0", id: message.id, error: { code: -32601, message: `No handler for ${message.method}` } });
37
+ return;
38
+ }
39
+ const answered = await handler(message.params ?? null);
40
+ write(answered.ok
41
+ ? { jsonrpc: "2.0", id: message.id, result: answered.value }
42
+ : { jsonrpc: "2.0", id: message.id, error: { code: -32000, message: answered.error.message } });
43
+ }
44
+ const pump = (async () => {
45
+ const decoder = new TextDecoder();
46
+ let buffer = "";
47
+ for await (const chunk of child.stdout) {
48
+ buffer += decoder.decode(chunk, { stream: true });
49
+ for (;;) {
50
+ const newline = buffer.indexOf("\n");
51
+ if (newline < 0)
52
+ break;
53
+ const line = buffer.slice(0, newline).trim();
54
+ buffer = buffer.slice(newline + 1);
55
+ if (!line)
56
+ continue;
57
+ // A malformed line is the agent's bug, not a reason to drop the stream.
58
+ try {
59
+ await dispatch(JSON.parse(line));
60
+ }
61
+ catch { /* ignore */ }
62
+ }
63
+ }
64
+ })();
65
+ // Held rather than piped: an agent that dies explains itself on stderr, and
66
+ // that explanation is the only useful part of the failure we can report.
67
+ const diagnostics = (async () => {
68
+ const decoder = new TextDecoder();
69
+ let text = "";
70
+ for await (const chunk of child.stderr) {
71
+ text += decoder.decode(chunk, { stream: true });
72
+ }
73
+ return text.slice(-4000);
74
+ })();
75
+ const closed = (async () => {
76
+ await child.exited;
77
+ open = false;
78
+ await pump.catch(() => undefined);
79
+ const text = await diagnostics.catch(() => "");
80
+ for (const [id, waiting] of pending) {
81
+ pending.delete(id);
82
+ waiting(err({ code: "closed", message: `The agent exited. ${text}`.trim(), retryable: true }));
83
+ }
84
+ return text;
85
+ })();
86
+ return {
87
+ request(method, params) {
88
+ if (!open)
89
+ return Promise.resolve(err({ code: "closed", message: "The agent has exited.", retryable: true }));
90
+ const id = nextId++;
91
+ return new Promise((resolve) => {
92
+ pending.set(id, resolve);
93
+ write({ jsonrpc: "2.0", id, method, params });
94
+ });
95
+ },
96
+ notify(method, params) { write({ jsonrpc: "2.0", method, params }); },
97
+ on(method, handler) { requests.set(method, handler); },
98
+ onNotify(method, handler) { notifications.set(method, handler); },
99
+ closed,
100
+ close() { open = false; child.kill(); },
101
+ };
102
+ }
103
+ //# sourceMappingURL=rpc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc.js","sourceRoot":"","sources":["../../../../src/harness/adapters/acp/rpc.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SandboxProcess } from \"../../../sandbox/sandbox.js\";\nimport type { JsonValue } from \"../../../json.js\";\nimport { err, ok, type Failure, type Result } from \"../../../result.js\";\n\n/** Handles a request the agent sends back to us. Rejecting is a JSON-RPC error. */\nexport type RpcHandler = (params: JsonValue) => Promise<Result<JsonValue, Failure>>;\n\nexport interface Rpc {\n request(method: string, params: JsonValue): Promise<Result<JsonValue, Failure>>;\n notify(method: string, params: JsonValue): void;\n /** Register a method the agent may call on us. Unregistered methods answer \"method not found\". */\n on(method: string, handler: RpcHandler): void;\n onNotify(method: string, handler: (params: JsonValue) => void): void;\n /** Resolves when the process ends; carries whatever it wrote to stderr. */\n readonly closed: Promise<string>;\n close(): void;\n}\n\ninterface Envelope {\n jsonrpc: \"2.0\";\n id?: number | string;\n method?: string;\n params?: JsonValue;\n result?: JsonValue;\n error?: { code: number; message: string };\n}\n\n/**\n * JSON-RPC 2.0 over one process's stdio, newline-delimited.\n *\n * Bidirectional on purpose: the protocol this speaks inverts control for files\n * and terminals, so the agent is as much a caller as a callee and the two\n * directions are the same machinery.\n */\nexport function createRpc(child: SandboxProcess): Rpc {\n let nextId = 1;\n const pending = new Map<number, (value: Result<JsonValue, Failure>) => void>();\n const requests = new Map<string, RpcHandler>();\n const notifications = new Map<string, (params: JsonValue) => void>();\n let open = true;\n\n const write = (message: Envelope) => {\n if (open) child.write(`${JSON.stringify(message)}\\n`);\n };\n\n async function dispatch(message: Envelope): Promise<void> {\n if (message.method === undefined) {\n const waiting = message.id === undefined ? undefined : pending.get(Number(message.id));\n if (!waiting) return;\n pending.delete(Number(message.id));\n waiting(message.error\n ? err<Failure>({ code: `rpc-${message.error.code}`, message: message.error.message, retryable: false })\n : ok(message.result ?? null));\n return;\n }\n if (message.id === undefined) {\n notifications.get(message.method)?.(message.params ?? null);\n return;\n }\n const handler = requests.get(message.method);\n if (!handler) {\n write({ jsonrpc: \"2.0\", id: message.id, error: { code: -32601, message: `No handler for ${message.method}` } });\n return;\n }\n const answered = await handler(message.params ?? null);\n write(answered.ok\n ? { jsonrpc: \"2.0\", id: message.id, result: answered.value }\n : { jsonrpc: \"2.0\", id: message.id, error: { code: -32000, message: answered.error.message } });\n }\n\n const pump = (async () => {\n const decoder = new TextDecoder();\n let buffer = \"\";\n for await (const chunk of child.stdout as unknown as AsyncIterable<Uint8Array>) {\n buffer += decoder.decode(chunk, { stream: true });\n for (;;) {\n const newline = buffer.indexOf(\"\\n\");\n if (newline < 0) break;\n const line = buffer.slice(0, newline).trim();\n buffer = buffer.slice(newline + 1);\n if (!line) continue;\n // A malformed line is the agent's bug, not a reason to drop the stream.\n try { await dispatch(JSON.parse(line) as Envelope); } catch { /* ignore */ }\n }\n }\n })();\n\n // Held rather than piped: an agent that dies explains itself on stderr, and\n // that explanation is the only useful part of the failure we can report.\n const diagnostics = (async () => {\n const decoder = new TextDecoder();\n let text = \"\";\n for await (const chunk of child.stderr as unknown as AsyncIterable<Uint8Array>) {\n text += decoder.decode(chunk, { stream: true });\n }\n return text.slice(-4000);\n })();\n\n const closed = (async () => {\n await child.exited;\n open = false;\n await pump.catch(() => undefined);\n const text = await diagnostics.catch(() => \"\");\n for (const [id, waiting] of pending) {\n pending.delete(id);\n waiting(err<Failure>({ code: \"closed\", message: `The agent exited. ${text}`.trim(), retryable: true }));\n }\n return text;\n })();\n\n return {\n request(method, params) {\n if (!open) return Promise.resolve(err<Failure>({ code: \"closed\", message: \"The agent has exited.\", retryable: true }));\n const id = nextId++;\n return new Promise((resolve) => {\n pending.set(id, resolve);\n write({ jsonrpc: \"2.0\", id, method, params });\n });\n },\n notify(method, params) { write({ jsonrpc: \"2.0\", method, params }); },\n on(method, handler) { requests.set(method, handler); },\n onNotify(method, handler) { notifications.set(method, handler); },\n closed,\n close() { open = false; child.kill(); },\n };\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import type { Model, Message } from "../../../model/model.js";
2
+ import type { Stored } from "../../../session/entry.js";
3
+ /**
4
+ * Crude and deliberate: four characters per token, over the serialized request.
5
+ * A real count needs the provider's tokenizer, which would mean shipping one
6
+ * per provider to decide a threshold that is itself a guess. Being wrong here
7
+ * costs one early or late compaction, not correctness.
8
+ */
9
+ export declare function estimateTokens(messages: readonly Message[]): number;
10
+ export declare function compactionCut(entries: readonly Stored[]): number | undefined;
11
+ export declare function summarize(input: {
12
+ model: Model;
13
+ messages: readonly Message[];
14
+ prompt?: (messages: readonly Message[]) => string;
15
+ signal?: AbortSignal;
16
+ }): Promise<string | undefined>;
@@ -0,0 +1,55 @@
1
+ import { collect } from "../../../model/model.js";
2
+ import { textOf } from "../../../content.js";
3
+ /**
4
+ * Crude and deliberate: four characters per token, over the serialized request.
5
+ * A real count needs the provider's tokenizer, which would mean shipping one
6
+ * per provider to decide a threshold that is itself a guess. Being wrong here
7
+ * costs one early or late compaction, not correctness.
8
+ */
9
+ export function estimateTokens(messages) {
10
+ return messages.reduce((total, message) => total + JSON.stringify(message).length, 0) / 4;
11
+ }
12
+ /**
13
+ * The latest position that can be folded without separating a tool call from
14
+ * its results.
15
+ *
16
+ * Only two entry types are clean boundaries: a finished run, and an assistant
17
+ * turn that asked for nothing. Cutting anywhere else can leave one call of a
18
+ * batch summarized and its sibling live, which shows the model a result for a
19
+ * call it can no longer see.
20
+ */
21
+ /** How much of the tail is kept verbatim. A constant until a caller disagrees. */
22
+ const KEEP_FRACTION = 0.4;
23
+ export function compactionCut(entries) {
24
+ const boundary = Math.floor(entries.length * (1 - KEEP_FRACTION));
25
+ let cut;
26
+ for (const entry of entries.slice(0, boundary)) {
27
+ if (entry.type === "run.finished")
28
+ cut = entry.seq;
29
+ if (entry.type === "assistant" && !entry.calls?.length)
30
+ cut = entry.seq;
31
+ }
32
+ return cut;
33
+ }
34
+ /** What the summary must preserve. Overridable, because what matters is domain-specific. */
35
+ function summaryPrompt(messages) {
36
+ return [
37
+ "You are compacting an agent conversation that continues after this summary.",
38
+ "Summarize the transcript below faithfully and concisely, covering:",
39
+ "- Intent: the goal and the current state of the task",
40
+ "- Decisions: choices made so far and why",
41
+ "- Artifacts: files, outputs and results worth remembering (exact names, paths, values)",
42
+ "- Pending: unfinished work, next steps, open questions",
43
+ "",
44
+ messages.map((message) => `${message.role}: ${textOf(message.content)}`).join("\n\n"),
45
+ ].join("\n");
46
+ }
47
+ export async function summarize(input) {
48
+ const outcome = await collect(input.model.generate({
49
+ messages: [{ role: "user", content: (input.prompt ?? summaryPrompt)(input.messages) }],
50
+ maxOutputTokens: 2_000,
51
+ ...(input.signal ? { signal: input.signal } : {}),
52
+ }));
53
+ return outcome.ok ? textOf(outcome.value.message.content) : undefined;
54
+ }
55
+ //# sourceMappingURL=compaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compaction.js","sourceRoot":"","sources":["../../../../src/harness/adapters/native/compaction.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,QAA4B;IACzD,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;AAC5F,CAAC;AAED;;;;;;;;GAQG;AACH,kFAAkF;AAClF,MAAM,aAAa,GAAG,GAAG,CAAC;AAE1B,MAAM,UAAU,aAAa,CAAC,OAA0B;IACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC;IAClE,IAAI,GAAuB,CAAC;IAC5B,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC/C,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc;YAAE,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;QACnD,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM;YAAE,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;IAC1E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,4FAA4F;AAC5F,SAAS,aAAa,CAAC,QAA4B;IACjD,OAAO;QACL,6EAA6E;QAC7E,oEAAoE;QACpE,sDAAsD;QACtD,0CAA0C;QAC1C,wFAAwF;QACxF,wDAAwD;QACxD,EAAE;QACF,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;KACtF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,KAK/B;IACC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC;QACjD,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,MAAM,IAAI,aAAa,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtF,eAAe,EAAE,KAAK;QACtB,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAClD,CAAC,CAAC,CAAC;IACJ,OAAO,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACxE,CAAC","sourcesContent":["import type { Model, Message } from \"../../../model/model.js\";\nimport type { Stored } from \"../../../session/entry.js\";\nimport { collect } from \"../../../model/model.js\";\nimport { textOf } from \"../../../content.js\";\n\n/**\n * Crude and deliberate: four characters per token, over the serialized request.\n * A real count needs the provider's tokenizer, which would mean shipping one\n * per provider to decide a threshold that is itself a guess. Being wrong here\n * costs one early or late compaction, not correctness.\n */\nexport function estimateTokens(messages: readonly Message[]): number {\n return messages.reduce((total, message) => total + JSON.stringify(message).length, 0) / 4;\n}\n\n/**\n * The latest position that can be folded without separating a tool call from\n * its results.\n *\n * Only two entry types are clean boundaries: a finished run, and an assistant\n * turn that asked for nothing. Cutting anywhere else can leave one call of a\n * batch summarized and its sibling live, which shows the model a result for a\n * call it can no longer see.\n */\n/** How much of the tail is kept verbatim. A constant until a caller disagrees. */\nconst KEEP_FRACTION = 0.4;\n\nexport function compactionCut(entries: readonly Stored[]): number | undefined {\n const boundary = Math.floor(entries.length * (1 - KEEP_FRACTION));\n let cut: number | undefined;\n for (const entry of entries.slice(0, boundary)) {\n if (entry.type === \"run.finished\") cut = entry.seq;\n if (entry.type === \"assistant\" && !entry.calls?.length) cut = entry.seq;\n }\n return cut;\n}\n\n/** What the summary must preserve. Overridable, because what matters is domain-specific. */\nfunction summaryPrompt(messages: readonly Message[]): string {\n return [\n \"You are compacting an agent conversation that continues after this summary.\",\n \"Summarize the transcript below faithfully and concisely, covering:\",\n \"- Intent: the goal and the current state of the task\",\n \"- Decisions: choices made so far and why\",\n \"- Artifacts: files, outputs and results worth remembering (exact names, paths, values)\",\n \"- Pending: unfinished work, next steps, open questions\",\n \"\",\n messages.map((message) => `${message.role}: ${textOf(message.content)}`).join(\"\\n\\n\"),\n ].join(\"\\n\");\n}\n\nexport async function summarize(input: {\n model: Model;\n messages: readonly Message[];\n prompt?: (messages: readonly Message[]) => string;\n signal?: AbortSignal;\n}): Promise<string | undefined> {\n const outcome = await collect(input.model.generate({\n messages: [{ role: \"user\", content: (input.prompt ?? summaryPrompt)(input.messages) }],\n maxOutputTokens: 2_000,\n ...(input.signal ? { signal: input.signal } : {}),\n }));\n return outcome.ok ? textOf(outcome.value.message.content) : undefined;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export { createNativeHarness } from "./loop.js";
2
+ export type { NativeHarnessOptions } from "./loop.js";
@@ -0,0 +1,2 @@
1
+ export { createNativeHarness } from "./loop.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/harness/adapters/native/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC","sourcesContent":["export { createNativeHarness } from \"./loop.js\";\nexport type { NativeHarnessOptions } from \"./loop.js\";\n"]}
@@ -0,0 +1,25 @@
1
+ import type { Harness } from "../../harness.js";
2
+ import type { Message, Model } from "../../../model/model.js";
3
+ export interface NativeHarnessOptions {
4
+ model: Model;
5
+ /** Fold older turns into a summary once a request passes this size. */
6
+ compaction?: {
7
+ maxInputTokens: number;
8
+ /** Defaults to the main model. A cheaper one is usually the right call. */
9
+ model?: Model;
10
+ prompt?: (messages: readonly Message[]) => string;
11
+ };
12
+ maxOutputTokens?: number;
13
+ temperature?: number;
14
+ /** How hard the model should think, where the provider supports it. */
15
+ effort?: "low" | "medium" | "high" | "xhigh" | "max";
16
+ }
17
+ /**
18
+ * Ask the model, run what it asks for, repeat.
19
+ *
20
+ * The loop holds no transcript. Every turn it re-reads `context.history()`,
21
+ * which projects the committed log, and every result it produces goes back
22
+ * through `context.commit` before the next turn is built. There is exactly one
23
+ * representation of the conversation and it is the log.
24
+ */
25
+ export declare function createNativeHarness(options: NativeHarnessOptions): Harness;
@@ -0,0 +1,153 @@
1
+ import { foldedThrough, toMessages } from "../../../session/messages.js";
2
+ import { compactionCut, estimateTokens, summarize } from "./compaction.js";
3
+ /**
4
+ * Ask the model, run what it asks for, repeat.
5
+ *
6
+ * The loop holds no transcript. Every turn it re-reads `context.history()`,
7
+ * which projects the committed log, and every result it produces goes back
8
+ * through `context.commit` before the next turn is built. There is exactly one
9
+ * representation of the conversation and it is the log.
10
+ */
11
+ export function createNativeHarness(options) {
12
+ return {
13
+ id: "native",
14
+ toolUse: "application",
15
+ recovery: "history",
16
+ async run(context) {
17
+ const { runId } = context;
18
+ let compacting = true;
19
+ for (;;) {
20
+ if (context.signal.aborted)
21
+ return { status: "cancelled" };
22
+ let history = context.history();
23
+ if (compacting && options.compaction && estimateTokens(history) > options.compaction.maxInputTokens) {
24
+ const entries = context.entries();
25
+ const folded = foldedThrough(entries);
26
+ const cut = compactionCut(entries);
27
+ // Only ever fold forward. A cut at or behind the last summary removes
28
+ // nothing, so committing one would buy a model call and another entry
29
+ // and leave the request exactly as large — every turn, without end.
30
+ if (cut !== undefined && cut > folded) {
31
+ const summary = await summarize({
32
+ model: options.compaction.model ?? options.model,
33
+ // Exactly the span being replaced. Summarizing the whole history
34
+ // put everything after the cut into the summary as well, so it
35
+ // stayed in the request twice: once verbatim, once paraphrased.
36
+ messages: toMessages({
37
+ instructions: context.instructions,
38
+ entries: entries.filter((entry) => entry.seq <= cut),
39
+ }),
40
+ ...(options.compaction.prompt ? { prompt: options.compaction.prompt } : {}),
41
+ signal: context.signal,
42
+ });
43
+ if (!summary) {
44
+ // Paid for and produced nothing. Attempted once per activation
45
+ // rather than once per turn: retrying bought another model call
46
+ // and another failure every turn, and the request stayed exactly
47
+ // as large either way.
48
+ compacting = false;
49
+ }
50
+ if (summary) {
51
+ // The folded entries stay in the log. Compaction changes what the
52
+ // model is shown on the next turn, never what happened.
53
+ await context.commit([{ type: "summary", runId, content: summary, replaces: cut }]);
54
+ history = context.history();
55
+ }
56
+ }
57
+ }
58
+ // One cache mark, at the end of the system prefix: instructions plus
59
+ // run-scoped context, which are fixed for the life of the run.
60
+ const cacheAfter = history.findIndex((message) => message.role !== "system");
61
+ const startedAt = new Date().toISOString();
62
+ const generation = options.model.generate({
63
+ messages: history,
64
+ ...(context.tools ? { tools: context.tools.list() } : {}),
65
+ ...(options.maxOutputTokens !== undefined ? { maxOutputTokens: options.maxOutputTokens } : {}),
66
+ ...(options.temperature !== undefined ? { temperature: options.temperature } : {}),
67
+ ...(options.effort ? { effort: options.effort } : {}),
68
+ ...(cacheAfter > 0 ? { cacheAfter } : {}),
69
+ signal: context.signal,
70
+ });
71
+ let step = await generation.next();
72
+ while (!step.done) {
73
+ const delta = step.value;
74
+ if (delta.type === "text.delta")
75
+ context.emit({ type: "text.delta", text: delta.text });
76
+ if (delta.type === "reasoning.delta")
77
+ context.emit({ type: "reasoning.delta", text: delta.text });
78
+ if (delta.type === "tool-call.delta") {
79
+ context.emit({ type: "tool-call.delta", callId: delta.callId, arguments: delta.arguments });
80
+ }
81
+ step = await generation.next();
82
+ }
83
+ if (!step.value.ok) {
84
+ return step.value.error.code === "cancelled"
85
+ ? { status: "cancelled" }
86
+ : { status: "failed", error: step.value.error };
87
+ }
88
+ const endedAt = new Date().toISOString();
89
+ const response = step.value.value;
90
+ const calls = response.message.calls ?? [];
91
+ await context.commit([{
92
+ type: "assistant", runId, content: response.message.content,
93
+ ...(calls.length ? { calls } : {}),
94
+ usage: response.usage,
95
+ generation: { ...(response.model ? { model: response.model } : {}), startedAt, endedAt },
96
+ }]);
97
+ if (!calls.length) {
98
+ // The reason the model stopped decides the outcome, and it decides it
99
+ // before the absence of tool calls does. A turn cut off by the output
100
+ // ceiling or declined by a safety classifier is not an answer, and
101
+ // reporting either as `completed` hands the caller a truncated or
102
+ // refused response dressed as a finished one.
103
+ if (response.finishReason === "length") {
104
+ return { status: "failed", error: {
105
+ code: "output-truncated",
106
+ message: "The response stopped at the output limit before it was finished.",
107
+ retryable: false,
108
+ } };
109
+ }
110
+ if (response.finishReason === "refusal") {
111
+ return { status: "failed", error: {
112
+ code: "refused",
113
+ message: "The provider declined to answer.",
114
+ retryable: false,
115
+ } };
116
+ }
117
+ return { status: "completed", output: response.message.content };
118
+ }
119
+ if (response.finishReason === "length") {
120
+ // A truncated response can carry half-parsed arguments that happen to
121
+ // be valid JSON. None of the batch runs; the model is told why and
122
+ // can reissue.
123
+ await context.commit(calls.map((call) => ({
124
+ type: "tool.finished", runId, callId: call.callId,
125
+ result: { content: "Not executed: the response was truncated. Reissue the call if it is still needed.", isError: true },
126
+ })));
127
+ continue;
128
+ }
129
+ if (!context.tools) {
130
+ await context.commit(calls.map((call) => ({
131
+ type: "tool.finished", runId, callId: call.callId,
132
+ result: { content: `No tool named '${call.name}' is available.`, isError: true },
133
+ })));
134
+ continue;
135
+ }
136
+ // Recorded before anything runs. On restart an unmatched `tool.started`
137
+ // is how we know the process died mid-effect — and why nothing is
138
+ // re-executed automatically.
139
+ await context.commit(calls.map((call) => ({ type: "tool.started", runId, callId: call.callId })));
140
+ const outcome = await context.tools.execute({ calls, signal: context.signal });
141
+ await context.commit(outcome.results.map(({ callId, result }) => ({
142
+ type: "tool.finished", runId, callId, result,
143
+ })));
144
+ // Nothing is half-done here: every call asked for has been answered and
145
+ // committed. Anything waiting is folded in before the next request is
146
+ // built, so a message delivered to a busy session is read without the
147
+ // activation having to be ended and its work thrown away.
148
+ await context.drain?.();
149
+ }
150
+ },
151
+ };
152
+ }
153
+ //# sourceMappingURL=loop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop.js","sourceRoot":"","sources":["../../../../src/harness/adapters/native/loop.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Harness, HarnessContext, HarnessResult } from \"../../harness.js\";\nimport type { Entry } from \"../../../session/entry.js\";\nimport type { Message, Model } from \"../../../model/model.js\";\nimport { foldedThrough, toMessages } from \"../../../session/messages.js\";\nimport { compactionCut, estimateTokens, summarize } from \"./compaction.js\";\n\nexport interface NativeHarnessOptions {\n model: Model;\n /** Fold older turns into a summary once a request passes this size. */\n compaction?: {\n maxInputTokens: number;\n /** Defaults to the main model. A cheaper one is usually the right call. */\n model?: Model;\n prompt?: (messages: readonly Message[]) => string;\n };\n maxOutputTokens?: number;\n temperature?: number;\n /** How hard the model should think, where the provider supports it. */\n effort?: \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\";\n}\n\n/**\n * Ask the model, run what it asks for, repeat.\n *\n * The loop holds no transcript. Every turn it re-reads `context.history()`,\n * which projects the committed log, and every result it produces goes back\n * through `context.commit` before the next turn is built. There is exactly one\n * representation of the conversation and it is the log.\n */\nexport function createNativeHarness(options: NativeHarnessOptions): Harness {\n return {\n id: \"native\",\n toolUse: \"application\",\n recovery: \"history\",\n\n async run(context: HarnessContext): Promise<HarnessResult> {\n const { runId } = context;\n let compacting = true;\n\n for (;;) {\n if (context.signal.aborted) return { status: \"cancelled\" };\n\n let history = context.history();\n\n if (compacting && options.compaction && estimateTokens(history) > options.compaction.maxInputTokens) {\n const entries = context.entries();\n const folded = foldedThrough(entries);\n const cut = compactionCut(entries);\n\n // Only ever fold forward. A cut at or behind the last summary removes\n // nothing, so committing one would buy a model call and another entry\n // and leave the request exactly as large — every turn, without end.\n if (cut !== undefined && cut > folded) {\n const summary = await summarize({\n model: options.compaction.model ?? options.model,\n // Exactly the span being replaced. Summarizing the whole history\n // put everything after the cut into the summary as well, so it\n // stayed in the request twice: once verbatim, once paraphrased.\n messages: toMessages({\n instructions: context.instructions,\n entries: entries.filter((entry) => entry.seq <= cut),\n }),\n ...(options.compaction.prompt ? { prompt: options.compaction.prompt } : {}),\n signal: context.signal,\n });\n if (!summary) {\n // Paid for and produced nothing. Attempted once per activation\n // rather than once per turn: retrying bought another model call\n // and another failure every turn, and the request stayed exactly\n // as large either way.\n compacting = false;\n }\n if (summary) {\n // The folded entries stay in the log. Compaction changes what the\n // model is shown on the next turn, never what happened.\n await context.commit([{ type: \"summary\", runId, content: summary, replaces: cut }]);\n history = context.history();\n }\n }\n }\n\n // One cache mark, at the end of the system prefix: instructions plus\n // run-scoped context, which are fixed for the life of the run.\n const cacheAfter = history.findIndex((message) => message.role !== \"system\");\n\n const startedAt = new Date().toISOString();\n const generation = options.model.generate({\n messages: history,\n ...(context.tools ? { tools: context.tools.list() } : {}),\n ...(options.maxOutputTokens !== undefined ? { maxOutputTokens: options.maxOutputTokens } : {}),\n ...(options.temperature !== undefined ? { temperature: options.temperature } : {}),\n ...(options.effort ? { effort: options.effort } : {}),\n ...(cacheAfter > 0 ? { cacheAfter } : {}),\n signal: context.signal,\n });\n\n let step = await generation.next();\n while (!step.done) {\n const delta = step.value;\n if (delta.type === \"text.delta\") context.emit({ type: \"text.delta\", text: delta.text });\n if (delta.type === \"reasoning.delta\") context.emit({ type: \"reasoning.delta\", text: delta.text });\n if (delta.type === \"tool-call.delta\") {\n context.emit({ type: \"tool-call.delta\", callId: delta.callId, arguments: delta.arguments });\n }\n step = await generation.next();\n }\n if (!step.value.ok) {\n return step.value.error.code === \"cancelled\"\n ? { status: \"cancelled\" }\n : { status: \"failed\", error: step.value.error };\n }\n\n const endedAt = new Date().toISOString();\n const response = step.value.value;\n const calls = response.message.calls ?? [];\n\n await context.commit([{\n type: \"assistant\", runId, content: response.message.content,\n ...(calls.length ? { calls } : {}),\n usage: response.usage,\n generation: { ...(response.model ? { model: response.model } : {}), startedAt, endedAt },\n }]);\n\n if (!calls.length) {\n // The reason the model stopped decides the outcome, and it decides it\n // before the absence of tool calls does. A turn cut off by the output\n // ceiling or declined by a safety classifier is not an answer, and\n // reporting either as `completed` hands the caller a truncated or\n // refused response dressed as a finished one.\n if (response.finishReason === \"length\") {\n return { status: \"failed\", error: {\n code: \"output-truncated\",\n message: \"The response stopped at the output limit before it was finished.\",\n retryable: false,\n } };\n }\n if (response.finishReason === \"refusal\") {\n return { status: \"failed\", error: {\n code: \"refused\",\n message: \"The provider declined to answer.\",\n retryable: false,\n } };\n }\n return { status: \"completed\", output: response.message.content };\n }\n\n if (response.finishReason === \"length\") {\n // A truncated response can carry half-parsed arguments that happen to\n // be valid JSON. None of the batch runs; the model is told why and\n // can reissue.\n await context.commit(calls.map((call): Entry => ({\n type: \"tool.finished\", runId, callId: call.callId,\n result: { content: \"Not executed: the response was truncated. Reissue the call if it is still needed.\", isError: true },\n })));\n continue;\n }\n\n if (!context.tools) {\n await context.commit(calls.map((call): Entry => ({\n type: \"tool.finished\", runId, callId: call.callId,\n result: { content: `No tool named '${call.name}' is available.`, isError: true },\n })));\n continue;\n }\n\n // Recorded before anything runs. On restart an unmatched `tool.started`\n // is how we know the process died mid-effect — and why nothing is\n // re-executed automatically.\n await context.commit(calls.map((call): Entry => ({ type: \"tool.started\", runId, callId: call.callId })));\n\n const outcome = await context.tools.execute({ calls, signal: context.signal });\n await context.commit(outcome.results.map(({ callId, result }): Entry => ({\n type: \"tool.finished\", runId, callId, result,\n })));\n\n // Nothing is half-done here: every call asked for has been answered and\n // committed. Anything waiting is folded in before the next request is\n // built, so a message delivered to a busy session is read without the\n // activation having to be ended and its work thrown away.\n await context.drain?.();\n }\n },\n };\n}\n"]}
@@ -0,0 +1,129 @@
1
+ import type { Content } from "../content.js";
2
+ import type { Entry, Stored } from "../session/entry.js";
3
+ import type { Message } from "../model/model.js";
4
+ import type { ToolExecutor } from "../tools/tool.js";
5
+ import type { Failure } from "../result.js";
6
+ import type { JsonValue } from "../json.js";
7
+ /**
8
+ * Ephemeral output for a live viewer. Only committed entries are recovery state.
9
+ *
10
+ * An `entry` update carries the whole `Stored` shape — sequence and commit time
11
+ * both. Anything building a live projection off this stream needs the timestamp
12
+ * the log already has, and inventing one at receipt would put a second answer
13
+ * to the same question into the world.
14
+ */
15
+ export type Update = {
16
+ type: "text.delta";
17
+ text: string;
18
+ } | {
19
+ type: "reasoning.delta";
20
+ text: string;
21
+ } | {
22
+ type: "tool.progress";
23
+ callId: string;
24
+ data: JsonValue;
25
+ }
26
+ /** Arguments arriving a fragment at a time, so a viewer can show a call forming. */
27
+ | {
28
+ type: "tool-call.delta";
29
+ callId: string;
30
+ arguments: string;
31
+ } | {
32
+ type: "entry";
33
+ entry: Stored;
34
+ };
35
+ export interface HarnessContext {
36
+ sessionId: string;
37
+ runId: string;
38
+ instructions: Content;
39
+ /**
40
+ * The log projected for this turn. A function, not a snapshot: it is rebuilt
41
+ * from committed entries every time it is read, so a harness cannot hold a
42
+ * transcript that drifts from the log.
43
+ */
44
+ history(): readonly Message[];
45
+ /**
46
+ * The committed log itself. A harness needs it to reason in sequence numbers
47
+ * — where a compaction may cut, which call has no result yet — which the
48
+ * projection deliberately does not carry.
49
+ */
50
+ entries(): readonly Stored[];
51
+ /**
52
+ * Application context placed around the cache boundary. `run` is stable for
53
+ * the whole run and sits inside the cached prefix; `turn` is for this request
54
+ * only and sits after it. Getting that split right is the loop's job because
55
+ * only the loop knows where the prefix ends.
56
+ */
57
+ context?: {
58
+ run?: string;
59
+ turn?: string;
60
+ };
61
+ tools?: ToolExecutor;
62
+ /**
63
+ * Commit to the log. Returns when durable — a harness never holds
64
+ * uncommitted state.
65
+ *
66
+ * A refused write **rejects**, and a harness must not catch it: a lost
67
+ * compare-and-swap means another writer is at this position, and the run is
68
+ * over. Anything the agent was sending with that write goes with it.
69
+ */
70
+ commit(entries: readonly Entry[]): Promise<void>;
71
+ /**
72
+ * Fold any waiting input into this activation, and say how much arrived.
73
+ *
74
+ * Called at a boundary the harness picks — ours takes it after each batch of
75
+ * tool results, which is the last point at which nothing is half-done. This
76
+ * is the whole of `priority: "turn"`: a message reaching a busy session
77
+ * without the turn's work being thrown away to get its attention.
78
+ *
79
+ * A harness with no such point does not offer this, and a `"turn"` delivery
80
+ * to it waits for the next activation instead. It is never promoted to
81
+ * ending the one that is running — the sender asked for the place that costs
82
+ * nothing, and the answer to "I cannot" is later, not more destructive.
83
+ *
84
+ * Absent when there is no store, because then nothing can be waiting.
85
+ *
86
+ * Two things it will not do. It folds nothing into an activation that has
87
+ * been cancelled — a message taken off the queue by a run that is ending is a
88
+ * message nobody answers and nothing can find again. And a store it cannot
89
+ * read answers zero, the same as an empty queue: the failure is not hidden
90
+ * for long, because the next commit meets it.
91
+ */
92
+ drain?(): Promise<number>;
93
+ emit(update: Update): void;
94
+ signal: AbortSignal;
95
+ }
96
+ /**
97
+ * How one activation ended, and nothing about what it consumed.
98
+ *
99
+ * A harness used to report usage too, and that was a second answer to a
100
+ * question the log already answers: every generation's counts are on the
101
+ * `assistant` entry that carried it. `runAgent` sums those, so accounting is
102
+ * the same fact for a harness that owns its own loop as for ours, and a
103
+ * cancelled or failed activation still says what it spent.
104
+ */
105
+ export type HarnessResult = {
106
+ status: "completed";
107
+ output: Content;
108
+ } | {
109
+ status: "cancelled";
110
+ } | {
111
+ status: "failed";
112
+ error: Failure;
113
+ };
114
+ /**
115
+ * Executes one activation of an agent.
116
+ *
117
+ * Two facts, not eight. Everything the old capability matrix carried was either
118
+ * optional output, a different subsystem, or something no consumer asked about:
119
+ * a caller only ever needs to know whose tools run, and whether an unfinished
120
+ * activation can restart from the log.
121
+ */
122
+ export interface Harness {
123
+ readonly id: string;
124
+ /** "application": through the executor, validated and authorized. "harness": its own, which we never claim to have authorized. */
125
+ readonly toolUse: "application" | "harness" | "none";
126
+ /** "history": committed entries are enough to restart. "none": an interrupted run is over. */
127
+ readonly recovery: "history" | "none";
128
+ run(context: HarnessContext): Promise<HarnessResult>;
129
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=harness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness.js","sourceRoot":"","sources":["../../src/harness/harness.ts"],"names":[],"mappings":"","sourcesContent":["import type { Content } from \"../content.js\";\nimport type { Entry, Stored } from \"../session/entry.js\";\nimport type { Message } from \"../model/model.js\";\nimport type { ToolExecutor } from \"../tools/tool.js\";\nimport type { Failure } from \"../result.js\";\nimport type { JsonValue } from \"../json.js\";\n\n/**\n * Ephemeral output for a live viewer. Only committed entries are recovery state.\n *\n * An `entry` update carries the whole `Stored` shape — sequence and commit time\n * both. Anything building a live projection off this stream needs the timestamp\n * the log already has, and inventing one at receipt would put a second answer\n * to the same question into the world.\n */\nexport type Update =\n | { type: \"text.delta\"; text: string }\n | { type: \"reasoning.delta\"; text: string }\n | { type: \"tool.progress\"; callId: string; data: JsonValue }\n /** Arguments arriving a fragment at a time, so a viewer can show a call forming. */\n | { type: \"tool-call.delta\"; callId: string; arguments: string }\n | { type: \"entry\"; entry: Stored };\n\nexport interface HarnessContext {\n sessionId: string;\n runId: string;\n instructions: Content;\n /**\n * The log projected for this turn. A function, not a snapshot: it is rebuilt\n * from committed entries every time it is read, so a harness cannot hold a\n * transcript that drifts from the log.\n */\n history(): readonly Message[];\n /**\n * The committed log itself. A harness needs it to reason in sequence numbers\n * — where a compaction may cut, which call has no result yet — which the\n * projection deliberately does not carry.\n */\n entries(): readonly Stored[];\n /**\n * Application context placed around the cache boundary. `run` is stable for\n * the whole run and sits inside the cached prefix; `turn` is for this request\n * only and sits after it. Getting that split right is the loop's job because\n * only the loop knows where the prefix ends.\n */\n context?: { run?: string; turn?: string };\n tools?: ToolExecutor;\n /**\n * Commit to the log. Returns when durable — a harness never holds\n * uncommitted state.\n *\n * A refused write **rejects**, and a harness must not catch it: a lost\n * compare-and-swap means another writer is at this position, and the run is\n * over. Anything the agent was sending with that write goes with it.\n */\n commit(entries: readonly Entry[]): Promise<void>;\n /**\n * Fold any waiting input into this activation, and say how much arrived.\n *\n * Called at a boundary the harness picks — ours takes it after each batch of\n * tool results, which is the last point at which nothing is half-done. This\n * is the whole of `priority: \"turn\"`: a message reaching a busy session\n * without the turn's work being thrown away to get its attention.\n *\n * A harness with no such point does not offer this, and a `\"turn\"` delivery\n * to it waits for the next activation instead. It is never promoted to\n * ending the one that is running — the sender asked for the place that costs\n * nothing, and the answer to \"I cannot\" is later, not more destructive.\n *\n * Absent when there is no store, because then nothing can be waiting.\n *\n * Two things it will not do. It folds nothing into an activation that has\n * been cancelled — a message taken off the queue by a run that is ending is a\n * message nobody answers and nothing can find again. And a store it cannot\n * read answers zero, the same as an empty queue: the failure is not hidden\n * for long, because the next commit meets it.\n */\n drain?(): Promise<number>;\n emit(update: Update): void;\n signal: AbortSignal;\n}\n\n/**\n * How one activation ended, and nothing about what it consumed.\n *\n * A harness used to report usage too, and that was a second answer to a\n * question the log already answers: every generation's counts are on the\n * `assistant` entry that carried it. `runAgent` sums those, so accounting is\n * the same fact for a harness that owns its own loop as for ours, and a\n * cancelled or failed activation still says what it spent.\n */\nexport type HarnessResult =\n | { status: \"completed\"; output: Content }\n | { status: \"cancelled\" }\n | { status: \"failed\"; error: Failure };\n\n/**\n * Executes one activation of an agent.\n *\n * Two facts, not eight. Everything the old capability matrix carried was either\n * optional output, a different subsystem, or something no consumer asked about:\n * a caller only ever needs to know whose tools run, and whether an unfinished\n * activation can restart from the log.\n */\nexport interface Harness {\n readonly id: string;\n /** \"application\": through the executor, validated and authorized. \"harness\": its own, which we never claim to have authorized. */\n readonly toolUse: \"application\" | \"harness\" | \"none\";\n /** \"history\": committed entries are enough to restart. \"none\": an interrupted run is over. */\n readonly recovery: \"history\" | \"none\";\n run(context: HarnessContext): Promise<HarnessResult>;\n}\n"]}
@@ -0,0 +1,3 @@
1
+ export { createNativeHarness } from "./adapters/native/loop.js";
2
+ export type { NativeHarnessOptions } from "./adapters/native/loop.js";
3
+ export type { Harness, HarnessContext, HarnessResult, Update } from "./harness.js";
@@ -0,0 +1,2 @@
1
+ export { createNativeHarness } from "./adapters/native/loop.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/harness/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC","sourcesContent":["export { createNativeHarness } from \"./adapters/native/loop.js\";\nexport type { NativeHarnessOptions } from \"./adapters/native/loop.js\";\nexport type { Harness, HarnessContext, HarnessResult, Update } from \"./harness.js\";\n"]}
@@ -0,0 +1,10 @@
1
+ export { ok, err } from "./result.js";
2
+ export type { Result, Failure } from "./result.js";
3
+ export type { JsonValue } from "./json.js";
4
+ export { textOf } from "./content.js";
5
+ export type { Content, ContentPart, ContentSource } from "./content.js";
6
+ export { runAgent } from "./run.js";
7
+ export type { Agent, AgentRun, Arrival, RunAgentOptions, RunResult } from "./agent.js";
8
+ export { defineTool } from "./tools/tool.js";
9
+ export type { Decide, Tool, ToolContext, ToolExecutor } from "./tools/tool.js";
10
+ export type { Delivery, Entry, From, Stored, ToolCall, ToolResult, Usage } from "./session/entry.js";
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { ok, err } from "./result.js";
2
+ export { textOf } from "./content.js";
3
+ export { runAgent } from "./run.js";
4
+ export { defineTool } from "./tools/tool.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAGtC,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAGtC,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAGpC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC","sourcesContent":["export { ok, err } from \"./result.js\";\nexport type { Result, Failure } from \"./result.js\";\nexport type { JsonValue } from \"./json.js\";\nexport { textOf } from \"./content.js\";\nexport type { Content, ContentPart, ContentSource } from \"./content.js\";\n\nexport { runAgent } from \"./run.js\";\nexport type { Agent, AgentRun, Arrival, RunAgentOptions, RunResult } from \"./agent.js\";\n\nexport { defineTool } from \"./tools/tool.js\";\nexport type { Decide, Tool, ToolContext, ToolExecutor } from \"./tools/tool.js\";\n\nexport type { Delivery, Entry, From, Stored, ToolCall, ToolResult, Usage } from \"./session/entry.js\";\n"]}
package/dist/json.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ /** Anything that survives a round-trip through storage or a provider wire. */
2
+ export type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
3
+ readonly [key: string]: JsonValue;
4
+ };
package/dist/json.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=json.js.map