feat(protocol): add edge prompt to Transition + EvaluateResult (#402)
- Transition type gains prompt: string | null
- evaluate() returns EvaluateResult { role, prompt } instead of string
- normalizeGraph coerces prompt: undefined → null
- spawnAgent passes edge prompt via UWF_EDGE_PROMPT env
- AgentContext gains edgePrompt field
Refs #402
This commit is contained in:
@@ -624,13 +624,17 @@ function resolveAgentConfig(
|
||||
return agentConfig;
|
||||
}
|
||||
|
||||
function spawnAgent(agent: AgentConfig, threadId: ThreadId, role: string): CasRef {
|
||||
function spawnAgent(agent: AgentConfig, threadId: ThreadId, role: string, edgePrompt: string | null): CasRef {
|
||||
const argv = [...agent.args, threadId, role];
|
||||
const env = { ...process.env };
|
||||
if (edgePrompt !== null) {
|
||||
env.UWF_EDGE_PROMPT = edgePrompt;
|
||||
}
|
||||
let stdout: string;
|
||||
try {
|
||||
stdout = execFileSync(agent.command, argv, {
|
||||
encoding: "utf8",
|
||||
env: process.env,
|
||||
env,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -712,7 +716,7 @@ async function cmdThreadStepOnce(
|
||||
fail(nextResult.error.message);
|
||||
}
|
||||
|
||||
if (nextResult.value === END_ROLE) {
|
||||
if (nextResult.value.role === END_ROLE) {
|
||||
await archiveThread(storageRoot, threadId, workflowHash, headHash);
|
||||
return {
|
||||
workflow: workflowHash,
|
||||
@@ -722,12 +726,13 @@ async function cmdThreadStepOnce(
|
||||
};
|
||||
}
|
||||
|
||||
const role = nextResult.value;
|
||||
const role = nextResult.value.role;
|
||||
const edgePrompt = nextResult.value.prompt;
|
||||
const config = await loadWorkflowConfig(storageRoot);
|
||||
const agent = resolveAgentConfig(config, workflow, role, agentOverride);
|
||||
|
||||
loadDotenv({ path: getEnvPath(storageRoot) });
|
||||
const newHead = spawnAgent(agent, threadId, role);
|
||||
const newHead = spawnAgent(agent, threadId, role, edgePrompt);
|
||||
|
||||
// Re-create store to pick up nodes written by the agent subprocess
|
||||
const uwfAfter = await createUwfStore(storageRoot);
|
||||
|
||||
@@ -58,6 +58,7 @@ function normalizeGraph(graph: Record<string, Transition[]>): Record<string, Tra
|
||||
result[node] = transitions.map((t) => ({
|
||||
role: t.role,
|
||||
condition: t.condition ?? null,
|
||||
prompt: t.prompt ?? null,
|
||||
}));
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -133,6 +133,7 @@ export async function buildContext(threadId: ThreadId, role: string): Promise<Ag
|
||||
}
|
||||
|
||||
const steps = await buildHistory(store, chain.stepsNewestFirst);
|
||||
const edgePrompt = process.env.UWF_EDGE_PROMPT ?? null;
|
||||
|
||||
return {
|
||||
threadId,
|
||||
@@ -142,6 +143,7 @@ export async function buildContext(threadId: ThreadId, role: string): Promise<Ag
|
||||
workflow,
|
||||
store,
|
||||
outputFormatInstruction: "",
|
||||
edgePrompt,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -178,6 +180,7 @@ export async function buildContextWithMeta(
|
||||
}
|
||||
|
||||
const steps = await buildHistory(store, chain.stepsNewestFirst);
|
||||
const edgePrompt = process.env.UWF_EDGE_PROMPT ?? null;
|
||||
|
||||
return {
|
||||
threadId,
|
||||
@@ -187,6 +190,7 @@ export async function buildContextWithMeta(
|
||||
workflow,
|
||||
store,
|
||||
outputFormatInstruction: "",
|
||||
edgePrompt,
|
||||
meta: { storageRoot, store, schemas, headHash, chain },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,6 +12,12 @@ export type AgentContext = ModeratorContext & {
|
||||
* role's output schema. Populated by `createAgent` at run time.
|
||||
*/
|
||||
outputFormatInstruction: string;
|
||||
/**
|
||||
* Edge prompt from the graph transition that led to this role.
|
||||
* null on first entry (use full role definition), non-null on re-entry
|
||||
* (use as continuation instruction from moderator).
|
||||
*/
|
||||
edgePrompt: string | null;
|
||||
};
|
||||
|
||||
export type AgentRunResult = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ModeratorContext, WorkflowPayload } from "@uncaged/workflow-protocol";
|
||||
import jsonata from "jsonata";
|
||||
|
||||
import type { Result } from "./types.js";
|
||||
import type { EvaluateResult, Result } from "./types.js";
|
||||
|
||||
const START_ROLE = "$START";
|
||||
|
||||
@@ -78,7 +78,7 @@ function currentRole(context: ModeratorContext): string {
|
||||
export async function evaluate(
|
||||
workflow: WorkflowPayload,
|
||||
context: ModeratorContext,
|
||||
): Promise<Result<string, Error>> {
|
||||
): Promise<Result<EvaluateResult, Error>> {
|
||||
const role = currentRole(context);
|
||||
const transitions = workflow.graph[role];
|
||||
if (transitions === undefined) {
|
||||
@@ -90,7 +90,7 @@ export async function evaluate(
|
||||
|
||||
for (const transition of transitions) {
|
||||
if (transition.condition === null) {
|
||||
return { ok: true, value: transition.role };
|
||||
return { ok: true, value: { role: transition.role, prompt: transition.prompt } };
|
||||
}
|
||||
|
||||
const conditionDef = workflow.conditions[transition.condition];
|
||||
@@ -106,7 +106,7 @@ export async function evaluate(
|
||||
return evalResult;
|
||||
}
|
||||
if (isTruthy(evalResult.value)) {
|
||||
return { ok: true, value: transition.role };
|
||||
return { ok: true, value: { role: transition.role, prompt: transition.prompt } };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export { evaluate } from "./evaluate.js";
|
||||
export type { EvaluateResult } from "./types.js";
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
export type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };
|
||||
|
||||
/** The result of moderator evaluation — which role to go to, and the edge prompt (if any). */
|
||||
export type EvaluateResult = {
|
||||
role: string;
|
||||
prompt: string | null;
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ export type RoleDefinition = {
|
||||
export type Transition = {
|
||||
role: string;
|
||||
condition: string | null;
|
||||
prompt: string | null;
|
||||
};
|
||||
|
||||
export type ConditionDefinition = {
|
||||
|
||||
Reference in New Issue
Block a user