From ad74768630f0be4f7aeb34b8c16665a833943caf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Mon, 18 May 2026 15:29:48 +0000 Subject: [PATCH] feat(uwf-agent): Phase 1 agent returns output and detailHash - Change AgentRunFn to return { output, detailHash } instead of raw string - Remove agent-kit detail CAS write; agents store their own detail nodes - Hermes stores raw output as typed hermes-raw-output CAS node Co-authored-by: Cursor --- packages/uwf-agent-hermes/package.json | 1 + packages/uwf-agent-hermes/src/hermes.ts | 33 ++++++++++++++++++++++--- packages/uwf-agent-kit/src/index.ts | 10 ++++++-- packages/uwf-agent-kit/src/run.ts | 15 ++++++----- packages/uwf-agent-kit/src/schemas.ts | 6 +---- packages/uwf-agent-kit/src/types.ts | 7 +++++- scripts/mock-agent.ts | 24 ++++++++++++++++-- 7 files changed, 75 insertions(+), 21 deletions(-) diff --git a/packages/uwf-agent-hermes/package.json b/packages/uwf-agent-hermes/package.json index c20f7ba..189c7c5 100644 --- a/packages/uwf-agent-hermes/package.json +++ b/packages/uwf-agent-hermes/package.json @@ -21,6 +21,7 @@ "test": "bun test" }, "dependencies": { + "@uncaged/json-cas": "^0.3.0", "@uncaged/uwf-agent-kit": "workspace:^" }, "devDependencies": { diff --git a/packages/uwf-agent-hermes/src/hermes.ts b/packages/uwf-agent-hermes/src/hermes.ts index 6851928..3aab684 100644 --- a/packages/uwf-agent-hermes/src/hermes.ts +++ b/packages/uwf-agent-hermes/src/hermes.ts @@ -1,10 +1,27 @@ import { spawn } from "node:child_process"; -import { type AgentContext, createAgent } from "@uncaged/uwf-agent-kit"; +import { bootstrap, type JSONSchema, putSchema } from "@uncaged/json-cas"; +import { + type AgentContext, + type AgentRunResult, + createAgent, + createAgentStore, + resolveStorageRoot, +} from "@uncaged/uwf-agent-kit"; const HERMES_COMMAND = "hermes"; const HERMES_MAX_TURNS = 90; +const HERMES_RAW_OUTPUT_SCHEMA: JSONSchema = { + title: "hermes-raw-output", + type: "object", + required: ["text"], + properties: { + text: { type: "string" }, + }, + additionalProperties: false, +}; + function buildHistorySummary(history: AgentContext["history"]): string { if (history.length === 0) { return ""; @@ -76,9 +93,19 @@ function spawnHermesChat(prompt: string): Promise { }); } -async function runHermes(ctx: AgentContext): Promise { +async function storeHermesRawOutput(rawOutput: string): Promise { + const storageRoot = resolveStorageRoot(); + const { store } = await createAgentStore(storageRoot); + await bootstrap(store); + const schemaHash = await putSchema(store, HERMES_RAW_OUTPUT_SCHEMA); + return store.put(schemaHash, { text: rawOutput }); +} + +async function runHermes(ctx: AgentContext): Promise { const fullPrompt = buildHermesPrompt(ctx); - return spawnHermesChat(fullPrompt); + const rawOutput = await spawnHermesChat(fullPrompt); + const detailHash = await storeHermesRawOutput(rawOutput); + return { output: rawOutput, detailHash }; } /** Agent CLI factory: parses argv, runs Hermes, extracts output, writes StepNode. */ diff --git a/packages/uwf-agent-kit/src/index.ts b/packages/uwf-agent-kit/src/index.ts index acd6eb1..d98b3c2 100644 --- a/packages/uwf-agent-kit/src/index.ts +++ b/packages/uwf-agent-kit/src/index.ts @@ -1,6 +1,5 @@ export type { BuildContextMeta } from "./context.js"; export { buildContext, buildContextWithMeta } from "./context.js"; -export { getConfigPath, getEnvPath, loadWorkflowConfig } from "./storage.js"; export type { ExtractResult, ResolvedLlmProvider } from "./extract.js"; export { extract, @@ -8,4 +7,11 @@ export { resolveModel, } from "./extract.js"; export { createAgent } from "./run.js"; -export type { AgentContext, AgentOptions, AgentRunFn } from "./types.js"; +export { + createAgentStore, + getConfigPath, + getEnvPath, + loadWorkflowConfig, + resolveStorageRoot, +} from "./storage.js"; +export type { AgentContext, AgentOptions, AgentRunFn, AgentRunResult } from "./types.js"; diff --git a/packages/uwf-agent-kit/src/run.ts b/packages/uwf-agent-kit/src/run.ts index 43f9722..3abc69d 100644 --- a/packages/uwf-agent-kit/src/run.ts +++ b/packages/uwf-agent-kit/src/run.ts @@ -6,7 +6,7 @@ import { buildContextWithMeta } from "./context.js"; import { extract } from "./extract.js"; import type { AgentStore } from "./storage.js"; import { getEnvPath, loadWorkflowConfig, resolveStorageRoot } from "./storage.js"; -import type { AgentContext, AgentOptions } from "./types.js"; +import type { AgentContext, AgentOptions, AgentRunResult } from "./types.js"; function fail(message: string): never { process.stderr.write(`${message}\n`); @@ -65,7 +65,7 @@ async function writeStepNode(options: { return hash; } -async function runAgent(options: AgentOptions, ctx: AgentContext): Promise { +async function runAgent(options: AgentOptions, ctx: AgentContext): Promise { return runWithMessage("agent run failed", () => options.run(ctx)); } @@ -85,12 +85,11 @@ async function extractOutput( async function persistStep(options: { ctx: Awaited>; - rawOutput: string; outputHash: CasRef; + detailHash: CasRef; agentName: string; }): Promise { const { store, schemas, chain, headHash } = options.ctx.meta; - const detailHash = await store.put(null, options.rawOutput); return writeStepNode({ store, schemas, @@ -98,7 +97,7 @@ async function persistStep(options: { prevHash: chain.headIsStart ? null : headHash, role: options.ctx.role, outputHash: options.outputHash, - detailHash, + detailHash: options.detailHash, agentName: options.agentName, }); } @@ -121,12 +120,12 @@ export function createAgent(options: AgentOptions): () => Promise { fail(`unknown role: ${role}`); } - const rawOutput = await runAgent(options, ctx); - const outputHash = await extractOutput(rawOutput, roleDef.outputSchema, storageRoot); + const agentResult = await runAgent(options, ctx); + const outputHash = await extractOutput(agentResult.output, roleDef.outputSchema, storageRoot); const stepHash = await persistStep({ ctx, - rawOutput, outputHash, + detailHash: agentResult.detailHash, agentName: agentLabel(options.name), }); diff --git a/packages/uwf-agent-kit/src/schemas.ts b/packages/uwf-agent-kit/src/schemas.ts index 0500d74..f8273ad 100644 --- a/packages/uwf-agent-kit/src/schemas.ts +++ b/packages/uwf-agent-kit/src/schemas.ts @@ -1,10 +1,6 @@ import type { Hash, Store } from "@uncaged/json-cas"; import { putSchema } from "@uncaged/json-cas"; -import { - START_NODE_SCHEMA, - STEP_NODE_SCHEMA, - WORKFLOW_SCHEMA, -} from "@uncaged/uwf-protocol"; +import { START_NODE_SCHEMA, STEP_NODE_SCHEMA, WORKFLOW_SCHEMA } from "@uncaged/uwf-protocol"; export type UwfAgentSchemaHashes = { workflow: Hash; diff --git a/packages/uwf-agent-kit/src/types.ts b/packages/uwf-agent-kit/src/types.ts index 2940909..abf41d3 100644 --- a/packages/uwf-agent-kit/src/types.ts +++ b/packages/uwf-agent-kit/src/types.ts @@ -9,7 +9,12 @@ export type AgentContext = { workflow: WorkflowPayload; }; -export type AgentRunFn = (ctx: AgentContext) => Promise; +export type AgentRunResult = { + output: string; + detailHash: string; +}; + +export type AgentRunFn = (ctx: AgentContext) => Promise; export type AgentOptions = { name: string; diff --git a/scripts/mock-agent.ts b/scripts/mock-agent.ts index 48cc195..a6a6789 100644 --- a/scripts/mock-agent.ts +++ b/scripts/mock-agent.ts @@ -1,11 +1,31 @@ #!/usr/bin/env bun // Mock agent for smoke testing -import { createAgent } from "../packages/uwf-agent-kit/src/index.js"; +import { bootstrap, type JSONSchema, putSchema } from "@uncaged/json-cas"; +import { + createAgent, + createAgentStore, + resolveStorageRoot, +} from "../packages/uwf-agent-kit/src/index.js"; + +const MOCK_RAW_OUTPUT_SCHEMA: JSONSchema = { + title: "mock-raw-output", + type: "object", + required: ["text"], + properties: { + text: { type: "string" }, + }, + additionalProperties: false, +}; const agent = createAgent({ name: "mock", run: async (ctx) => { - return `Mock output for role ${ctx.role}: task was "${ctx.prompt}"`; + const output = `Mock output for role ${ctx.role}: task was "${ctx.prompt}"`; + const { store } = await createAgentStore(resolveStorageRoot()); + await bootstrap(store); + const schemaHash = await putSchema(store, MOCK_RAW_OUTPUT_SCHEMA); + const detailHash = await store.put(schemaHash, { text: output }); + return { output, detailHash }; }, });