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 <cursoragent@cursor.com>
This commit is contained in:
2026-05-18 15:29:48 +00:00
parent a38ca7e8db
commit ad74768630
7 changed files with 75 additions and 21 deletions
+1
View File
@@ -21,6 +21,7 @@
"test": "bun test" "test": "bun test"
}, },
"dependencies": { "dependencies": {
"@uncaged/json-cas": "^0.3.0",
"@uncaged/uwf-agent-kit": "workspace:^" "@uncaged/uwf-agent-kit": "workspace:^"
}, },
"devDependencies": { "devDependencies": {
+30 -3
View File
@@ -1,10 +1,27 @@
import { spawn } from "node:child_process"; 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_COMMAND = "hermes";
const HERMES_MAX_TURNS = 90; 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 { function buildHistorySummary(history: AgentContext["history"]): string {
if (history.length === 0) { if (history.length === 0) {
return ""; return "";
@@ -76,9 +93,19 @@ function spawnHermesChat(prompt: string): Promise<string> {
}); });
} }
async function runHermes(ctx: AgentContext): Promise<string> { async function storeHermesRawOutput(rawOutput: string): Promise<string> {
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<AgentRunResult> {
const fullPrompt = buildHermesPrompt(ctx); 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. */ /** Agent CLI factory: parses argv, runs Hermes, extracts output, writes StepNode. */
+8 -2
View File
@@ -1,6 +1,5 @@
export type { BuildContextMeta } from "./context.js"; export type { BuildContextMeta } from "./context.js";
export { buildContext, buildContextWithMeta } from "./context.js"; export { buildContext, buildContextWithMeta } from "./context.js";
export { getConfigPath, getEnvPath, loadWorkflowConfig } from "./storage.js";
export type { ExtractResult, ResolvedLlmProvider } from "./extract.js"; export type { ExtractResult, ResolvedLlmProvider } from "./extract.js";
export { export {
extract, extract,
@@ -8,4 +7,11 @@ export {
resolveModel, resolveModel,
} from "./extract.js"; } from "./extract.js";
export { createAgent } from "./run.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";
+7 -8
View File
@@ -6,7 +6,7 @@ import { buildContextWithMeta } from "./context.js";
import { extract } from "./extract.js"; import { extract } from "./extract.js";
import type { AgentStore } from "./storage.js"; import type { AgentStore } from "./storage.js";
import { getEnvPath, loadWorkflowConfig, resolveStorageRoot } 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 { function fail(message: string): never {
process.stderr.write(`${message}\n`); process.stderr.write(`${message}\n`);
@@ -65,7 +65,7 @@ async function writeStepNode(options: {
return hash; return hash;
} }
async function runAgent(options: AgentOptions, ctx: AgentContext): Promise<string> { async function runAgent(options: AgentOptions, ctx: AgentContext): Promise<AgentRunResult> {
return runWithMessage("agent run failed", () => options.run(ctx)); return runWithMessage("agent run failed", () => options.run(ctx));
} }
@@ -85,12 +85,11 @@ async function extractOutput(
async function persistStep(options: { async function persistStep(options: {
ctx: Awaited<ReturnType<typeof buildContextWithMeta>>; ctx: Awaited<ReturnType<typeof buildContextWithMeta>>;
rawOutput: string;
outputHash: CasRef; outputHash: CasRef;
detailHash: CasRef;
agentName: string; agentName: string;
}): Promise<CasRef> { }): Promise<CasRef> {
const { store, schemas, chain, headHash } = options.ctx.meta; const { store, schemas, chain, headHash } = options.ctx.meta;
const detailHash = await store.put(null, options.rawOutput);
return writeStepNode({ return writeStepNode({
store, store,
schemas, schemas,
@@ -98,7 +97,7 @@ async function persistStep(options: {
prevHash: chain.headIsStart ? null : headHash, prevHash: chain.headIsStart ? null : headHash,
role: options.ctx.role, role: options.ctx.role,
outputHash: options.outputHash, outputHash: options.outputHash,
detailHash, detailHash: options.detailHash,
agentName: options.agentName, agentName: options.agentName,
}); });
} }
@@ -121,12 +120,12 @@ export function createAgent(options: AgentOptions): () => Promise<void> {
fail(`unknown role: ${role}`); fail(`unknown role: ${role}`);
} }
const rawOutput = await runAgent(options, ctx); const agentResult = await runAgent(options, ctx);
const outputHash = await extractOutput(rawOutput, roleDef.outputSchema, storageRoot); const outputHash = await extractOutput(agentResult.output, roleDef.outputSchema, storageRoot);
const stepHash = await persistStep({ const stepHash = await persistStep({
ctx, ctx,
rawOutput,
outputHash, outputHash,
detailHash: agentResult.detailHash,
agentName: agentLabel(options.name), agentName: agentLabel(options.name),
}); });
+1 -5
View File
@@ -1,10 +1,6 @@
import type { Hash, Store } from "@uncaged/json-cas"; import type { Hash, Store } from "@uncaged/json-cas";
import { putSchema } from "@uncaged/json-cas"; import { putSchema } from "@uncaged/json-cas";
import { import { START_NODE_SCHEMA, STEP_NODE_SCHEMA, WORKFLOW_SCHEMA } from "@uncaged/uwf-protocol";
START_NODE_SCHEMA,
STEP_NODE_SCHEMA,
WORKFLOW_SCHEMA,
} from "@uncaged/uwf-protocol";
export type UwfAgentSchemaHashes = { export type UwfAgentSchemaHashes = {
workflow: Hash; workflow: Hash;
+6 -1
View File
@@ -9,7 +9,12 @@ export type AgentContext = {
workflow: WorkflowPayload; workflow: WorkflowPayload;
}; };
export type AgentRunFn = (ctx: AgentContext) => Promise<string>; export type AgentRunResult = {
output: string;
detailHash: string;
};
export type AgentRunFn = (ctx: AgentContext) => Promise<AgentRunResult>;
export type AgentOptions = { export type AgentOptions = {
name: string; name: string;
+22 -2
View File
@@ -1,11 +1,31 @@
#!/usr/bin/env bun #!/usr/bin/env bun
// Mock agent for smoke testing // 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({ const agent = createAgent({
name: "mock", name: "mock",
run: async (ctx) => { 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 };
}, },
}); });