feat(FN-1272): add task document tools for triage and executor

- Add task_document_write and task_document_read factories with schemas, revision-aware responses, and error handling in agent-tools
- Wire document tools into executor and triage sessions and update prompts to persist and reuse planning artifacts across runs
- Export document tool factories and parameter schemas from engine public entrypoints while keeping executor re-export compatibility
- Add comprehensive tests covering write/read success paths, empty/not-found cases, and store failure handling
This commit is contained in:
gsxdsm
2026-04-08 12:27:11 -07:00
parent 0cdc163a67
commit d271b42840
5 changed files with 409 additions and 2 deletions

View File

@@ -7,7 +7,7 @@
* The parameter schemas are canonical here — executor.ts imports and reuses them.
*/
import type { TaskStore } from "@fusion/core";
import type { TaskDocument, TaskDocumentCreateInput, TaskStore } from "@fusion/core";
import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
import { Type, type Static } from "@mariozechner/pi-ai";
import type { AgentReflectionService } from "./agent-reflection.js";
@@ -26,6 +26,20 @@ export const taskLogParams = Type.Object({
outcome: Type.Optional(Type.String({ description: "Result or consequence (optional)" })),
});
export const taskDocumentWriteParams = Type.Object({
key: Type.String({
description: "Document key (e.g., 'plan', 'notes', 'research'). Alphanumeric, hyphens, underscores, 1-64 chars.",
}),
content: Type.String({ description: "Document content to store" }),
author: Type.Optional(Type.String({ description: "Who is writing (default: 'agent')" })),
});
export const taskDocumentReadParams = Type.Object({
key: Type.Optional(
Type.String({ description: "Document key to read. Omit to list all documents for this task." }),
),
});
export const reflectOnPerformanceParams = Type.Object({
focus_area: Type.Optional(
Type.String({ description: "Optional focus area for reflection (e.g., 'code quality', 'speed', 'testing')" }),
@@ -93,6 +107,117 @@ export function createTaskLogTool(store: TaskStore, taskId: string): ToolDefinit
};
}
/**
* Create a `task_document_write` tool that stores a named task document.
*
* @param store - TaskStore for task document persistence
* @param taskId - The task ID to write documents against
* @returns ToolDefinition for the `task_document_write` tool
*/
export function createTaskDocumentWriteTool(store: TaskStore, taskId: string): ToolDefinition {
return {
name: "task_document_write",
label: "Write Document",
description:
"Save a named document for this task (for example plan, notes, or research). " +
"Each write creates a new revision so you can update documents over time.",
parameters: taskDocumentWriteParams,
execute: async (_id: string, params: Static<typeof taskDocumentWriteParams>) => {
const input: TaskDocumentCreateInput = {
key: params.key,
content: params.content,
author: params.author || "agent",
};
try {
const document: TaskDocument = await store.upsertTaskDocument(taskId, input);
return {
content: [{
type: "text" as const,
text: `Saved document "${document.key}" (revision ${document.revision}).`,
}],
details: {},
};
} catch (err: any) {
return {
content: [{
type: "text" as const,
text: `ERROR: Failed to save document "${params.key}": ${err.message}`,
}],
details: {},
};
}
},
};
}
/**
* Create a `task_document_read` tool that reads task-scoped documents.
*
* @param store - TaskStore for task document reads
* @param taskId - The task ID to read documents from
* @returns ToolDefinition for the `task_document_read` tool
*/
export function createTaskDocumentReadTool(store: TaskStore, taskId: string): ToolDefinition {
return {
name: "task_document_read",
label: "Read Document",
description:
"Read a named document for this task, or list all documents when no key is provided.",
parameters: taskDocumentReadParams,
execute: async (_id: string, params: Static<typeof taskDocumentReadParams>) => {
try {
if (params.key) {
const document: TaskDocument | null = await store.getTaskDocument(taskId, params.key);
if (!document) {
return {
content: [{ type: "text" as const, text: `Document "${params.key}" not found.` }],
details: {},
};
}
return {
content: [{
type: "text" as const,
text:
`Document: ${document.key}\n` +
`Revision: ${document.revision}\n` +
`Updated: ${document.updatedAt}\n\n` +
document.content,
}],
details: {},
};
}
const documents: TaskDocument[] = await store.getTaskDocuments(taskId);
if (documents.length === 0) {
return {
content: [{ type: "text" as const, text: "No documents found for this task." }],
details: {},
};
}
const lines = documents.map((doc) => `- ${doc.key} (revision ${doc.revision}, updated ${doc.updatedAt})`);
return {
content: [{
type: "text" as const,
text: `Task documents:\n${lines.join("\n")}`,
}],
details: {},
};
} catch (err: any) {
return {
content: [{
type: "text" as const,
text: `ERROR: Failed to read task documents: ${err.message}`,
}],
details: {},
};
}
},
};
}
/**
* Create a `reflect_on_performance` tool that asks the reflection service to
* analyze recent agent performance and return actionable insights.