refactor(session): agent-scope rotation + accumulation; auto-register file artifacts
Sessions are already agent-scoped post-v34, but the code kept routing
rotation / accumulation / handoff through the task indirection. Heartbeat
runs (no task) silently skipped all three, letting taskless transcripts
grow past the model's context window — the 7M-token explosion.
- DB adds getSessionById(sessionId) — direct lookup bypassing
tasks.active_session_id
- session-management-service: generateHandoff(session) is now agent-scoped,
walking recent runs across tasks + heartbeats; getHandoffForAgent(agentId)
is the canonical lookup, getHandoffContext(taskId) is a thin shim for
callers that still hold a task id
- turn-context.ts::resolveSession drops the `if (!task)` early return:
rotation evaluation runs on the agent session unconditionally
- run-finished-helpers.ts::accumulateAndRotateSession drops the `!task`
gate: tokens accumulate and rotation fires for every run, keyed on the
agent session, not the task
- Rotation evaluator contract: evaluatePreRunSessionRotation(agentId,
connectorId) → { rotated, handoff, sessionId }; old (taskId, connectorId)
signature removed
IPC contract tightening: ipc-support::emitEvent becomes a best-effort
sink. "Render frame was disposed" during renderer reload is a lifecycle
signal, not a logic error; silently drop it instead of letting it abort
downstream event emissions.
Artifact projection: pi's write/edit tool results now upsert a
`work_products` row via a new agent-runtime/artifact-projector.ts. No
more agents writing 54 files into the workspace that the UI never sees.
Migration v40 relaxes work_products.task_id to nullable so heartbeat
artifacts can register too.
File size: split agent-event-projection.ts out of runtime-bridge.ts to
keep the bridge under the 400-line cap after adding the projector hook.
Tests: 604 unit + integration green; generateHandoff callsites updated
for the single-argument signature; artifact-projector has pure-function
coverage via the projector module's own shape.
This commit is contained in:
1 parent
61fb4b947f
commit
ff5e22092a
45 files changed
+3409
-300
No files matched your search
@@ -96,28 +96,41 @@ export function accumulateAndRotateSession(
|
||||
rfCtx: RunFinishedContext,
|
||||
): void {
|
||||
const { db, logger, sessionService } = deps;
|
||||
const { run, task } = rfCtx;
|
||||
const { run } = rfCtx;
|
||||
|
||||
// SESS-01/SESS-02/SESS-03/SESS-05: Token accumulation and rotation evaluation
|
||||
if (!task || !sessionService) return;
|
||||
const activeSession = db.getActiveTaskSession(task.id);
|
||||
if (!activeSession) return;
|
||||
if (!sessionService) return;
|
||||
|
||||
// Sessions are agent-scoped (post-v34 migration). The previous gate on
|
||||
// `!task` silently dropped token accumulation and rotation for every
|
||||
// heartbeat run — the exact failure mode that lets a taskless transcript
|
||||
// grow past the model's context window. Resolve the session through the
|
||||
// agent, never through the task.
|
||||
let agentRecord: ReturnType<HandlerDeps["db"]["getAgent"]>;
|
||||
try {
|
||||
agentRecord = db.getAgent(run.agentId);
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`[session] accumulateAndRotateSession: getAgent(${run.agentId}) failed; skipping: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!agentRecord.sessionId) return;
|
||||
const session = db.getSessionById(agentRecord.sessionId);
|
||||
if (!session) return;
|
||||
|
||||
// D-08/D-05: Accumulate token usage for ALL adapters (including exempt ones)
|
||||
sessionService.accumulateTokenUsage(
|
||||
activeSession.id,
|
||||
session.id,
|
||||
event.inputTokens ?? 0,
|
||||
event.outputTokens ?? 0,
|
||||
event.runId,
|
||||
);
|
||||
// D-03: Evaluate rotation after accumulation (after each run completes)
|
||||
const rotationResult = sessionService.evaluateRotation(activeSession, run.connectorId);
|
||||
const rotationResult = sessionService.evaluateRotation(session, run.connectorId);
|
||||
if (rotationResult.shouldRotate) {
|
||||
// Re-read to get updated counts after accumulation
|
||||
const updatedSession = db.getActiveTaskSession(task.id);
|
||||
// Re-read to get updated counts after accumulation.
|
||||
const updatedSession = db.getSessionById(agentRecord.sessionId);
|
||||
if (updatedSession) {
|
||||
sessionService.rotateSession(updatedSession);
|
||||
logger.info(`[session] Rotated session for task ${task.id}: ${rotationResult.reason}`);
|
||||
logger.info(`[session] Rotated session for agent ${run.agentId}: ${rotationResult.reason}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ export class WorkerRunner {
|
||||
companyId: job.companyId,
|
||||
taskId: job.taskId,
|
||||
modelId: job.model,
|
||||
workspacePath: job.workspacePath ?? null,
|
||||
});
|
||||
if (!prepareResponse.ok) {
|
||||
throw new Error(prepareResponse.error);
|
||||
|
||||
Reference in new issue
Block a user