fix(runtime): compact context inside worker
This commit is contained in:
1 parent
33c6ede381
commit
6dfa1a851d
13 files changed
+214
-92
No files matched your search
@@ -3,6 +3,7 @@ import type { workerMessageSchema } from "./types";
|
||||
import { handleRunLog } from "../handlers/run-log-handler";
|
||||
import { handleRunStatus } from "../handlers/run-status-handler";
|
||||
import { handleRunFinished } from "../handlers/run-finished-handler";
|
||||
import { handleContextCompaction } from "../handlers/context-compaction-handler";
|
||||
import type { HandlerContext, HandlerDeps } from "../handlers/handler-context";
|
||||
|
||||
export async function handleWorkerMessage(
|
||||
@@ -67,6 +68,9 @@ export async function handleWorkerMessage(
|
||||
}
|
||||
return;
|
||||
}
|
||||
case "context-compaction":
|
||||
handleContextCompaction(event, deps, context);
|
||||
return;
|
||||
case "rpc-request": {
|
||||
const bridge = deps.getRuntimeBridge?.();
|
||||
const response = bridge
|
||||
|
||||
@@ -24,7 +24,6 @@ export const workerRpcMethodSchema = z.enum([
|
||||
"prepareRun",
|
||||
"beforeToolCall",
|
||||
"afterToolCall",
|
||||
"transformContext",
|
||||
"invokeInternalTool",
|
||||
]);
|
||||
|
||||
@@ -65,6 +64,16 @@ export const workerMessageSchema = z.union([
|
||||
emittedAt: z.string(),
|
||||
event: z.record(z.string(), z.unknown()).and(z.object({ type: z.string() })),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("context-compaction"),
|
||||
runId: z.string().uuid(),
|
||||
emittedAt: z.string(),
|
||||
estimatedTokens: z.number(),
|
||||
originalMessageCount: z.number().int(),
|
||||
compactedMessageCount: z.number().int(),
|
||||
compactedOlderMessageCount: z.number().int(),
|
||||
retainedRecentMessageCount: z.number().int(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("rpc-request"),
|
||||
rpcId: z.string(),
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { persistRunEvent } from "../observability/run-event-recorder";
|
||||
import type { HandlerContext, HandlerDeps, WorkerMessageEvent } from "./handler-context";
|
||||
|
||||
export function handleContextCompaction(
|
||||
event: Extract<WorkerMessageEvent, { type: "context-compaction" }>,
|
||||
deps: HandlerDeps,
|
||||
_context: HandlerContext,
|
||||
): void {
|
||||
const runState = deps.db.runs.getRunAuthState(event.runId);
|
||||
if (!runState) {
|
||||
deps.logger.warn(`[context-compaction] run ${event.runId} disappeared before telemetry could be recorded.`);
|
||||
return;
|
||||
}
|
||||
persistRunEvent(deps.db, deps.logger, {
|
||||
companyId: runState.companyId,
|
||||
runId: event.runId,
|
||||
agentId: runState.agentId,
|
||||
eventType: "context_compaction",
|
||||
level: "info",
|
||||
message: `Compacted ${event.compactedOlderMessageCount} older messages into a summary.`,
|
||||
payload: {
|
||||
emittedAt: event.emittedAt,
|
||||
estimatedTokens: event.estimatedTokens,
|
||||
originalMessageCount: event.originalMessageCount,
|
||||
compactedMessageCount: event.compactedMessageCount,
|
||||
compactedOlderMessageCount: event.compactedOlderMessageCount,
|
||||
retainedRecentMessageCount: event.retainedRecentMessageCount,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -77,6 +77,16 @@ export type ChildMessage =
|
||||
emittedAt: string;
|
||||
event: { type: string } & Record<string, unknown>;
|
||||
}
|
||||
| {
|
||||
type: "context-compaction";
|
||||
runId: string;
|
||||
emittedAt: string;
|
||||
estimatedTokens: number;
|
||||
originalMessageCount: number;
|
||||
compactedMessageCount: number;
|
||||
compactedOlderMessageCount: number;
|
||||
retainedRecentMessageCount: number;
|
||||
}
|
||||
| { type: "rpc-request"; rpcId: string; runId: string; method: string; payload: unknown }
|
||||
| { type: "run-agent-state"; runId: string; state: RunAgentLifecycleState }
|
||||
| { type: "steer-ack"; runId: string; correlationId: string; accepted: boolean };
|
||||
|
||||
@@ -178,6 +178,18 @@ export class WorkerRunner {
|
||||
apiKey: prepared.apiKey,
|
||||
extraHeaders,
|
||||
rpc,
|
||||
onContextCompaction: (telemetry) => {
|
||||
post({
|
||||
type: "context-compaction",
|
||||
runId: telemetry.runId,
|
||||
emittedAt: telemetry.emittedAt,
|
||||
estimatedTokens: telemetry.estimatedTokens,
|
||||
originalMessageCount: telemetry.originalMessageCount,
|
||||
compactedMessageCount: telemetry.compactedMessageCount,
|
||||
compactedOlderMessageCount: telemetry.compactedOlderMessageCount,
|
||||
retainedRecentMessageCount: telemetry.retainedRecentMessageCount,
|
||||
});
|
||||
},
|
||||
});
|
||||
this.activeAgents.set(job.runId, agent);
|
||||
post({ type: "run-agent-state", runId: job.runId, state: "agent_active" });
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* none of which the worker should replicate.
|
||||
*
|
||||
* This is structurally the same pattern as `beforeToolCall`/
|
||||
* `afterToolCall`/`transformContext` in `hook-rpc.ts`; what's different
|
||||
* `afterToolCall` in `hook-rpc.ts`; what's different
|
||||
* is that those are side-effect hooks around pi's own tools, whereas
|
||||
* this is a primary-effect tool whose business logic lives entirely in
|
||||
* main. Keeping the payload shape minimal (name + JSON args) mirrors
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
* thrown hooks — a thrown `beforeToolCall` produces an error tool
|
||||
* result, a thrown `afterToolCall` likewise — so RPC failures degrade
|
||||
* to a deterministic tool-error instead of crashing the run. The
|
||||
* `transformContext` contract explicitly forbids throwing, so on RPC
|
||||
* failure we return the original messages unchanged.
|
||||
* `transformContext` runs the deterministic compactor in the worker
|
||||
* process and lets compaction failures fail the run explicitly.
|
||||
*/
|
||||
|
||||
import {
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from "@mariozechner/pi-agent-core";
|
||||
import { streamSimple, type Api, type Model } from "@mariozechner/pi-ai";
|
||||
import { buildAgentHooks } from "@main/agent-runtime/agent-hooks";
|
||||
import type { ContextCompactionTelemetry } from "@main/agent-runtime/agent-hooks";
|
||||
import type { WorkerRpcClient } from "../entry/rpc";
|
||||
// Side-effect import: drags the hook signature contract into the typecheck
|
||||
// graph so any drift in pi-agent-core hook shapes breaks the build here.
|
||||
@@ -40,6 +41,7 @@ export interface PiAgentFactoryInput {
|
||||
apiKey: string;
|
||||
extraHeaders: Record<string, string> | undefined;
|
||||
rpc: WorkerRpcClient;
|
||||
onContextCompaction?: (telemetry: ContextCompactionTelemetry) => void;
|
||||
}
|
||||
|
||||
/** Map the UI-facing `thinkingEffort` string onto pi's `ThinkingLevel`. */
|
||||
@@ -61,7 +63,11 @@ export function resolveThinkingLevel(effort: string | null | undefined): Thinkin
|
||||
}
|
||||
|
||||
export function createPiAgent(input: PiAgentFactoryInput): Agent {
|
||||
const hooks = buildAgentHooks({ runId: input.runId, rpc: input.rpc });
|
||||
const hooks = buildAgentHooks({
|
||||
runId: input.runId,
|
||||
rpc: input.rpc,
|
||||
onContextCompaction: input.onContextCompaction,
|
||||
});
|
||||
return new Agent({
|
||||
initialState: {
|
||||
systemPrompt: input.systemPrompt,
|
||||
|
||||
Reference in new issue
Block a user