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:
@@ -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": {
|
||||||
|
|||||||
@@ -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. */
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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,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;
|
||||||
|
|||||||
@@ -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
@@ -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 };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user