-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathshared.ts
More file actions
196 lines (187 loc) · 7.46 KB
/
Copy pathshared.ts
File metadata and controls
196 lines (187 loc) · 7.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/**
* Shared fixtures for the supervisor-loop runners: the demo goal + its deployable
* completion check, and a SCRIPTED driver brain so the box / cli-bridge runners
* exercise their wiring with no inference.
*
* The supervisor itself is `supervise()` (`@tangle-network/agent-runtime/kernel`);
* these are only the per-example task + the offline brain it can be driven with.
*/
import { type AgentProfile, harnessTypeSchema } from '@tangle-network/agent-interface'
import type {
ExecutorConfig,
SandboxClient as RuntimeSandboxClient,
} from '@tangle-network/agent-runtime/kernel'
import { Sandbox } from '@tangle-network/sandbox'
import type { ToolLoopChat } from '../../src/testing'
/** The marker every runner asks its workers to emit; the check confirms it landed. */
export const expectedAnswer = 'ANSWER=42'
/** The deployable completion oracle: a worker settles `valid:true` ONLY when its
* output contains `ANSWER=42` — text content for off-box backends, the serialized
* harness event stream for a box. Never the model judging itself. */
export const demoCheck = (out: unknown): boolean => {
if (out && typeof out === 'object' && 'content' in out) {
return String((out as { content: unknown }).content).includes(expectedAnswer)
}
return JSON.stringify(out ?? '').includes(expectedAnswer)
}
/** The shared demo goal: produce the exact line `ANSWER=42`. */
export const demoGoal = `Produce the exact line "${expectedAnswer}".`
/**
* A SCRIPTED `ToolLoopChat`: spawn `workerCount` workers (the "drive N workers"
* shape), await each settlement, then stop. This is the exact contract
* Runtime derives from the supervisor's exact profile in production; here the chat
* function returns a fixed turn sequence so the brain runs with no inference (the
* same offline seam the driver's own unit tests use). The brain still REASONS the
* loop (spawn → await → stop) against a live `Scope`; only the driver-LLM call is mocked.
*
* The canonical loop parses `toolCalls[].arguments` itself, so each scripted call
* serializes its arguments to a JSON string; the loop JSON.parses them before
* running the tool.
*/
export function scriptedSupervisorChat(
workerCount: number,
labelPrefix = 'solver',
workerProfile: AgentProfile = workerProfileFromEnv(),
): ToolLoopChat {
interface ScriptedTurn {
content: string
toolCalls: Array<{ name: string; arguments: Record<string, unknown> }>
}
const turns: ScriptedTurn[] = []
for (let i = 0; i < workerCount; i += 1) {
turns.push({
content: `delegating slice ${i}`,
toolCalls: [
{
name: 'spawn_agent',
arguments: {
profile: {
...workerProfile,
name: `${labelPrefix}-${i}`,
prompt: { ...workerProfile.prompt, systemPrompt: `Emit ${expectedAnswer}.` },
},
task: `Emit the exact line ${expectedAnswer} and nothing else.`,
label: `${labelPrefix}-${i}`,
},
},
],
})
}
for (let i = 0; i < workerCount; i += 1) {
turns.push({
content: 'awaiting a worker',
toolCalls: [{ name: 'await_event', arguments: {} }],
})
}
turns.push({ content: 'all workers delivered — stopping', toolCalls: [] })
let i = 0
return (messages) => {
// This scripted brain deliberately IGNORES `messages` and advances a fixed plan, so
// do NOT mistake it for the supervisor pattern — a real brain READS the folded worker
// outputs and composes its next move FROM them (the fold; see examples/driver-loop/).
// We touch `messages` only so the shape is exercised:
void messages.length
const turn = turns[Math.min(i, turns.length - 1)] ?? { content: '', toolCalls: [] }
i += 1
return Promise.resolve({
content: turn.content,
toolCalls: turn.toolCalls.map((tc, j) => ({
id: `call-${i}-${j}`,
name: tc.name,
arguments: JSON.stringify(tc.arguments),
})),
})
}
}
/**
* Build the worker-leaf `ExecutorConfig` for the chosen backend — THE one swap seam the supervisor-loop
* thesis is about. `WORKER_BACKEND=bridge` (default) runs workers through the local cli-bridge (real
* harness CLIs on your machine, no cloud); `WORKER_BACKEND=sandbox` runs them in real Tangle boxes.
* Nothing downstream cares which — `workerFromBackend` injects the seam and returns a uniform worker.
*/
export function buildWorkerBackend(): { backend: ExecutorConfig; profile: AgentProfile } {
const profile = workerProfileFromEnv()
const backend = process.env.WORKER_BACKEND ?? 'bridge'
if (backend === 'sandbox') {
const apiKey = process.env.TANGLE_API_KEY
const baseUrl = process.env.SANDBOX_BASE_URL
if (!apiKey || !baseUrl) {
throw new Error(
'WORKER_BACKEND=sandbox needs a real sandbox client: set TANGLE_API_KEY + SANDBOX_BASE_URL.\n' +
' No box? WORKER_BACKEND=bridge WORKER_MODEL=opencode/zai-coding-plan/glm-5.1 runs the SAME\n' +
' supervisor against local harness CLIs (start the bridge: cd ~/code/cli-bridge && pnpm start).',
)
}
// The real Tangle client satisfies the runtime's `SandboxClient` port (it exposes `create(...)`).
const sandboxClient = new Sandbox({ apiKey, baseUrl }) as RuntimeSandboxClient
return {
backend: { backend: 'sandbox', sandboxClient, maxIterations: 1 },
profile,
}
}
if (backend !== 'bridge') {
throw new Error(`WORKER_BACKEND must be "bridge" or "sandbox" (got ${JSON.stringify(backend)})`)
}
return {
backend: {
backend: 'bridge',
bridgeUrl: process.env.BRIDGE_URL ?? 'http://127.0.0.1:3344',
bridgeBearer: process.env.BRIDGE_BEARER ?? 'local',
timeoutMs: 180_000,
},
profile,
}
}
function workerProfileFromEnv(): AgentProfile {
const model = process.env.WORKER_MODEL
if (!model) {
throw new Error(
'Set WORKER_MODEL to the concrete model, plus optional WORKER_HARNESS and WORKER_PROVIDER.',
)
}
return {
name: 'worker',
harness: harnessTypeSchema.parse(process.env.WORKER_HARNESS ?? 'pi'),
model: {
provider: process.env.WORKER_PROVIDER ?? 'tangle-router',
default: model,
},
}
}
/** The supervisor BRAIN: the real router driver when a key + model are present, else the scripted $0
* offline brain. (The brain is NEVER cli-bridge — full-agent harnesses don't return raw tool_calls.) */
export function resolveSupervisorBrain(
workerCount: number,
labelPrefix: string,
workerProfile: AgentProfile,
): { brain?: ToolLoopChat; profile: AgentProfile; label: string } {
const routerKey = process.env.TANGLE_API_KEY
const driverModel =
process.env.DRIVER_MODEL ?? process.env.LOOP_MODEL ?? workerProfile.model?.default
if (!driverModel) throw new Error('supervisor profile needs a concrete model')
const profile: AgentProfile = {
name: 'supervisor',
harness: 'cli-base',
model: {
provider: process.env.DRIVER_PROVIDER ?? 'tangle-router',
default: driverModel,
metadata: { maxTurns: 12 },
},
prompt: {
systemPrompt:
'You are a supervisor. Spawn one worker session, await it with await_event, and stop once ' +
'it delivered. The worker profile must use the exact execution identity supplied in the task.',
},
}
if (process.env.DRIVER !== 'scripted' && routerKey && driverModel) {
return {
profile,
label: `router(${driverModel})`,
}
}
return {
brain: scriptedSupervisorChat(workerCount, labelPrefix, workerProfile),
profile,
label: 'scripted',
}
}