feat(scripts): route mock scripts to parent requests and add a multi-agent example

This commit is contained in:
John Smith
2026-09-24 02:37:30 -04:00
parent fb1e461b0c
commit 4ae53bab0b
5 changed files with 63 additions and 4 deletions
+7 -1
View File
@@ -7,7 +7,7 @@
"asset-curator",
"qa-playtester"
],
"last_cycle": 211,
"last_cycle": 212,
"completed_targets": [
{
"cycle": 75,
@@ -1240,6 +1240,12 @@
"target": "Fix the plan-flip clobber: cache wins for pre-existing messages on write-back",
"metric_delta": "Fixed the cycle-210 live defect. customAddMessageToAiRequest built its write-back output from the turn-start snapshot, so a concurrent in-place rewrite of a pre-existing message (the studio flipping a plan task to done) was silently overwritten - same messageId, so the cache-only merge could not recover it. Now the write reconciles by messageId: for a message that existed before the turn, the CACHE version wins (it may hold a concurrent gated rewrite); messages the turn produced have fresh ids and are kept. +1 regression test that performs an in-place flip DURING the model call and asserts it survives (red pre-fix: status pending; mutation-checked). LIVE-VERIFIED with the deterministic harness end-to-end: with a project open, the scripted loop ran plan -> spawn -> report and the plan task now reads **done** (previously pending). 1641/1641 across 81 suites, eslint+prettier clean, production build exit 0, divergence clean.",
"commit": "fix(ai): stop a turn from clobbering a concurrent in-place rewrite"
},
{
"cycle": 212,
"target": "Script the parent while sub-agents reply plainly (mock routing) + multi-task example",
"metric_delta": "A single global turn sequence could not drive a realistic multi-agent run: parent and child requests interleave, so a child consumed the parent's next scripted turn. Added sub-agent detection to the mock (a system prompt naming a studio role) so the script applies to PARENT requests only and children return a plain reply. Added scripts/dev/mock-script-multi.json (a 3-task plan with dependsOn plus three sequential spawns) and documented the routing. Verified deterministically by direct requests: parent#1 -> create_or_update_plan, child -> plain content (no tool calls, script not consumed), parent#2 -> spawn_agent(design), parent#3 -> spawn_agent(build) - exactly the intended order. Divergence +1 forkOnly (the example).",
"commit": "feat(scripts): route mock scripts to parent requests and add a multi-agent example"
}
],
"backlog": [
+1
View File
@@ -192,6 +192,7 @@
"runs/RUNS.md",
"scripts/check-fork-divergence.js",
"scripts/dev/mock-ai-provider.js",
"scripts/dev/mock-script-multi.json",
"scripts/dev/mock-script.example.json",
"scripts/security/check-lockfiles.js",
"scripts/sonarcloud-drift.js"
+6 -2
View File
@@ -40,8 +40,12 @@ See [MAINTENANCE.md](../MAINTENANCE.md) for how these fit together.
custom AI base URL at `http://localhost:11435/v1` to use it. Set
`MOCK_SCRIPT=<file>` to serve a fixed sequence of turns (content and/or
`toolCalls`) from a JSON array, one per request — see
`dev/mock-script.example.json` (plan -> spawn_agent -> report) — to drive a
deterministic multi-step studio run offline.
`dev/mock-script.example.json` (plan -> spawn_agent -> report) or
`dev/mock-script-multi.json` (a 3-task plan with dependencies and three
sequential spawns) — to drive a deterministic multi-step studio run offline.
The script applies to PARENT requests only; a sub-agent request (its system
prompt names a studio role) gets a plain reply, so the interleaved
parent/child request order cannot consume the script out of sequence.
## Documentation and translations
+16 -1
View File
@@ -78,6 +78,20 @@ const json = (res, status, body, extraHeaders) => {
res.end(JSON.stringify(body));
};
// A sub-agent request carries its role prompt ("... of a small game studio");
// the top-level orchestrator does not. Distinguishing them lets a scripted run
// give the PARENT the scripted turns (plan, spawn, spawn, ...) while each child
// simply returns a report, so the interleaved parent/child request order does
// not consume the script out of sequence.
const isSubAgentRequest = messages =>
messages.some(
message =>
message &&
message.role === 'system' &&
typeof message.content === 'string' &&
message.content.includes('small game studio')
);
const buildReply = (messages, rawBody) => {
const lastUser = [...messages].reverse().find(m => m && m.role === 'user');
const text =
@@ -124,7 +138,8 @@ const server = http.createServer((req, res) => {
// A malformed body still gets a deterministic reply.
}
const messages = Array.isArray(payload.messages) ? payload.messages : [];
const turn = nextTurn();
const isChild = isSubAgentRequest(messages);
const turn = isChild ? null : nextTurn();
const reply =
turn && typeof turn.content === 'string'
? turn.content
+33
View File
@@ -0,0 +1,33 @@
[
{
"content": "Planning three tasks.",
"toolCalls": [
{ "id": "c_plan", "name": "create_or_update_plan", "arguments": { "tasks": [
{ "id": "design", "title": "Design", "description": "Write GDD_Overview.", "status": "pending", "dependsOn": [] },
{ "id": "build", "title": "Build", "description": "Place tiles.", "status": "pending", "dependsOn": ["design"] },
{ "id": "test", "title": "Test", "description": "Verify.", "status": "pending", "dependsOn": ["build"] }
] } }
]
},
{
"content": "Delegating design.",
"toolCalls": [
{ "id": "c_spawn1", "name": "spawn_agent", "arguments": { "role": "designer", "short_title": "Design", "task": "Write GDD_Overview.", "related_task_id": "design" } }
]
},
{
"content": "Delegating build.",
"toolCalls": [
{ "id": "c_spawn2", "name": "spawn_agent", "arguments": { "role": "developer", "short_title": "Build", "task": "Place the tiles.", "related_task_id": "build" } }
]
},
{
"content": "Delegating test.",
"toolCalls": [
{ "id": "c_spawn3", "name": "spawn_agent", "arguments": { "role": "tester", "short_title": "Test", "task": "Verify the build.", "related_task_id": "test" } }
]
},
{
"content": "All three tasks are delegated and reported."
}
]