Files
united-workforce/packages/util-agent
xiaoju d666516ce6
CI / check (pull_request) Successful in 3m20s
feat(util-agent): extend AgentOptions with fork / cleanup (Phase 2a)
Add AgentForkFn and AgentCleanupFn type aliases. Extend AgentOptions
with fork: AgentForkFn | null and cleanup: AgentCleanupFn | null
fields. Add getAskSessionId / setAskSessionId session-cache helpers
using <stepHash>:ask key shape (coexists with exec sessions in the
same per-agent cache file). All four adapters pass fork: null,
cleanup: null — real wiring lands in Phase 2b. Resolves #145.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-07 08:36:58 +00:00
..
2026-06-07 15:44:00 +08:00

@united-workforce/util-agent

Agent framework — createAgent factory, context builder, frontmatter fast-path, and LLM extract pipeline.

Overview

Layer 2 agent framework. Provides the standard entrypoint for all agent CLIs: parse <thread-id> <role> from argv, load thread/workflow context from CAS, invoke the agent's run/continue functions, validate output via frontmatter fast-path or LLM extract, and write a StepNodePayload to CAS.

Also exports prompt builders, config/storage helpers, and session ID caching for multi-turn agents.

Dependencies: @ocas/core, @ocas/fs, @united-workforce/protocol, @united-workforce/util, dotenv, yaml

Installation

bun add @united-workforce/util-agent

API

All exports come from src/index.ts.

Agent factory

function createAgent(options: AgentOptions): () => Promise<void>

type AgentOptions = {
  name: string;
  run: AgentRunFn;
  continue: AgentContinueFn;
};

type AgentRunFn = (ctx: AgentContext) => Promise<AgentRunResult>;
type AgentContinueFn = (
  sessionId: string,
  message: string,
  store: AgentContext["store"],
) => Promise<AgentRunResult>;

type AgentRunResult = {
  output: string;
  detailHash: string;
  sessionId: string;
};

Agent CLIs call createAgent(...) and invoke the returned function as main().

Context

function buildContext(
  threadId: ThreadId,
  role: string,
  edgePrompt: string,
  storageRoot: string,
  casDir: string,
): Promise<AgentContext>
function buildContextWithMeta(
  threadId: ThreadId,
  role: string,
  edgePrompt: string,
  storageRoot: string,
  casDir: string,
): Promise<AgentContext & { meta: BuildContextMeta }>

type AgentContext = ModeratorContext & {
  threadId: ThreadId;
  role: string;
  store: Store;
  workflow: WorkflowPayload;
  outputFormatInstruction: string;
  edgePrompt: string;
  isFirstVisit: boolean;
  storageRoot: string;
  casDir: string;
};

type BuildContextMeta = {
  storageRoot: string;
  store: Store;
  schemas: AgentStore["schemas"];
  headHash: CasRef;
  chain: ChainState;
};

Requires UWF_EDGE_PROMPT in the environment (set by uwf thread step).

Prompt builders

function buildRolePrompt(role: RoleDefinition): string
function buildOutputFormatInstruction(schema: JSONSchema): string
function buildContinuationPrompt(
  steps: StepContext[],
  role: string,
  edgePrompt: string,
  options?: { includeContent?: boolean; quota?: number },
): string

Extract pipeline

function resolveExtractModelAlias(config: WorkflowConfig): ModelAlias
function resolveModel(config: WorkflowConfig, alias: ModelAlias): ResolvedLlmProvider
function extract(
  rawOutput: string,
  outputSchema: CasRef,
  config: WorkflowConfig,
  storageRoot: string,
  casDir: string,
): Promise<ExtractResult>

type ResolvedLlmProvider = { baseUrl: string; apiKey: string; model: string };
type ExtractResult = { value: unknown; hash: CasRef };

Frontmatter fast-path

function tryFrontmatterFastPath(
  rawOutput: string,
  outputSchema: CasRef,
  store: Store,
): Promise<FrontmatterFastPathResult | null>

type FrontmatterFastPathResult = { body: string; outputHash: CasRef };

Session cache

function getCachedSessionId(
  agentName: string,
  threadId: ThreadId,
  role: string,
  storageRoot: string,
): Promise<string | null>
function setCachedSessionId(
  agentName: string,
  threadId: ThreadId,
  role: string,
  sessionId: string,
  storageRoot: string,
): Promise<void>

Config and storage

function getConfigPath(storageRoot: string): string
function getEnvPath(storageRoot: string): string
function resolveStorageRoot(override: string | null): string
function loadWorkflowConfig(storageRoot: string): Promise<WorkflowConfig>

Usage

import { createAgent, buildRolePrompt } from "@united-workforce/util-agent";
import type { AgentContext, AgentRunResult } from "@united-workforce/util-agent";

async function run(ctx: AgentContext): Promise<AgentRunResult> {
  const prompt = buildRolePrompt(ctx.workflow.roles[ctx.role]!);
  // ... spawn external process, capture output ...
  return { output: markdown, detailHash: "...", sessionId: "..." };
}

async function continueSession(
  sessionId: string,
  message: string,
): Promise<AgentRunResult> {
  // ... continue multi-turn session ...
  return { output: markdown, detailHash: "...", sessionId };
}

export const main = createAgent({ name: "my-agent", run, continue: continueSession });

Internal Structure

src/
├── index.ts
├── run.ts                         createAgent entrypoint
├── context.ts                     Thread chain walk, AgentContext builder
├── extract.ts                     LLM structured extract fallback
├── frontmatter.ts                 Frontmatter fast-path validation
├── build-role-prompt.ts           Role definition → prompt text
├── build-output-format-instruction.ts
├── build-continuation-prompt.ts
├── session-cache.ts               Per-thread/session ID persistence
├── storage.ts                     CAS store, config, threads index
├── schemas.ts                     Agent CAS schema registration
└── types.ts                       AgentContext, AgentOptions, etc.

Configuration

Reads config.yaml and .env from the workflow storage root (~/.uwf by default). See @united-workforce/protocol for WorkflowConfig shape. Set via uwf setup.