refactor: extract wrapAgentAsAdapter to util-agent, support childThread in RoleFn (#222)

This commit is contained in:
2026-05-13 02:37:32 +00:00
parent 1f4bd3f431
commit ec4599a230
16 changed files with 55 additions and 41 deletions
+1
View File
@@ -0,0 +1 @@
/home/azureuser/repos/workflow/packages/workflow-cas
+1
View File
@@ -30,6 +30,7 @@ export type {
Result, Result,
RoleDefinition, RoleDefinition,
RoleFn, RoleFn,
RoleResult,
RoleMeta, RoleMeta,
RoleOutput, RoleOutput,
RoleStep, RoleStep,
+3 -1
View File
@@ -157,7 +157,9 @@ export type AgentBinding = {
// ── Adapter (replaces Agent) ──────────────────────────────────────── // ── Adapter (replaces Agent) ────────────────────────────────────────
export type RoleFn<T> = (ctx: ThreadContext, runtime: WorkflowRuntime) => Promise<T>; export type RoleResult<T> = { meta: T; childThread: string | null };
export type RoleFn<T> = (ctx: ThreadContext, runtime: WorkflowRuntime) => Promise<RoleResult<T>>;
export type AdapterFn = <T>(prompt: string, schema: z.ZodType<T>) => RoleFn<T>; export type AdapterFn = <T>(prompt: string, schema: z.ZodType<T>) => RoleFn<T>;
+1
View File
@@ -0,0 +1 @@
/home/azureuser/repos/workflow/packages/workflow-protocol
@@ -84,7 +84,8 @@ async function advanceOneRound<M extends RoleMeta>(
const adapter = adapterForRole(binding, next); const adapter = adapterForRole(binding, next);
const roleFn = adapter(roleDef.systemPrompt, roleDef.schema as z.ZodType<Record<string, unknown>>); const roleFn = adapter(roleDef.systemPrompt, roleDef.schema as z.ZodType<Record<string, unknown>>);
const meta = await roleFn(modCtx as unknown as ThreadContext, runtime); const result = await roleFn(modCtx as unknown as ThreadContext, runtime);
const meta = result.meta;
const refsFromMeta = resolveExtractedRefs( const refsFromMeta = resolveExtractedRefs(
roleDef as unknown as RoleDefinition<Record<string, unknown>>, roleDef as unknown as RoleDefinition<Record<string, unknown>>,
@@ -110,7 +111,7 @@ async function advanceOneRound<M extends RoleMeta>(
contentHash: step.contentHash, contentHash: step.contentHash,
meta: step.meta, meta: step.meta,
refs: step.refs, refs: step.refs,
childThread: null, childThread: result.childThread,
}, },
step, step,
}; };
+1
View File
@@ -20,6 +20,7 @@ export type {
Result, Result,
RoleDefinition, RoleDefinition,
RoleFn, RoleFn,
RoleResult,
RoleMeta, RoleMeta,
RoleOutput, RoleOutput,
RoleStep, RoleStep,
+1
View File
@@ -24,6 +24,7 @@ export type {
Result, Result,
RoleDefinition, RoleDefinition,
RoleFn, RoleFn,
RoleResult,
RoleMeta, RoleMeta,
RoleOutput, RoleOutput,
RoleStep, RoleStep,
+1
View File
@@ -0,0 +1 @@
/home/azureuser/repos/workflow/packages/workflow-runtime
@@ -4,10 +4,8 @@
* All roles use cursor-agent with workspace auto-extracted from context. * All roles use cursor-agent with workspace auto-extracted from context.
*/ */
import { createCursorAgent } from "@uncaged/workflow-agent-cursor"; import { createCursorAgent } from "@uncaged/workflow-agent-cursor";
import { putContentNodeWithRefs } from "@uncaged/workflow-cas";
import type { AdapterFn, AgentContext, AgentFnResult, ThreadContext, WorkflowRuntime } from "@uncaged/workflow-runtime";
import { createWorkflow } from "@uncaged/workflow-runtime"; import { createWorkflow } from "@uncaged/workflow-runtime";
import type * as z from "zod/v4"; import { wrapAgentAsAdapter } from "@uncaged/workflow-util-agent";
import { buildDevelopDescriptor, developWorkflowDefinition } from "./src/index.js"; import { buildDevelopDescriptor, developWorkflowDefinition } from "./src/index.js";
function requireEnv(name: string): string { function requireEnv(name: string): string {
@@ -43,19 +41,6 @@ const agent = createCursorAgent({
llmProvider, llmProvider,
}); });
function wrapAgentAsAdapter(agentFn: (ctx: AgentContext) => Promise<AgentFnResult>): AdapterFn {
return <T>(prompt: string, schema: z.ZodType<T>) => {
return async (ctx: ThreadContext, runtime: WorkflowRuntime): Promise<T> => {
const agentCtx: AgentContext = { ...ctx, currentRole: { name: "agent", systemPrompt: prompt } };
const result = await agentFn(agentCtx);
const output = typeof result === "string" ? result : result.output;
const contentHash = await putContentNodeWithRefs(runtime.cas, output, []);
const extracted = await runtime.extract(schema as z.ZodType<Record<string, unknown>>, contentHash);
return extracted.meta as T;
};
};
}
const adapter = wrapAgentAsAdapter(agent); const adapter = wrapAgentAsAdapter(agent);
const wf = createWorkflow(developWorkflowDefinition, { adapter, overrides: null }); const wf = createWorkflow(developWorkflowDefinition, { adapter, overrides: null });
@@ -11,6 +11,7 @@ import {
createWorkflow, createWorkflow,
END, END,
type ModeratorContext, type ModeratorContext,
type RoleResult,
type RoleStep, type RoleStep,
START, START,
type ThreadContext, type ThreadContext,
@@ -112,13 +113,13 @@ function makeThread(prompt: string) {
function createSequenceAdapter(sequence: ReadonlyArray<Record<string, unknown>>): AdapterFn { function createSequenceAdapter(sequence: ReadonlyArray<Record<string, unknown>>): AdapterFn {
let i = 0; let i = 0;
return <T>(_prompt: string, _schema: z.ZodType<T>) => { return <T>(_prompt: string, _schema: z.ZodType<T>) => {
return async (_ctx: ThreadContext, _runtime: WorkflowRuntime): Promise<T> => { return async (_ctx: ThreadContext, _runtime: WorkflowRuntime): Promise<RoleResult<T>> => {
const meta = sequence[i] ?? sequence[sequence.length - 1]; const meta = sequence[i] ?? sequence[sequence.length - 1];
if (meta === undefined) { if (meta === undefined) {
throw new Error("createSequenceAdapter: empty sequence"); throw new Error("createSequenceAdapter: empty sequence");
} }
i += 1; i += 1;
return meta as T; return { meta: meta as T, childThread: null };
}; };
}; };
} }
@@ -130,9 +131,9 @@ function createTrackingAdapter(
meta: Record<string, unknown>, meta: Record<string, unknown>,
): AdapterFn { ): AdapterFn {
return <T>(_prompt: string, _schema: z.ZodType<T>) => { return <T>(_prompt: string, _schema: z.ZodType<T>) => {
return async (_ctx: ThreadContext, _runtime: WorkflowRuntime): Promise<T> => { return async (_ctx: ThreadContext, _runtime: WorkflowRuntime): Promise<RoleResult<T>> => {
calls.push(name); calls.push(name);
return meta as T; return { meta: meta as T, childThread: null };
}; };
}; };
} }
@@ -5,11 +5,9 @@
* developer → workflow-as-agent (delegates to "develop" workflow) * developer → workflow-as-agent (delegates to "develop" workflow)
*/ */
import { createHermesAgent } from "@uncaged/workflow-agent-hermes"; import { createHermesAgent } from "@uncaged/workflow-agent-hermes";
import { putContentNodeWithRefs } from "@uncaged/workflow-cas";
import { workflowAsAgent } from "@uncaged/workflow-execute"; import { workflowAsAgent } from "@uncaged/workflow-execute";
import type { AdapterFn, AgentContext, AgentFnResult, ThreadContext, WorkflowRuntime } from "@uncaged/workflow-runtime";
import { createWorkflow } from "@uncaged/workflow-runtime"; import { createWorkflow } from "@uncaged/workflow-runtime";
import type * as z from "zod/v4"; import { wrapAgentAsAdapter } from "@uncaged/workflow-util-agent";
import { buildSolveIssueDescriptor, solveIssueWorkflowDefinition } from "./src/index.js"; import { buildSolveIssueDescriptor, solveIssueWorkflowDefinition } from "./src/index.js";
function optionalEnv(name: string): string | null { function optionalEnv(name: string): string | null {
@@ -20,19 +18,6 @@ function optionalEnv(name: string): string | null {
return value; return value;
} }
function wrapAgentAsAdapter(agentFn: (ctx: AgentContext) => Promise<AgentFnResult>): AdapterFn {
return <T>(prompt: string, schema: z.ZodType<T>) => {
return async (ctx: ThreadContext, runtime: WorkflowRuntime): Promise<T> => {
const agentCtx: AgentContext = { ...ctx, currentRole: { name: "agent", systemPrompt: prompt } };
const result = await agentFn(agentCtx);
const output = typeof result === "string" ? result : result.output;
const contentHash = await putContentNodeWithRefs(runtime.cas, output, []);
const extracted = await runtime.extract(schema as z.ZodType<Record<string, unknown>>, contentHash);
return extracted.meta as T;
};
};
}
const hermesAgent = createHermesAgent({ const hermesAgent = createHermesAgent({
model: optionalEnv("WORKFLOW_HERMES_MODEL"), model: optionalEnv("WORKFLOW_HERMES_MODEL"),
timeout: optionalEnv("WORKFLOW_HERMES_TIMEOUT") timeout: optionalEnv("WORKFLOW_HERMES_TIMEOUT")
+2 -1
View File
@@ -14,6 +14,7 @@
"test": "bun test" "test": "bun test"
}, },
"dependencies": { "dependencies": {
"@uncaged/workflow-runtime": "workspace:*" "@uncaged/workflow-runtime": "workspace:*",
"@uncaged/workflow-cas": "workspace:*"
} }
} }
@@ -1,3 +1,4 @@
export { buildAgentPrompt, buildThreadInput } from "./build-agent-prompt.js"; export { buildAgentPrompt, buildThreadInput } from "./build-agent-prompt.js";
export type { SpawnCliConfig, SpawnCliError, SpawnCliResult } from "./spawn-cli.js"; export type { SpawnCliConfig, SpawnCliError, SpawnCliResult } from "./spawn-cli.js";
export { spawnCli } from "./spawn-cli.js"; export { spawnCli } from "./spawn-cli.js";
export { wrapAgentAsAdapter } from "./wrap-agent-as-adapter.js";
@@ -0,0 +1,31 @@
import { putContentNodeWithRefs } from "@uncaged/workflow-cas";
import type {
AdapterFn,
AgentContext,
AgentFnResult,
RoleResult,
ThreadContext,
WorkflowRuntime,
} from "@uncaged/workflow-runtime";
import type * as z from "zod/v4";
/**
* Wraps a legacy AgentFn into an AdapterFn.
* The agent produces a string (or { output, childThread }); the adapter
* stores the output in CAS, runs extract, and returns typed meta + childThread.
*/
export function wrapAgentAsAdapter(
agentFn: (ctx: AgentContext) => Promise<AgentFnResult>,
): AdapterFn {
return <T>(prompt: string, schema: z.ZodType<T>) => {
return async (ctx: ThreadContext, runtime: WorkflowRuntime): Promise<RoleResult<T>> => {
const agentCtx: AgentContext = { ...ctx, currentRole: { name: "agent", systemPrompt: prompt } };
const result = await agentFn(agentCtx);
const output = typeof result === "string" ? result : result.output;
const childThread = typeof result === "string" ? null : result.childThread;
const contentHash = await putContentNodeWithRefs(runtime.cas, output, []);
const extracted = await runtime.extract(schema as z.ZodType<Record<string, unknown>>, contentHash);
return { meta: extracted.meta as T, childThread };
};
};
}
+1 -1
View File
@@ -6,5 +6,5 @@
"composite": true "composite": true
}, },
"include": ["src/**/*.ts"], "include": ["src/**/*.ts"],
"references": [{ "path": "../workflow-runtime" }] "references": [{ "path": "../workflow-runtime" }, { "path": "../workflow-cas" }]
} }
+1
View File
@@ -0,0 +1 @@
/home/azureuser/repos/workflow/packages/workflow-util