@tangle-network/agent-runtime 0.89.0 → 0.90.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -18
- package/dist/agent.d.ts +53 -2
- package/dist/agent.js +168 -6
- package/dist/agent.js.map +1 -1
- package/dist/analyst-loop.d.ts +1 -1
- package/dist/chunk-5QOB7H74.js +387 -0
- package/dist/chunk-5QOB7H74.js.map +1 -0
- package/dist/{chunk-4WXGK6GV.js → chunk-7ON74BQO.js} +2 -2
- package/dist/{chunk-7LO5GMAO.js → chunk-CMYMTRGA.js} +14 -5
- package/dist/chunk-CMYMTRGA.js.map +1 -0
- package/dist/{chunk-UD4BHQMI.js → chunk-IVGYLCFH.js} +2 -2
- package/dist/{chunk-UD4BHQMI.js.map → chunk-IVGYLCFH.js.map} +1 -1
- package/dist/{chunk-VNOOH22O.js → chunk-OOL3675H.js} +85 -4
- package/dist/chunk-OOL3675H.js.map +1 -0
- package/dist/{chunk-5AVV7KAH.js → chunk-QK4DV5PR.js} +2 -2
- package/dist/chunk-R2VAJGR3.js +214 -0
- package/dist/chunk-R2VAJGR3.js.map +1 -0
- package/dist/{chunk-N7EJV7N3.js → chunk-RYBVU4M3.js} +3592 -3562
- package/dist/chunk-RYBVU4M3.js.map +1 -0
- package/dist/{chunk-BQPFZE2C.js → chunk-WRUSWK4F.js} +5 -5
- package/dist/{chunk-PIPPLSOF.js → chunk-ZV4LXYCJ.js} +451 -24
- package/dist/chunk-ZV4LXYCJ.js.map +1 -0
- package/dist/{worktree-harness-CX_McRLp.d.ts → completion-gate-DkAnUmpb.d.ts} +45 -45
- package/dist/{coordination-CuDLO8wj.d.ts → coordination-rRj5hjJK.d.ts} +415 -227
- package/dist/environment-provider.d.ts +3 -3
- package/dist/index.d.ts +255 -16
- package/dist/index.js +463 -72
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +228 -84
- package/dist/intelligence.js +259 -166
- package/dist/intelligence.js.map +1 -1
- package/dist/knowledge.d.ts +120 -0
- package/dist/knowledge.js +28 -0
- package/dist/knowledge.js.map +1 -0
- package/dist/lifecycle.d.ts +2 -2
- package/dist/lifecycle.js +2 -2
- package/dist/{local-harness-sI0S_XNA.d.ts → local-harness-dcD5WTTr.d.ts} +3 -0
- package/dist/{loop-runner-bin-hQDUL8Ld.d.ts → loop-runner-bin-DTbZVGfM.d.ts} +2 -2
- package/dist/loop-runner-bin.d.ts +6 -6
- package/dist/loop-runner-bin.js +7 -7
- package/dist/loops.d.ts +34 -369
- package/dist/loops.js +31 -5
- package/dist/mcp/bin.js +5 -5
- package/dist/mcp/index.d.ts +10 -11
- package/dist/mcp/index.js +10 -10
- package/dist/{mcp-serve-verifier-FL7-ZEb_.d.ts → mcp-serve-verifier-XsX8rkB9.d.ts} +12 -1
- package/dist/{openai-tools-BnrOmGjN.d.ts → openai-tools-C4ZfUD4L.d.ts} +1 -1
- package/dist/profiles.d.ts +196 -5
- package/dist/profiles.js +8 -0
- package/dist/profiles.js.map +1 -1
- package/dist/{router-client-r8y_VFVM.d.ts → router-client-DJImUDlm.d.ts} +1 -1
- package/dist/structural-rollout-MwlpgQ-6.d.ts +446 -0
- package/dist/supervise-DPmYPk0j.d.ts +131 -0
- package/dist/{types-ESeMOj94.d.ts → types-SyuwunY_.d.ts} +1 -1
- package/dist/{types-Driepl87.d.ts → types-eMNgWgFi.d.ts} +2 -2
- package/dist/{worktree-fanout-D6xR2CIA.d.ts → worktree-fanout-BDFQIO-Y.d.ts} +234 -234
- package/package.json +12 -4
- package/skills/agent-runtime-adoption/SKILL.md +74 -24
- package/skills/build-with-agent-runtime/SKILL.md +23 -20
- package/skills/loop-writer/SKILL.md +6 -5
- package/skills/supervise/SKILL.md +2 -2
- package/dist/chunk-7LO5GMAO.js.map +0 -1
- package/dist/chunk-N7EJV7N3.js.map +0 -1
- package/dist/chunk-PIPPLSOF.js.map +0 -1
- package/dist/chunk-VNOOH22O.js.map +0 -1
- package/dist/otel-export-BKmNwiCb.d.ts +0 -180
- /package/dist/{chunk-4WXGK6GV.js.map → chunk-7ON74BQO.js.map} +0 -0
- /package/dist/{chunk-5AVV7KAH.js.map → chunk-QK4DV5PR.js.map} +0 -0
- /package/dist/{chunk-BQPFZE2C.js.map → chunk-WRUSWK4F.js.map} +0 -0
|
@@ -54,10 +54,12 @@ Topology is the **one recursive agent tree**: each round an agent decides to ref
|
|
|
54
54
|
construction; the body is harness-re-verified, so an authored strategy can't
|
|
55
55
|
fabricate a win. Use when the right shape is task-dependent (scout-then-fanout,
|
|
56
56
|
refine-then-branch, decompose).
|
|
57
|
-
- **`createCoordinationTools`**
|
|
58
|
-
spawns / steers / awaits child agents
|
|
59
|
-
live `Scope`, recursively. Use when a
|
|
60
|
-
its workers in natural language.
|
|
57
|
+
- **`createCoordinationTools`** (from `@tangle-network/agent-runtime/mcp`) — the
|
|
58
|
+
agent-driving-agent loop: a driver agent spawns / steers / awaits child agents
|
|
59
|
+
(and sub-drivers) through MCP verbs over a live `Scope`, recursively. Use when a
|
|
60
|
+
driver should reason about and orchestrate its workers in natural language. From
|
|
61
|
+
`/loops` the equivalent surfaces are `serveCoordinationMcp` (the verbs as an HTTP
|
|
62
|
+
MCP over a live `Scope`) and the offline `driverAgent`.
|
|
61
63
|
|
|
62
64
|
Topology is **orthogonal to harness** — a strategy decides the shape; the executor
|
|
63
65
|
decides which harness (claude-code / codex / opencode / pi / router) runs each
|
|
@@ -100,6 +102,12 @@ agent-driving-agent loop), expose `createCoordinationTools` over a live `Scope`
|
|
|
100
102
|
|
|
101
103
|
- `runLoop` validates `ctx.sandboxClient.create` exists or throws
|
|
102
104
|
`ValidationError`. Never stub a `null` client.
|
|
105
|
+
- Build that client with `resolveSandboxClient({ backend })` (from
|
|
106
|
+
`@tangle-network/agent-runtime/loops`) — the one call that selects the sandbox /
|
|
107
|
+
bridge (cli-bridge) / router transport `runLoop` drives; do not hand-construct it.
|
|
108
|
+
Its sibling `resolveAgentBackend` is a DIFFERENT resolver — it resolves the CHAT
|
|
109
|
+
leg (`runChatThroughRuntime` / `runAgentTaskStream`) and returns an
|
|
110
|
+
`AgentExecutionBackend`, not a feeder for `resolveSandboxClient`.
|
|
103
111
|
- The kernel emits `loop.started / iteration.dispatch / iteration.ended /
|
|
104
112
|
decision / ended` via `ctx.traceEmitter`. Wire it to the same OTLP sink as the
|
|
105
113
|
chat path so loop telemetry is queryable.
|
|
@@ -108,17 +116,19 @@ agent-driving-agent loop), expose `createCoordinationTools` over a live `Scope`
|
|
|
108
116
|
- Dynamic driver: set the kernel's `runLoop` `maxIterations >=` the driver's so
|
|
109
117
|
the driver's cap governs and the loop closes on a clean `'done'`.
|
|
110
118
|
|
|
111
|
-
## Campaign bridge — `loopDispatch`
|
|
119
|
+
## Campaign bridge — `loopCampaignDispatch` / `loopDispatch`
|
|
112
120
|
|
|
113
121
|
To run `runLoop` as an agent-eval campaign cell, do NOT hand-build the ExecCtx +
|
|
114
122
|
forward trace + report usage every time (the third is silent — forgetting it
|
|
115
|
-
yields a `{0,0}` cell `assertRealBackend` reads as a stub). Use the
|
|
116
|
-
|
|
117
|
-
|
|
123
|
+
yields a `{0,0}` cell `assertRealBackend` reads as a stub). Use the bridge. Both
|
|
124
|
+
are exported from `src/runtime/index.ts` and are distinct sibling adapters, NOT a
|
|
125
|
+
rename: `loopCampaignDispatch` returns a `DispatchFn` for plain `runCampaign` /
|
|
126
|
+
`runEvalCampaign`; `loopDispatch` returns a `ProfileDispatchFn` and is the
|
|
127
|
+
`runProfileMatrix` variant (it adds the profile axis).
|
|
118
128
|
|
|
119
129
|
```ts
|
|
120
|
-
import {
|
|
121
|
-
const dispatch =
|
|
130
|
+
import { loopCampaignDispatch } from '@tangle-network/agent-runtime/loops'
|
|
131
|
+
const dispatch = loopCampaignDispatch({
|
|
122
132
|
sandboxClient,
|
|
123
133
|
toLoopOptions: (scenario, profile) => ({ driver, agentRun, output, validator, task: toTask(scenario) }),
|
|
124
134
|
// toArtifact? — defaults to result.winner?.output
|
|
@@ -126,19 +136,59 @@ const dispatch = loopDispatch({
|
|
|
126
136
|
// pass `dispatch` to runCampaign / runEvalCampaign; usage + trace are auto-forwarded
|
|
127
137
|
```
|
|
128
138
|
|
|
129
|
-
|
|
139
|
+
For the common shape — a fixed set of `cases` + a `prompt` builder + a `score`
|
|
140
|
+
fn, swept across profiles — prefer the declarative facade
|
|
141
|
+
`defineLeaderboard({ cases, prompt, score })` (from
|
|
142
|
+
`@tangle-network/agent-runtime/loops`). It composes
|
|
143
|
+
`expandProfileAxes × loopDispatch × naiveDriver` into one call, exposes
|
|
144
|
+
`.run(argv?)` (CLI-flag parsing + matrix) and `.toBenchmarkAdapter()`, and yields
|
|
145
|
+
a ranked leaderboard. Reach for raw `loopDispatch` only when a cell needs a custom
|
|
146
|
+
driver/validator.
|
|
130
147
|
|
|
131
|
-
## Identity-gated optimization — agent-eval's `selfImprove`
|
|
148
|
+
## Identity-gated optimization — agent-runtime's `improve()` (facade over agent-eval's `selfImprove`)
|
|
132
149
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
150
|
+
**Start with `improve()`** — the one pluggable RSI verb, exported at the
|
|
151
|
+
`@tangle-network/agent-runtime` package ROOT (its own header: "the ONE public,
|
|
152
|
+
surface-pluggable RSI verb. A thin facade over agent-eval's `selfImprove`"). Real
|
|
153
|
+
signature is 3-arg, NOT a single options object:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
improve<TScenario, TArtifact>(
|
|
157
|
+
profile: AgentProfile,
|
|
158
|
+
findings: unknown[],
|
|
159
|
+
opts: ImproveOptions,
|
|
160
|
+
): Promise<ImproveResult>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`opts`: `surface?: 'prompt'|'skills'|'tools'|'mcp'|'hooks'|'code'` (default
|
|
164
|
+
`'prompt'`), `scenarios`, `judge`, `agent`, `gate?: 'holdout'|'none'` (default
|
|
165
|
+
`'holdout'`; `'none'` forces `generations = 0`), plus `budget?` / `llm?` /
|
|
166
|
+
`generator?` / `code?` / `skills?` / `runDir?`. It picks the default proposer for
|
|
167
|
+
the surface (`gepaProposer` for `'prompt'`, `skillOptProposer` for `'skills'`;
|
|
168
|
+
`'code'`/`'tools'`/`'mcp'`/`'hooks'` throw `ConfigError` unless you pass
|
|
169
|
+
`opts.generator` or `opts.code`), extracts the baseline from the profile, runs
|
|
170
|
+
`selfImprove` with the held-out gate, and on a ship verdict writes the winner back
|
|
171
|
+
into the profile field. Returns `ImproveResult { profile, shipped, lift,
|
|
172
|
+
gateDecision, raw }` — deploy with `if (out.shipped) deploy(out.profile)`:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import { improve } from '@tangle-network/agent-runtime'
|
|
176
|
+
const out = await improve(profile, findings, {
|
|
177
|
+
surface: 'prompt', scenarios, judge, agent, gate: 'holdout', llm,
|
|
178
|
+
})
|
|
179
|
+
if (out.shipped) deploy(out.profile)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Drop to `selfImprove`** (`@tangle-network/agent-eval/contract`) only when you
|
|
183
|
+
need finer control — a custom proposer/gate, or the code-surface git-worktree path
|
|
184
|
+
via agent-runtime's `improvementDriver` (`/improvement`), which you pass to it as
|
|
185
|
+
`proposer` to optimize CODE instead of a string. `selfImprove` optimizes any
|
|
186
|
+
text/config surface (system / planner / judge rubric) and is **identity-gated by
|
|
187
|
+
construction**: it runs evals, proposes candidates (default proposer
|
|
188
|
+
`gepaProposer`), and a held-out gate ships a winner only if it beats the baseline.
|
|
189
|
+
`result.winner.surface` is the **baseline unless `result.gateDecision === 'ship'`**
|
|
190
|
+
— so registering a surface for optimization can never regress it; it only improves
|
|
191
|
+
when held-out data earns it.
|
|
142
192
|
|
|
143
193
|
```ts
|
|
144
194
|
import { selfImprove } from '@tangle-network/agent-eval/contract'
|
|
@@ -148,8 +198,8 @@ const result = await selfImprove({
|
|
|
148
198
|
scenarios,
|
|
149
199
|
judge,
|
|
150
200
|
budget: { holdoutScenarios, generations: 3, populationSize: 2 },
|
|
151
|
-
llm: { baseUrl, apiKey, model: REFLECTION_MODEL }, // drives the default
|
|
152
|
-
//
|
|
201
|
+
llm: { baseUrl, apiKey, model: REFLECTION_MODEL }, // drives the default gepaProposer
|
|
202
|
+
// proposer? — pass agent-runtime's improvementDriver to optimize CODE (worktree) instead of a string
|
|
153
203
|
// gate? — defaults to a held-out gate; pass defaultProductionGate for red-team hardening
|
|
154
204
|
})
|
|
155
205
|
// use result.winner.surface unconditionally: it's the baseline until a candidate genuinely wins
|
|
@@ -157,7 +207,7 @@ const result = await selfImprove({
|
|
|
157
207
|
|
|
158
208
|
### selfImprove gotchas — read before wiring
|
|
159
209
|
|
|
160
|
-
- **`
|
|
210
|
+
- **`gepaProposer` mutates TEXT only**, and its only structural guard is `##` H2
|
|
161
211
|
headings (`preserveSections`) + `maxSentenceEdits`. Make load-bearing sections
|
|
162
212
|
of your prompt real `##` headings, and treat the output schema as fixed code —
|
|
163
213
|
GEPA optimizes the prose, never the envelope/contract.
|
|
@@ -92,14 +92,14 @@ to its native default (`HARNESS_NATIVE_MODEL`) — never silently dropped.
|
|
|
92
92
|
| **Spawn N coding agents on isolated git worktrees, keep the one whose patch passes checks** | `worktreeFanout` + `createWorktreeCliExecutor` + `gateOnDeliverable(DeliverableSpec)` over a raw `WorktreePatchArtifact`, winner via `selectValidWinner` — `/loops` — NOT a hand-rolled spawn-loop / "coder" role | canonical-api §3.1 / §5 |
|
|
93
93
|
| **Sandbox coding rollout** (fresh box/round, or persistent+resume) | `runLoop(options)` / `openSandboxRun(client, opts, deliverable)` — `/loops` | canonical-api §3.1 |
|
|
94
94
|
| **Optimize a CODE surface** in a gated loop | `improvementDriver({ worktree, generator })` — root `.` | canonical-api §3.4 |
|
|
95
|
-
| **Optimize a PROMPT/config surface** (one call) | `selfImprove({ agent, scenarios, judge, baselineSurface })`
|
|
96
|
-
| **Gate: ship/hold a candidate** (campaign ctx) | `defaultProductionGate` / `heldOutGate` / `composeGate` — `agent-eval/contract` | canonical-api §3.4 |
|
|
95
|
+
| **Optimize a PROMPT/config surface** (one call) — START HERE | `improve(profile, findings, { surface, gate })` — root `.` (the one pluggable RSI verb; picks the default proposer from `surface` — `gepaProposer` for prompt, `skillOptProposer` for skills — and wraps `selfImprove`; drop to `selfImprove({ agent, scenarios, judge, baselineSurface })` from `agent-eval/contract` only for the lower-level loop) | canonical-api §3.4 |
|
|
96
|
+
| **Gate: ship/hold a candidate** (campaign ctx) | `defaultProductionGate` / `heldOutGate` / `composeGate` — `agent-eval/contract`; `neutralizationGate` (footprint-matched PLACEBO gate — proves a held-out lift is CONTENT, not added prompt/mount footprint) — `agent-eval/campaign` | canonical-api §3.4 |
|
|
97
97
|
| **Gate: ship/hold from a `BenchmarkReport`** (per-task cells) | `promotionGate({ report, incumbent, candidate })` — `/loops` | canonical-api §3.4 |
|
|
98
98
|
| **Run the full multi-generation flywheel + certify** | `runStrategyEvolution(config)` — `/loops` | canonical-api §3.4 |
|
|
99
99
|
| **Observe a run** (cost/time waterfall, OTLP) | `createWaterfallCollector()` — `/loops`; `createOtelExporter` attached via `composeRuntimeHooks(...)` — root `.` | canonical-api §2 |
|
|
100
100
|
| **State any A/B claim** | `pairedLift` (bench) over `pairedBootstrap`/`heldoutSignificance` (substrate) | canonical-api §3.5 |
|
|
101
|
-
| **Observe
|
|
102
|
-
| **Pull the certified profile from the Intelligence plane** (pull-by-default delivery: fold the gate-certified prompt onto the base surface) | `pullCertified` / `
|
|
101
|
+
| **Observe + deliver on a live agent with billing-boundary** | `withIntelligence(agent, { project, target, effort })` — `/intelligence` | canonical-api §2 |
|
|
102
|
+
| **Pull the certified profile from the Intelligence plane** (pull-by-default delivery: fold the gate-certified prompt onto the base surface) | `pullCertified` / `withIntelligence` / `composeCertifiedPrompt` — `/intelligence` | `src/intelligence/delivery.ts` |
|
|
103
103
|
|
|
104
104
|
## Do-NOT-reinvent — the traps this skill exists to stop
|
|
105
105
|
|
|
@@ -110,7 +110,7 @@ holds the load-bearing invariant the parallel breaks:
|
|
|
110
110
|
`loopUntil` + `runPersonified` (threads executor seams; equal-k; selector≠judge
|
|
111
111
|
firewall; journal/replay — a parallel runner silently fails to wire the seams).
|
|
112
112
|
- "skill optimizer" / "topology mutator" that opens branches + applies patches
|
|
113
|
-
**≈** `improvementDriver` (code surface) or `selfImprove`/`
|
|
113
|
+
**≈** `improvementDriver` (code surface) or `selfImprove`/`gepaProposer` (prompt
|
|
114
114
|
surface) — both gated on a frozen holdout.
|
|
115
115
|
- "profile-seam" / agent-config wrapper carrying model+prompt+tools+role **≈**
|
|
116
116
|
`AgentProfile` (it IS that bundle) + `definePersona` (the run record);
|
|
@@ -177,26 +177,29 @@ sandbox coding rollouts against external benchmarks. The full when-which map is
|
|
|
177
177
|
|
|
178
178
|
## Observe / ship with the Intelligence SDK
|
|
179
179
|
|
|
180
|
-
One
|
|
181
|
-
`
|
|
180
|
+
One hook wraps any agent with send + receive + billing boundary:
|
|
181
|
+
`withIntelligence(agent, { project, target, effort })`, `effort ∈
|
|
182
182
|
off|eco|standard|thorough|max` (`'off'` is the provable passthrough floor —
|
|
183
|
-
intelligence spend clamped to 0). It
|
|
184
|
-
|
|
185
|
-
|
|
183
|
+
intelligence spend clamped to 0). It SENDs a typed `RunRecord` and RECEIVEs the
|
|
184
|
+
certified profile, building on `createOtelExporter` + `buildLoopOtelSpans` —
|
|
185
|
+
don't hand-roll a trace-wrapper, a second receive path, or effort/tier config.
|
|
186
|
+
Verify the live subpath against `src/intelligence/with-intelligence.ts`.
|
|
186
187
|
|
|
187
188
|
Two operational facts every consumer must know:
|
|
188
189
|
|
|
189
|
-
- **
|
|
190
|
-
when `
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
190
|
+
- **Send is a silent no-op without a tenant apiKey.** The export leg only ships
|
|
191
|
+
when an `apiKey` (or `TANGLE_API_KEY`) is present — the ingest requires the
|
|
192
|
+
tenant Bearer. One `baseUrl` (`TANGLE_INTELLIGENCE_URL`, default
|
|
193
|
+
`https://intelligence.tangle.tools`) drives both the OTLP send (`/v1/otlp`)
|
|
194
|
+
and the receive pull. The client's `doctor().exportConfigured` is the check
|
|
195
|
+
that a send will actually land.
|
|
194
196
|
- **Delivery pulls the certified profile from the plane.** `pullCertified` /
|
|
195
|
-
`
|
|
196
|
-
`GET {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
`withIntelligence` hit
|
|
198
|
+
`GET {baseUrl}/v1/profiles/:target/composed` with `Bearer TANGLE_API_KEY`;
|
|
199
|
+
the pull deserializes the typed `agentProfileDiffs` (surfaced as PROPOSALS,
|
|
200
|
+
never auto-applied) alongside the certified prompt. `withIntelligence` folds
|
|
201
|
+
the certified prompt onto the base surface, refreshes at most every 5 minutes,
|
|
202
|
+
and is fail-closed — a failed pull runs the agent on its base surface.
|
|
200
203
|
|
|
201
204
|
## Final check
|
|
202
205
|
|
|
@@ -30,7 +30,8 @@ The driver owns strategy.
|
|
|
30
30
|
| Review from several lenses | `panel` |
|
|
31
31
|
| Simulated user/product eval | `defineConversation` + `runConversation` |
|
|
32
32
|
| Dynamic topology / drivers of drivers | `Scope` or sandbox driver + `createCoordinationTools` |
|
|
33
|
-
|
|
|
33
|
+
| Run N coding workers on isolated worktrees, gate each, pick best patch | `worktreeFanout` |
|
|
34
|
+
| Mutate a shared repo | git branch/clone loop with typed merge outcomes (`gitWorkspace` seam) |
|
|
34
35
|
|
|
35
36
|
If a fixed combinator solves it, do not use a dynamic driver.
|
|
36
37
|
|
|
@@ -110,9 +111,9 @@ const result = await createSupervisor<Task, Output>().run(driver, task, supervis
|
|
|
110
111
|
```
|
|
111
112
|
|
|
112
113
|
When the driver lives in a sandbox, expose the same verbs through
|
|
113
|
-
`createCoordinationTools`: `
|
|
114
|
-
`
|
|
115
|
-
optional analyst tools.
|
|
114
|
+
`createCoordinationTools`: `spawn_agent`, `await_event`, `observe_agent`,
|
|
115
|
+
`steer_agent`, `list_questions`, `answer_question`, `ask_parent`, `stop`, and
|
|
116
|
+
optional analyst tools (`list_analysts`, `run_analyst`).
|
|
116
117
|
|
|
117
118
|
## Role Boundaries
|
|
118
119
|
|
|
@@ -133,7 +134,7 @@ with unresolved `blocks-run` questions.
|
|
|
133
134
|
Steer sparingly: only when an analyst finds a concrete mistake, a loop is
|
|
134
135
|
duplicating work, a parent/Pi answers a blocker, or a verifier reveals a specific
|
|
135
136
|
fix a running worker can still use. Delivery is through `Scope.send` or
|
|
136
|
-
`
|
|
137
|
+
`steer_agent`; failed delivery means spawn a fresh corrected attempt.
|
|
137
138
|
|
|
138
139
|
## Workspace Loops
|
|
139
140
|
|
|
@@ -10,7 +10,7 @@ You are a supervisor. You do NOT do the work yourself — you design and drive s
|
|
|
10
10
|
## Loop
|
|
11
11
|
|
|
12
12
|
1. **Decompose** the task into the smallest set of sub-tasks a single focused worker can each deliver.
|
|
13
|
-
2. **Author** a worker per sub-task by calling `
|
|
13
|
+
2. **Author** a worker per sub-task by calling `spawn_agent` with a complete `profile`:
|
|
14
14
|
- `name` — a short id.
|
|
15
15
|
- `skills` — the skill files the worker should carry (by name), OR `systemPrompt` — rich, specific instructions for this sub-task.
|
|
16
16
|
- `model` — the model best suited to this sub-task (optional).
|
|
@@ -21,4 +21,4 @@ You are a supervisor. You do NOT do the work yourself — you design and drive s
|
|
|
21
21
|
|
|
22
22
|
## Authoring sub-supervisors
|
|
23
23
|
|
|
24
|
-
If a sub-task is itself too large for one worker, author it as a **sub-supervisor**: give its profile a `skills` list that includes `supervise`. It will decompose and drive its own workers one level deeper. This is not a special call — it is the same `
|
|
24
|
+
If a sub-task is itself too large for one worker, author it as a **sub-supervisor**: give its profile a `skills` list that includes `supervise`. It will decompose and drive its own workers one level deeper. This is not a special call — it is the same `spawn_agent`, just a profile that carries this skill.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mcp/local-harness.ts"],"sourcesContent":["/**\n *\n * Subprocess wrappers for the local coding-harness CLIs installed in the\n * sandbox image (claude-code, codex, opencode). Used by the in-process\n * delegation executor (`createInProcessExecutor`) so a delegated coding task\n * spawns a real harness on a real git worktree instead of provisioning a\n * sibling sandbox.\n *\n * All harness invocations:\n * - run with `cwd` set to the worktree\n * - inherit env from the parent (the MCP server inside the sandbox has\n * the harness's auth already)\n * - capture stdout/stderr\n * - support cancellation via AbortSignal\n * - enforce a wall-clock timeout\n *\n * @experimental\n */\n\nimport { type ChildProcess, spawn } from 'node:child_process'\nimport type { AgentProfile } from '@tangle-network/agent-interface'\n\n/** Local coding harness available inside the sandbox. */\nexport type LocalHarness = 'claude' | 'codex' | 'opencode'\n\n/**\n * Default per-harness command + arg shape. `buildArgs` takes ONLY the task prompt and\n * emits the prompt-only invocation (no model, no system prompt) — the historical shape\n * the in-process executor's `streamPrompt` drives. `modelArgs` maps a resolved model to\n * the harness's selector flag (every supported harness takes `-m <model>`). The §1.5\n * profile-aware mapper `harnessInvocation` composes these to thread the full\n * supervisor-authored profile (systemPrompt + model) into argv.\n */\nconst HARNESS_INVOCATIONS: Record<\n LocalHarness,\n {\n command: string\n buildArgs: (taskPrompt: string) => string[]\n /** Map a resolved model to the harness's model-selector flag. */\n modelArgs: (model: string) => string[]\n }\n> = {\n claude: {\n command: 'claude',\n buildArgs: (taskPrompt) => ['--headless', '-p', taskPrompt],\n modelArgs: (model) => ['-m', model],\n },\n codex: {\n command: 'codex',\n buildArgs: (taskPrompt) => ['run', taskPrompt],\n modelArgs: (model) => ['-m', model],\n },\n opencode: {\n command: 'opencode',\n buildArgs: (taskPrompt) => ['run', taskPrompt],\n modelArgs: (model) => ['-m', model],\n },\n}\n\n/** Result of mapping an `AgentProfile` + task prompt onto a harness invocation. */\nexport interface HarnessInvocation {\n command: string\n args: string[]\n}\n\n/**\n * Map a supervisor-authored `AgentProfile` + the per-task prompt onto a concrete harness\n * `command` + `args` (the §1.5 fix). UNLIKE the prompt-only `HARNESS_INVOCATIONS.buildArgs`\n * — which drops both the authored model and the system prompt — this threads the FULL\n * profile payload into argv:\n *\n * - `profile.prompt.systemPrompt` → the PROMPT channel: a portable, harness-agnostic\n * default that prepends the system prompt above the task prompt (`<system>\\n\\n<task>`),\n * so the authored standing instructions reach EVERY harness (none of the three CLIs\n * expose a portable replace-system-prompt flag for a one-shot non-interactive run).\n * - `profile.model.default` → the harness's `-m <model>` selector.\n *\n * The task prompt alone is the floor; an empty/absent profile yields exactly the legacy\n * `buildArgs(taskPrompt)` shape so existing callers are byte-identical.\n */\nexport function harnessInvocation(\n harness: LocalHarness,\n profile: AgentProfile,\n taskPrompt: string,\n): HarnessInvocation {\n const invocation = HARNESS_INVOCATIONS[harness]\n if (!invocation) {\n throw new Error(`harnessInvocation: unknown harness ${String(harness)}`)\n }\n\n const systemPrompt = profile.prompt?.systemPrompt\n const composedPrompt =\n typeof systemPrompt === 'string' && systemPrompt.trim().length > 0\n ? `${systemPrompt}\\n\\n${taskPrompt}`\n : taskPrompt\n\n const args = invocation.buildArgs(composedPrompt)\n\n const model = profile.model?.default\n if (typeof model === 'string' && model.length > 0) {\n args.push(...invocation.modelArgs(model))\n }\n\n return { command: invocation.command, args }\n}\n\n/** @experimental */\nexport interface RunLocalHarnessOptions {\n harness: LocalHarness\n /** Working directory for the subprocess (typically a worktree path). */\n cwd: string\n /** Prompt forwarded as the harness CLI's task argument. */\n taskPrompt: string\n /**\n * Pre-built command + args (e.g. from `harnessInvocation` so the full authored\n * `AgentProfile` — systemPrompt + model — reaches the harness). When set it OVERRIDES the\n * default prompt-only `buildArgs(taskPrompt)` path; `command` defaults to the harness's\n * default binary when only `args` is supplied. When absent the legacy prompt-only shape\n * is used unchanged.\n */\n invocation?: { command?: string; args: ReadonlyArray<string> }\n /** Wall-clock kill deadline (ms). Default 5 min. Subprocess SIGTERMed on expiry. */\n timeoutMs?: number\n /** Caller cancellation. SIGTERM is sent on abort. */\n signal?: AbortSignal\n /** Override env (defaults to inheriting from the parent). */\n env?: NodeJS.ProcessEnv\n /**\n * Test seam — inject a custom spawner so unit tests can mock the\n * subprocess without touching the OS. Defaults to node's `child_process.spawn`.\n */\n spawn?: (\n command: string,\n args: ReadonlyArray<string>,\n opts: {\n cwd: string\n env: NodeJS.ProcessEnv\n stdio: 'pipe'\n },\n ) => ChildProcess\n}\n\n/** @experimental */\nexport interface LocalHarnessResult {\n /** OS exit code. `null` when killed before exit. */\n exitCode: number | null\n /** Concatenated stdout. */\n stdout: string\n /** Concatenated stderr. */\n stderr: string\n /** Set when the process exited via signal (timeout / abort). */\n killedBySignal: NodeJS.Signals | null\n /** Wall-clock duration ms (spawn → exit). */\n durationMs: number\n /** Set when timeoutMs elapsed before exit. */\n timedOut: boolean\n}\n\nconst DEFAULT_TIMEOUT_MS = 5 * 60 * 1000\n\n/**\n * Spawn a local coding harness CLI as a subprocess + collect its output.\n *\n * NOT responsible for parsing the harness's output or extracting a diff —\n * the in-process executor's `streamPrompt` orchestrates `git diff` against\n * the worktree after this resolves. This function is intentionally narrow:\n * spawn, wait, capture, return.\n *\n * Fails loud — throws when:\n * - `cwd` doesn't exist (subprocess emits ENOENT; surfaced as Error)\n * - the harness binary is not on PATH (ENOENT)\n *\n * Does NOT throw when:\n * - the subprocess exits non-zero (`result.exitCode` carries the code)\n * - the subprocess is aborted / timed out (`result.killedBySignal` /\n * `result.timedOut` carries the reason)\n *\n * @experimental\n */\nexport function runLocalHarness(options: RunLocalHarnessOptions): Promise<LocalHarnessResult> {\n const { harness, cwd, taskPrompt } = options\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS\n const env = options.env ?? process.env\n const spawnImpl = options.spawn ?? spawn\n\n const invocation = HARNESS_INVOCATIONS[harness]\n if (!invocation) {\n return Promise.reject(new Error(`runLocalHarness: unknown harness ${String(harness)}`))\n }\n\n const startedAt = Date.now()\n const command = options.invocation?.command ?? invocation.command\n const args = options.invocation ? [...options.invocation.args] : invocation.buildArgs(taskPrompt)\n\n return new Promise<LocalHarnessResult>((resolve, reject) => {\n let child: ChildProcess\n try {\n child = spawnImpl(command, args, { cwd, env, stdio: 'pipe' })\n } catch (err) {\n reject(err instanceof Error ? err : new Error(String(err)))\n return\n }\n\n // The harness takes its task as an argv arg, not on stdin. Leaving stdin\n // OPEN makes a non-TTY `opencode run` (and likely the other harnesses)\n // BLOCK forever waiting on input — zero output, SIGTERM at the wall cap,\n // empty patch -> \"no candidate passed validation\". Close stdin so the\n // subprocess sees EOF and proceeds (the `cliExecutor` leaf does the same).\n child.stdin?.end()\n\n let stdout = ''\n let stderr = ''\n let timedOut = false\n let settled = false\n\n const timer =\n timeoutMs > 0\n ? setTimeout(() => {\n timedOut = true\n if (!child.killed) child.kill('SIGTERM')\n }, timeoutMs)\n : null\n if (timer && typeof (timer as { unref?: () => void }).unref === 'function') {\n ;(timer as { unref: () => void }).unref()\n }\n\n const onAbort = () => {\n if (!child.killed) child.kill('SIGTERM')\n }\n if (options.signal) {\n if (options.signal.aborted) onAbort()\n else options.signal.addEventListener('abort', onAbort, { once: true })\n }\n\n child.stdout?.on('data', (chunk) => {\n stdout += String(chunk)\n })\n child.stderr?.on('data', (chunk) => {\n stderr += String(chunk)\n })\n\n const finalize = (result: LocalHarnessResult) => {\n if (settled) return\n settled = true\n if (timer) clearTimeout(timer)\n options.signal?.removeEventListener('abort', onAbort)\n resolve(result)\n }\n\n child.on('error', (err) => {\n if (settled) return\n settled = true\n if (timer) clearTimeout(timer)\n options.signal?.removeEventListener('abort', onAbort)\n reject(err)\n })\n\n child.on('close', (code, signal) => {\n finalize({\n exitCode: code,\n stdout,\n stderr,\n killedBySignal: signal,\n durationMs: Date.now() - startedAt,\n timedOut,\n })\n })\n })\n}\n"],"mappings":"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","names":[]}
|