Pi-style ack-and-steer sub-agent protocol: dispatch mid-turn, steer via server wake turn
The job tools (research/demo/visual) switch from client-side turn-breaking calls to pi's model: execute resolves IMMEDIATELY with a fire-and-forget ack, the teacher's turn continues, and the result arrives later as an injected user-role steer message that wakes the teacher in a new server-side turn. - contracts.ts: THE ASYNC PROTOCOL rewritten (ack now, steer later); steer types (SubagentSteerMessage, metadata.subagentResult + data-subagent- artifact part), pure buildSubagentSteer/steerDelivered/isSubagentFailure - tools/jobs.ts: jobTools(ctx) factory — server-side execute creates the job (deduped by toolCallId), fires spawn with the dispatch-time input (the part isn't persisted yet at execute — a transcript scan would race) - runner.ts: runJob(jobId, input?) builds the steer and hands it to the injected deliver seam; pipeline owns the done/error flip (a polling client re-syncs once, at the end); failures still steer - server/jobs.ts (new pipeline): per-chat serialized queues on globalThis, idle gates (turn liveness + pending question, 1.5s grace), 409 retry with backoff, idempotent per jobId, stale-job recovery sweep (re-deliver or re-spawn); wake turns run through the same handleChatRequest composition via setTurnRunner injection - chat-handler.ts: wires runner+deliver+tools ctx; drops spawnTurnJobs from onFinish; teacher default registry = question tools only - plan.ts: PlanToolContext split out (plan tools need no job wiring) - visual-maker: edit_svg added (pi parity — exact-match single replacement, fuzzy context on a miss); researcher: pi's kept/dropped source accounting restored - prompts 20-jobs/40-demos: async discipline rewritten (announce and keep teaching, no waiting/polling/fabricating, parallel dispatch, teach with the artifact when it arrives) - tests: jobs.test.ts rewritten (dispatch ack mid-turn, runner steers, wake pipeline: deferral/409/idempotency/failure), demos.test.tsx runner tests ported to runJob + fake deliver; 222 green FLAGGED surgical touches outside the stream's owned files, for master review at merge: src/core/contracts.ts (protocol doc + steer types), src/core/ui-message.ts (job tool + data-part types), src/server/ chat-handler.ts (wiring), src/server/turn-service.ts (spawnTurnJobs retirement), src/agent/teacher-agent.ts (default tools), src/agent/tools/ plan.ts (context type split). Verified live on PORT=3003 with real models: teacher dispatched research mid-turn, researcher ran real searches/fetches, steer injected on quiz answer, wake turn wove the brief in citing its sources, job flipped done; a client/wake race correctly 409'd.
This commit is contained in:
+17
-6
@@ -1,18 +1,29 @@
|
||||
import type { UIMessage } from "ai";
|
||||
import type { DemoToolInput, ResearchToolInput, SubagentArtifactData, VisualToolInput } from "./contracts.ts";
|
||||
import type { AskToolInput, QuestionAnswerData, QuizToolInput } from "./question-core.ts";
|
||||
|
||||
/**
|
||||
* The typed UI message for the teacher chat — gives the frontend typed tool
|
||||
* parts ("tool-quiz" / "tool-ask_user_question" with typed input/output)
|
||||
* without importing the server-side agent into the client bundle.
|
||||
* parts ("tool-quiz" / "tool-ask_user_question" / "tool-research" / …) without
|
||||
* importing the server-side agent into the client bundle.
|
||||
*
|
||||
* Declared explicitly (not via InferUITools) because a client-side tool has
|
||||
* no execute function, so its OUTPUT type lives only here: the structured
|
||||
* answer record the client builds and sends via addToolOutput.
|
||||
* Question tools are CLIENT-SIDE (no execute): their OUTPUT type is the
|
||||
* structured answer record the client builds and sends via addToolOutput.
|
||||
* Job tools are SERVER-EXECUTED: their output is the plain-string dispatch
|
||||
* acknowledgement (the result later arrives as a steer message — see
|
||||
* SubagentSteerMessage in contracts.ts, whose artifact rides the
|
||||
* data-subagent-artifact part typed here as DATA_PARTS).
|
||||
*/
|
||||
export type TeacherUITools = {
|
||||
quiz: { input: QuizToolInput; output: QuestionAnswerData };
|
||||
ask_user_question: { input: AskToolInput; output: QuestionAnswerData };
|
||||
research: { input: ResearchToolInput; output: string };
|
||||
demo: { input: DemoToolInput; output: string };
|
||||
visual: { input: VisualToolInput; output: string };
|
||||
};
|
||||
|
||||
export type TeacherUIMessage = UIMessage<unknown, never, TeacherUITools>;
|
||||
export type TeacherUIDataParts = {
|
||||
"subagent-artifact": SubagentArtifactData;
|
||||
};
|
||||
|
||||
export type TeacherUIMessage = UIMessage<unknown, TeacherUIDataParts, TeacherUITools>;
|
||||
|
||||
Reference in New Issue
Block a user