Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Changelog

## 0.135.1

### The caller-brain seam is production on `runGraph` (#694, option A)

`RunGraphOptions.brain` accepts a caller-owned `ToolLoopChat` on the `/kernel` production entry.
The root driver's inference becomes caller data: a deterministic conversation driver or a persona loop that makes its own LLM calls can drive a graph without a `/testing` detour, and the graph machinery around the seam — node pinning, directive delivery, the edge ledger, the journal twin — is the same shipped path.
This is the seam the multishot→`runGraph` consumer migrations require (issue #694, phase 4): strict-alternation driver orchestration must be caller-owned for the recorded artifacts to stay byte-compatible.

Graduation evidence per [docs/STABILITY.md](./docs/STABILITY.md):

- Tests: `tests/kernel/graph.test.ts` ("the caller-brain seam on the production surface") — a caller brain drives the 2-node graph to completion and the edge ledger plus its journal twin match the router-brained run row for row; the two refusals below are asserted; the router-brained default is proven unchanged.
- Curated doc: `docs/canonical-api.md` (the `/kernel` table's `runGraph(graph, { brain })` row).
- Consumer: `examples/graphs/user-sim-conversation.ts` runs the persona driver on the production surface end-to-end (`pnpm tsx examples/graphs/user-sim-conversation.ts`).
- Seam shape: `brain: ToolLoopChat` is unchanged since before 0.128.0; only its placement (test-only → production) moves in this release.

Contract details:

- Omitting `brain` leaves the router-brained default byte-identical: the root's model call derives from the root `AgentProfile` exactly as before.
- With a brain, the root profile keeps prompt control (`prompt-control-execution` materialization — `systemPrompt`/`instructions` still apply). Model selection, provider-identity validation (`expectedModel`), and per-turn usage reporting move to the caller.
- Fail-loud refusals before any compute: `brain` + `driverBackend` (two answers to who makes the root's model calls), and `brain` on a root whose profile declares an external harness (the harness IS that root's brain).
- `/kernel` re-exports `ToolLoopChat` / `ToolLoopCallContext` so a consumer can type its brain from the production entry.
- `supervise` and `supervisorAgent` keep refusing direct brain injection; the graduated surface is the graph root only. The `/testing` entry (`runGraphWithTestBrain`, `RunGraphTestOptions`) keeps working as an alias for tests written before this release.

## 0.135.0

### workerFromBackend honors `continuity: 'resume'` on the bridge backend
Expand Down
8 changes: 5 additions & 3 deletions docs/api/primitive-catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ Import from `@tangle-network/agent-runtime/intelligence` — 166 exports.

### Execution kernel — recursive atom, supervision, executors, round-synchronous loop

Import from `@tangle-network/agent-runtime/kernel` — 770 exports.
Import from `@tangle-network/agent-runtime/kernel` — 772 exports.

| Symbol | Kind | Summary |
|---|---|---|
Expand Down Expand Up @@ -1021,6 +1021,7 @@ Import from `@tangle-network/agent-runtime/kernel` — 770 exports.
| `SurfaceReadBox` | interface | The minimal box surface the box-backed reader needs — structurally typed so the real |
| `SurfaceWorkerConfig` | interface | How a worker runs the surface task (its router substrate + per-attempt bounds). |
| `SurfaceWorkerOut` | interface | What a surface worker settles with — the surface verdict the driver + deliverable read. `resolved` is |
| `ToolLoopCallContext` | interface | Runtime-owned identity and cancellation for one logical inference call. The wrapper is frozen |
| `ToolLoopCompaction` | interface | Self-compaction — bound the loop's OWN context window the way a fresh-respawn (dumb-Ralph) loop |
| `ToolLoopToolCall` | interface | One provider-neutral tool request emitted by a tool-loop model. |
| `TrajectoryNode` | interface | One node in the reconstructed trajectory tree — a driver OR a leaf, with its OWN spend and the |
Expand Down Expand Up @@ -1133,6 +1134,7 @@ Import from `@tangle-network/agent-runtime/kernel` — 770 exports.
| `SurfaceReader` | type | The read seam: fetch the current bytes at a mounted path. Implemented by a sandbox box's |
| `SurfaceReadOutcome` | type | Outcome of reading one surface back at settle. `missing: true` means the path no longer exists |
| `TerminalDecision` | type | One of the kernel's terminal decision values. |
| `ToolLoopChat` | type | One inference turn over the running conversation + the tool specs → the model's text, any |
| `ToolLoopCompactionOptions` | type | Public supervisor-facing compaction config: same knobs as the primitive, but `distill` is optional |
| `ToolLoopMessageRecord` | type | Provider-neutral conversation record accepted by a tool-loop brain. |
| `TrajectoryReportFn` | type | `trajectoryReport(...)` — the tree+cost reconstructor. Async (reads journal + optionally blobs). |
Expand Down Expand Up @@ -1382,11 +1384,11 @@ Import from `@tangle-network/agent-runtime/testing` — 14 exports.
| `driverAgent` | function | Build the intelligent recursive driver. Its `act` is the LLM tool-loop; spawn it as a |
| `loadAgentImprovementProposalFixture` | function | Load an isolated, production-validated Runtime proposal for consumer tests. |
| `loadAgentProfileImprovementFixture` | function | Load an isolated profile proposal and its private activation state for consumer tests. |
| `runGraphWithTestBrain` | function | Deterministic scripted-brain path for graph tests. Not exported from Runtime's main entry. |
| `runGraphWithTestBrain` | function | Alias for graph tests written before `RunGraphOptions.brain` was production. The production |
| `superviseWithTestBrain` | function | Deterministic scripted-brain path for tests. Not exported from Runtime's main entry. |
| `supervisorAgentWithTestBrain` | function | Scripted-brain construction for deterministic tests. Not exported from Runtime's main entry. |
| `AgentProfileImprovementFixture` | interface | Complete private state for exercising profile activation and restore in consumer tests. |
| `RunGraphTestOptions` | interface | Test-only graph options, exported only through the package's explicit `/testing` entry. |
| `RunGraphTestOptions` | interface | `RunGraphOptions` with the brain REQUIRED — the shape the `/testing` entry's |
| `SuperviseTestOptions` | interface | Test-only one-call shape, exported only through the package's explicit `/testing` entry. |
| `SupervisorAgentTestDeps` | interface | Test-only dependency shape. It is exported only through the package's explicit `/testing` |
| `ToolLoopCallContext` | interface | Runtime-owned identity and cancellation for one logical inference call. The wrapper is frozen |
Expand Down
65 changes: 65 additions & 0 deletions docs/api/runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -11471,6 +11471,23 @@ Leaf-execution override (offline tests / advanced). `runGraph` still owns node p

The driver brain's router substrate (`profile.harness` omitted or `cli-base`).

##### brain?

> `readonly` `optional` **brain?**: [`ToolLoopChat`](#toolloopchat)

The ROOT driver's inference seam — a caller-owned `ToolLoopChat` that makes every root
model call. Use it when the root's decisions must be caller-owned orchestration (a
deterministic conversation driver, a persona loop with its own LLM calls) rather than a
router-derived model call. The graph machinery around the seam is unchanged: node pinning,
directive delivery, the edge ledger, and the journal twin all run the same shipped path,
and the root profile keeps prompt control (`prompt-control-execution` materialization —
`systemPrompt`/`instructions` still apply). What moves to the caller with the brain:
model selection and provider-identity validation (`expectedModel` cannot be enforced on a
call the runtime did not place) and per-turn usage reporting (a brain that reports no
usage meters nothing into the pool). Omit = the router brain derived from the root
profile — the unchanged default. Mutually exclusive with `driverBackend`, and refused
when the root profile declares an external harness (that root is driven BY the harness).

##### hooks?

> `readonly` `optional` **hooks?**: [`RuntimeHooks`](index.md#runtimehooks)
Expand Down Expand Up @@ -17351,6 +17368,27 @@ Raw JSON arguments emitted by the model.

***

### ToolLoopCallContext

Runtime-owned identity and cancellation for one logical inference call. The wrapper is frozen
before dispatch; a transport may observe the signal but cannot replace the authority it names.

#### Properties

##### signal

> `readonly` **signal**: `AbortSignal`

##### callId

> `readonly` **callId**: `string`

##### correlationId

> `readonly` **correlationId**: `string`

***

### ToolLoopCompaction

Self-compaction — bound the loop's OWN context window the way a fresh-respawn (dumb-Ralph) loop
Expand Down Expand Up @@ -21016,6 +21054,33 @@ Provider-neutral conversation record accepted by a tool-loop brain.

***

### ToolLoopChat

> **ToolLoopChat** = (`messages`, `tools`, `context?`) => `Promise`\<\{ `content?`: `string` \| `null`; `toolCalls`: [`ToolLoopToolCall`](#toollooptoolcall)[]; `usage?`: \{ `input`: `number`; `output`: `number`; `reasoning?`: `number`; \}; `costUsd?`: `number`; `costProvenance?`: `"provider-receipt"` \| `"billing-receipt"` \| `"catalog-estimate"`; `usageUnknown?`: `true`; `model?`: `string`; `promptCache?`: `Readonly`\<`Record`\<`string`, `number` \| `string`\>\>; `transportAttempts?`: `number`; \}\>

One inference turn over the running conversation + the tool specs → the model's text, any
tool calls, and token usage. The seam every brain satisfies.

#### Parameters

##### messages

`ReadonlyArray`\<[`ToolLoopMessageRecord`](#toolloopmessagerecord)\>

##### tools

`ReadonlyArray`\<[`ToolSpec`](#toolspec)\>

##### context?

[`ToolLoopCallContext`](#toolloopcallcontext)

#### Returns

`Promise`\<\{ `content?`: `string` \| `null`; `toolCalls`: [`ToolLoopToolCall`](#toollooptoolcall)[]; `usage?`: \{ `input`: `number`; `output`: `number`; `reasoning?`: `number`; \}; `costUsd?`: `number`; `costProvenance?`: `"provider-receipt"` \| `"billing-receipt"` \| `"catalog-estimate"`; `usageUnknown?`: `true`; `model?`: `string`; `promptCache?`: `Readonly`\<`Record`\<`string`, `number` \| `string`\>\>; `transportAttempts?`: `number`; \}\>

***

### ToolLoopCompactionOptions

> **ToolLoopCompactionOptions** = `Omit`\<[`ToolLoopCompaction`](#toolloopcompaction), `"distill"`\> & `object`
Expand Down
91 changes: 37 additions & 54 deletions docs/api/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

##### brain

> `readonly` **brain**: [`ToolLoopChat`](#toolloopchat)
> `readonly` **brain**: [`ToolLoopChat`](runtime.md#toolloopchat)

The driver-LLM seam — ONE inference turn over the conversation + the coordination tool specs
(the canonical `ToolLoopChat`): a scripted mock offline, the router's tool-calling in
Expand Down Expand Up @@ -303,7 +303,8 @@ acknowledger (in-memory runs keep in-process control via handles).

### RunGraphTestOptions

Test-only graph options, exported only through the package's explicit `/testing` entry.
`RunGraphOptions` with the brain REQUIRED — the shape the `/testing` entry's
`runGraphWithTestBrain` keeps accepting now that `brain` is a production option.

#### Extends

Expand Down Expand Up @@ -524,7 +525,24 @@ Product authority over every steer/answer instruction (the filter seam). `runGra

##### brain

> `readonly` **brain**: [`ToolLoopChat`](#toolloopchat)
> `readonly` **brain**: [`ToolLoopChat`](runtime.md#toolloopchat)

The ROOT driver's inference seam — a caller-owned `ToolLoopChat` that makes every root
model call. Use it when the root's decisions must be caller-owned orchestration (a
deterministic conversation driver, a persona loop with its own LLM calls) rather than a
router-derived model call. The graph machinery around the seam is unchanged: node pinning,
directive delivery, the edge ledger, and the journal twin all run the same shipped path,
and the root profile keeps prompt control (`prompt-control-execution` materialization —
`systemPrompt`/`instructions` still apply). What moves to the caller with the brain:
model selection and provider-identity validation (`expectedModel` cannot be enforced on a
call the runtime did not place) and per-turn usage reporting (a brain that reports no
usage meters nothing into the pool). Omit = the router brain derived from the root
profile — the unchanged default. Mutually exclusive with `driverBackend`, and refused
when the root profile declares an external harness (that root is driven BY the harness).

###### Overrides

[`RunGraphOptions`](runtime.md#rungraphoptions).[`brain`](runtime.md#brain)

***

Expand Down Expand Up @@ -1277,7 +1295,7 @@ spans are telemetry, never the replay/resume record.

##### brain

> `readonly` **brain**: [`ToolLoopChat`](#toolloopchat)
> `readonly` **brain**: [`ToolLoopChat`](runtime.md#toolloopchat)

***

Expand Down Expand Up @@ -1701,28 +1719,7 @@ The durable run directory this manager acknowledges worker-scoped cancel request

##### brain

> `readonly` **brain**: [`ToolLoopChat`](#toolloopchat)

***

### ToolLoopCallContext

Runtime-owned identity and cancellation for one logical inference call. The wrapper is frozen
before dispatch; a transport may observe the signal but cannot replace the authority it names.

#### Properties

##### signal

> `readonly` **signal**: `AbortSignal`

##### callId

> `readonly` **callId**: `string`

##### correlationId

> `readonly` **correlationId**: `string`
> `readonly` **brain**: [`ToolLoopChat`](runtime.md#toolloopchat)

***

Expand Down Expand Up @@ -1750,33 +1747,6 @@ Complete private state for exercising profile activation and restore in consumer

## Type Aliases

### ToolLoopChat

> **ToolLoopChat** = (`messages`, `tools`, `context?`) => `Promise`\<\{ `content?`: `string` \| `null`; `toolCalls`: [`ToolLoopToolCall`](runtime.md#toollooptoolcall)[]; `usage?`: \{ `input`: `number`; `output`: `number`; `reasoning?`: `number`; \}; `costUsd?`: `number`; `costProvenance?`: `"provider-receipt"` \| `"billing-receipt"` \| `"catalog-estimate"`; `usageUnknown?`: `true`; `model?`: `string`; `promptCache?`: `Readonly`\<`Record`\<`string`, `number` \| `string`\>\>; `transportAttempts?`: `number`; \}\>

One inference turn over the running conversation + the tool specs → the model's text, any
tool calls, and token usage. The seam every brain satisfies.

#### Parameters

##### messages

`ReadonlyArray`\<[`ToolLoopMessageRecord`](runtime.md#toolloopmessagerecord)\>

##### tools

`ReadonlyArray`\<[`ToolSpec`](runtime.md#toolspec)\>

##### context?

[`ToolLoopCallContext`](#toolloopcallcontext)

#### Returns

`Promise`\<\{ `content?`: `string` \| `null`; `toolCalls`: [`ToolLoopToolCall`](runtime.md#toollooptoolcall)[]; `usage?`: \{ `input`: `number`; `output`: `number`; `reasoning?`: `number`; \}; `costUsd?`: `number`; `costProvenance?`: `"provider-receipt"` \| `"billing-receipt"` \| `"catalog-estimate"`; `usageUnknown?`: `true`; `model?`: `string`; `promptCache?`: `Readonly`\<`Record`\<`string`, `number` \| `string`\>\>; `transportAttempts?`: `number`; \}\>

***

### AgentProfileImprovementProposalFixture

> **AgentProfileImprovementProposalFixture** = `Omit`\<`AgentImprovementProposal`, `"evaluation"`\> & `object`
Expand Down Expand Up @@ -1814,7 +1784,8 @@ Build the intelligent recursive driver. Its `act` is the LLM tool-loop; spawn it

> **runGraphWithTestBrain**(`graph`, `opts`): `Promise`\<[`GraphResult`](runtime.md#graphresult)\<`unknown`\>\>

Deterministic scripted-brain path for graph tests. Not exported from Runtime's main entry.
Alias for graph tests written before `RunGraphOptions.brain` was production. The production
entry accepts the same shape; this wrapper only keeps the `/testing` import path working.

#### Parameters

Expand Down Expand Up @@ -1901,3 +1872,15 @@ Load an isolated profile proposal and its private activation state for consumer
#### Returns

[`AgentProfileImprovementFixture`](#agentprofileimprovementfixture)

## References

### ToolLoopCallContext

Re-exports [ToolLoopCallContext](runtime.md#toolloopcallcontext)

***

### ToolLoopChat

Re-exports [ToolLoopChat](runtime.md#toolloopchat)
1 change: 1 addition & 0 deletions docs/canonical-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ A general "loop" primitive is the single most common modelling error in this rep
| Run a supervisor toward a goal with default setup | `supervise(profile, task, { budget, backend? })`: `/kernel` | hand-wiring `createSupervisor().run` + `blobs`/`perWorker`/`journal`/`executors`; reaching for lower-level calls before you need a specific counterparty |
| Score a supervised sandbox worker by an executable check **against its live box** | `supervise(..., { backend: { backend: 'sandbox', sandboxClient, validator } })`: `/kernel` — the leaf forwards it to the composed `runAgentRounds`, which calls `validate(output, ctx)` with `ctx.box` still alive, and the verdict lands on the worker's settle | a post-settle hook (the box is destroyed by then), a second scoring loop beside `depthStrategy`, or pairing `validator` with `steering` (refused: a steerable session composes no loop to score) |
| Run a static root, workers, and analysts as reviewable `AgentProfile` nodes with versioned edge directives | `runGraph(graph, options)`: `/kernel` | a second graph executor, prompt-only roles, or pretending a static graph can discover new nodes while running |
| Drive a graph's ROOT with caller-owned orchestration (a deterministic conversation driver; a persona loop that makes its own LLM calls) | `runGraph(graph, { brain })`: `/kernel` — `brain: ToolLoopChat` is caller data for a router-brained root; node pinning, directive delivery, the edge ledger, and the journal twin stay the same shipped path, and the root profile keeps prompt control (`systemPrompt`/`instructions` still apply). Model selection, provider-identity validation, and usage reporting move to the caller with the brain | routing a production run through the `/testing` entry, a bespoke driver loop beside the graph, or pairing `brain` with `driverBackend` / an external-harness root (both refused: two answers to who makes the root's calls) |
| **Supervise agents to solve a graded `AgenticSurface` task** (workers `runAgentic` the surface, settle on its own check, driver self-improves from the failing tests) | `superviseSurface(profile, task, { surface, worker })`: `/kernel` | a worker-seam + a "self-improving supervisor" wrapper around `supervise()`; passing a custom `makeWorkerAgent` that runs `runAgentic` |
| Run a profile through a topology shape over the keystone Supervisor, end-to-end | `runPersonified({ persona, shape, task, budget })`: `/kernel` | a hand-rolled `createSupervisor().run` + seam-wiring helper |
| Address a supervisor run's durable state on disk, or steer a live worker from another process | `supervisorRunsRoot(root)` / `supervisorRunDir(root, id)` / `writeWorkerSteer(...)` / `readWorkerSteerRequests(...)`: `/kernel` — the `<root>/.agent/supervisor/<id>` contract `traces analyze --supervisor-run-dir` reads (`legacySupervisorRunDir` names the pre-rename `.loops` location for readers only) | inventing a run-dir layout, joining `.agent/supervisor` by hand, or writing a steer file whose shape no published reader knows |
Expand Down
Loading