From ba85bf9c790de3e52f3aa283f6ee429d5216c2ea Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 11:39:52 -0700 Subject: [PATCH] fix(seats): superviseRun passes the run's actor, task and pod to the trace store The supervisor started every run with only runId, adapter and label, so its own run.started entry, and any entry an adapter or the valet emitted without naming an actor, recorded actor: null, and no entry carried the board task or pod. superviseRun now takes taskId, podId and actor and hands them to store.startRun, which already validates them (a taskId without a podId is refused before anything spawns). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013ENddpGzhE7S8qjDSYAMuu --- packages/seats/src/supervisor.js | 13 +++++++-- packages/seats/test/supervisor.test.js | 37 ++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/packages/seats/src/supervisor.js b/packages/seats/src/supervisor.js index 3604a271ed..7bfd471b1a 100644 --- a/packages/seats/src/supervisor.js +++ b/packages/seats/src/supervisor.js @@ -248,13 +248,22 @@ const runAttempt = ({ adapter, prompt, ctx, run, settings, attempt, sessionId, e * @param {(info: {runId, adapter, attempt, idleMs, windowMs}) => void} [args.policy.onStalled] * @param {string} [args.runId] * @param {string} [args.label] + * @param {string} [args.taskId] the board task this run works; needs `podId` + * @param {string} [args.podId] + * @param {object} [args.actor] the run's own party, {type, id, owner?, onBehalfOf?}; + * entries the run writes without naming an actor (run.started, tool calls, + * permission requests) record this one instead of null * @returns {Promise<{ runId: string, outcome: string, attempts: number, result?: object, failure?: Error }>} */ -export const superviseRun = async ({ adapter, prompt, ctx = {}, store, policy = {}, runId, label } = {}) => { +export const superviseRun = async ({ + adapter, prompt, ctx = {}, store, policy = {}, runId, label, taskId, podId, actor, +} = {}) => { if (!adapter || typeof adapter.spawn !== 'function') throw new Error('superviseRun needs an adapter with spawn()'); if (!store || typeof store.startRun !== 'function') throw new Error('superviseRun needs a trace store'); const settings = resolvePolicy(policy); - const run = store.startRun({ runId, adapter: adapter.name, label }); + const run = store.startRun({ + runId, adapter: adapter.name, label, taskId, podId, actor, + }); // The runtime token rides in ctx, not through the valet, so the store would // not otherwise know to scrub it from agent text. if (typeof run.redact === 'function' && typeof ctx.runtimeToken === 'string' && ctx.runtimeToken) { diff --git a/packages/seats/test/supervisor.test.js b/packages/seats/test/supervisor.test.js index 0c0e806d9b..7005ee8454 100644 --- a/packages/seats/test/supervisor.test.js +++ b/packages/seats/test/supervisor.test.js @@ -78,6 +78,43 @@ test('a successful run: started, the adapter\'s events, settled ok — in that o assert.equal(store.listRuns()[0].outcome, 'ok'); })); +test('the run\'s actor, task and pod reach the record, so run-owned entries are attributed', () => withStore(async (store) => { + const actor = { type: 'agent', id: 'u-agent-quire', owner: 'u-sam', onBehalfOf: { id: 'u-cy', role: 'assigner' } }; + const adapter = { + name: 'ok', + async spawn(prompt, ctx) { + ctx.trace.emit('agent.message_chunk', { text: 'PONG' }); + return { text: 'PONG' }; + }, + }; + const out = await superviseRun({ + adapter, prompt: 'ping', store, policy: FAST, taskId: 'TASK-548', podId: 'pod-sharpen', actor, + }); + assert.equal(out.outcome, 'ok'); + const entries = store.readRun(out.runId); + for (const entry of entries) { + assert.equal(entry.taskId, 'TASK-548', `${entry.type} carries the task`); + assert.equal(entry.podId, 'pod-sharpen', `${entry.type} carries the pod`); + } + const byType = Object.fromEntries(entries.map((e) => [e.type, e])); + assert.deepEqual(byType['run.started'].actor, actor, 'the supervisor\'s own run start is the run\'s'); + assert.deepEqual(byType['agent.message_chunk'].actor, actor, 'an emit that names no actor takes the run\'s'); + const [indexed] = store.listRuns(); + assert.equal(indexed.taskId, 'TASK-548'); + assert.equal(indexed.podId, 'pod-sharpen'); + assert.deepEqual(indexed.actor, actor); +})); + +test('a taskId without a podId is refused before the adapter is spawned', () => withStore(async (store) => { + let spawned = false; + const adapter = { name: 'ok', async spawn() { spawned = true; return { text: '' }; } }; + await assert.rejects( + superviseRun({ adapter, prompt: 'ping', store, policy: FAST, taskId: 'TASK-548' }), + /a taskId needs a podId/, + ); + assert.equal(spawned, false); +})); + test('a hung adapter that honours abort settles stuck, with the reason on its signal', () => withStore(async (store) => { const calls = []; const stalled = [];