agent-contracts-runtime
Version:
Runtime bridge for executing agent-contracts workflows on Agent SDKs
623 lines (602 loc) • 25.6 kB
TypeScript
import { S as SdkAdapter, T as TaskOutcome, b as TaskContractLike, c as AgentContractLike, d as TaskContext, P as PromptBuilderOptions, e as TaskRunResult } from './task-runner-Bo37lS9q.js';
export { A as AdapterSendOptions, a as AgentExecutionRequest, f as AgentProgressEvent, C as CacheConfig, g as CandidateAgent, I as InMemoryStore, M as MemoryRef, h as MemoryStore, R as RetryDecision, i as SplitPrompt, j as TaskRunOptions, k as buildCandidateAgents, l as buildRetryFollowUp, m as buildSplitTaskPrompt, n as buildTaskPrompt, o as extractStructuredResult, r as renderAgentSystemPrompt, p as runTask, z as zodSchemaToPromptDescription, q as zodSchemaToYamlExample } from './task-runner-Bo37lS9q.js';
import { z } from 'zod';
import { a as GuardrailRuleData, G as GuardrailHooks } from './guardrail-hooks-C8G5NGsj.js';
export { b as GuardrailCheckFunctions, c as GuardrailCheckResult, d as GuardrailRule, H as HookContext, e as HookResponse, f as createGuardrailHooks, g as createGuardrailHooksForAgent, h as createGuardrailHooksFromRules, i as getBlockingViolations, j as getWarnings, k as hasBlockingViolation } from './guardrail-hooks-C8G5NGsj.js';
import { G as GuardrailContext } from './context-B_bekYiE.js';
/**
* ModelResolver — resolves a model_class to a concrete adapter + model pair.
*
* Resolution priority (highest wins):
* 1. AGENT_RUNTIME_MODEL_{FAST|STANDARD|THINKING} env var
* 2. AGENT_RUNTIME_MODEL env var (catch-all fallback)
* 3. agent-runtime.config.yaml model_mapping.<class>
* 4. CLI --adapter + adapter default model
*/
type ModelClass = "fast" | "standard" | "thinking";
interface ResolvedModel {
adapter: string;
model?: string;
}
interface ModelResolver {
resolve(modelClass: ModelClass | undefined): ResolvedModel;
}
interface ModelMappingConfig {
fast?: {
adapter: string;
model?: string;
};
standard?: {
adapter: string;
model?: string;
};
thinking?: {
adapter: string;
model?: string;
};
}
interface ModelResolverOptions {
modelMapping?: ModelMappingConfig;
fallbackAdapter: string;
fallbackModel?: string;
env?: Record<string, string | undefined>;
}
declare function createModelResolver(options: ModelResolverOptions): ModelResolver;
/**
* Workflow Runner — orchestrates a DSL workflow as a sequence of task executions
*
* Programmatic control flow that replaces LLM-driven step interpretation:
*
* 1. Load workflow contract (ordered steps)
* 2. Run plugin beforeWorkflow hooks
* 3. For each step:
* - delegate → call task-runner with followUp/retry recovery
* - gate → pause and wait for user input
* 4. Validate each result against handoff schema
* 5. Accumulate context between steps
* 6. Run plugin afterWorkflow hooks
*/
/**
* A factory that creates independent SdkAdapter instances.
* Used by runWorkflow to allocate one adapter per parallel step,
* avoiding session-level serialization in stateful SDK adapters.
*/
type SdkAdapterFactory = () => SdkAdapter;
/**
* Model-aware adapter factory. Accepts a model_class and returns an adapter
* configured for that capability level (possibly a different provider/model).
*/
type ModelAwareSdkAdapterFactory = (modelClass?: ModelClass) => SdkAdapter | Promise<SdkAdapter>;
/** Accepts either a single adapter (serial/shared) or a factory (parallel-safe). */
type AdapterOrFactory = SdkAdapter | SdkAdapterFactory | ModelAwareSdkAdapterFactory;
interface DelegateStep {
readonly type: "delegate";
readonly task: string;
readonly from_agent: string;
readonly description: string;
readonly optional: boolean;
readonly max_retries: number;
readonly max_follow_ups?: number;
readonly depends_on?: readonly string[];
readonly retry?: {
readonly condition: string;
readonly fix_task: string;
readonly revalidate_task?: string;
};
}
interface GateStep {
readonly type: "gate";
readonly gate_kind: string;
readonly description: string;
readonly depends_on?: readonly string[];
}
type WorkflowStep = DelegateStep | GateStep;
interface WorkflowContractLike {
readonly id: string;
readonly description: string;
readonly steps: readonly WorkflowStep[];
}
interface WorkflowRunOptions {
user_request: string;
/** Override max follow-up attempts per step (lightweight output-format correction). Default: 2 */
maxFollowUps?: number;
/** Override max full retry attempts per step. Default: from DSL step.max_retries */
maxRetries?: number;
/** Variables propagated to every TaskContext in the workflow (from WorkflowInvocation.context). */
variables?: Record<string, unknown>;
onGate?: (gateKind: string, description: string, stepResults: StepResult[]) => Promise<boolean>;
onStepComplete?: (event: StepCompleteEvent) => void;
onOptionalStep?: (step: DelegateStep, context: TaskContext) => Promise<boolean>;
/**
* Writable stream for real-time progress output during agent execution.
* Intermediate events from adapters are formatted and written here.
* Typical value: process.stderr.
*/
progressOutput?: {
write(chunk: string): unknown;
};
}
/** Typed handoff envelope for workflow input. */
interface HandoffInput<TType extends string = string, TPayload = unknown> {
readonly type: TType;
readonly version?: number;
readonly payload: TPayload;
}
/**
* Standard runtime invocation envelope. SDK-specific options (openaiThreadId,
* sessionId, etc.) belong on the adapter constructor — not here.
*/
interface WorkflowInvocation<THandoffType extends string = string, TPayload = unknown> {
workflow: string;
/** Structured typed handoff input. Takes precedence over user_request if both are set. */
handoff?: HandoffInput<THandoffType, TPayload>;
/** Simple string input (convenience shorthand). */
user_request?: string;
runtime?: {
maxFollowUps?: number;
maxRetries?: number;
timeoutMs?: number;
readonly?: boolean;
dryRun?: boolean;
};
hooks?: {
onStepComplete?: (event: StepCompleteEvent) => void;
onGate?: (gateKind: string, description: string, stepResults: StepResult[]) => Promise<boolean>;
onOptionalStep?: (step: DelegateStep, context: TaskContext) => Promise<boolean>;
};
/**
* Writable stream for real-time progress output during agent execution.
* Intermediate events from adapters are formatted and written here.
* Typical value: process.stderr.
*/
progressOutput?: {
write(chunk: string): unknown;
};
context?: {
cwd?: string;
environment?: "local" | "ci" | "remote";
artifacts?: Record<string, string>;
variables?: Record<string, unknown>;
};
}
interface StepResult {
step_index: number;
task_id?: string;
gate_kind?: string;
outcome: TaskOutcome | {
status: "gate_approved";
} | {
status: "gate_rejected";
} | {
status: "skipped";
};
follow_ups_used: number;
retries_used: number;
elapsed_ms: number;
}
interface StepCompleteEvent {
workflow_id: string;
step_index: number;
step_type: "delegate" | "gate";
task_id?: string;
gate_kind?: string;
outcome_status: string;
follow_ups_used: number;
retries_used: number;
elapsed_ms: number;
}
interface WorkflowResult {
workflow_id: string;
status: "completed" | "escalated" | "gate_rejected" | "error";
steps: StepResult[];
total_elapsed_ms: number;
escalation_reason?: string;
error_message?: string;
}
interface WorkflowRegistries {
workflowRegistry?: Record<string, WorkflowContractLike>;
taskRegistry?: Record<string, TaskContractLike>;
agentRegistry?: Record<string, AgentContractLike>;
handoffSchemas?: Record<string, z.ZodType>;
}
/** @deprecated Use the WorkflowInvocation overload for typed handoff input. */
declare function runWorkflow(adapter: AdapterOrFactory, workflowId: string, options: WorkflowRunOptions, registries?: WorkflowRegistries): Promise<WorkflowResult>;
/** Structured invocation with typed handoff envelope. */
declare function runWorkflow(adapter: AdapterOrFactory, invocation: WorkflowInvocation, registries?: WorkflowRegistries): Promise<WorkflowResult>;
declare function buildGateEvaluationPrompt(step: GateStep, priorContext: string): string;
declare function parseGateEvaluationResponse(response: string): boolean;
/**
* Plugin Interface — extension points for user code injection
*/
interface GuardrailResult {
matched: boolean;
guardrail_id: string;
severity: "critical" | "mandatory" | "warning" | "info";
action: "block" | "warn" | "shadow" | "info";
message: string;
}
interface PromptBuilderArgs {
agent: AgentContractLike;
task: TaskContractLike;
context: TaskContext;
options?: PromptBuilderOptions;
}
interface AgentPlugin {
readonly id: string;
beforeTask?(taskId: string, context: TaskContext): Promise<TaskContext | null>;
afterTask?(taskId: string, outcome: TaskOutcome): Promise<TaskOutcome>;
/**
* Enrich the structured TaskContext before prompt generation.
* Unlike beforeTask (which can skip a task by returning null),
* this hook is purely for context augmentation.
*/
contextEnhancer?(taskId: string, context: TaskContext): TaskContext;
/**
* Lightweight prompt post-processor. Applied after the prompt is built.
* Use for appending instructions or modifying sections.
*/
promptEnhancer?(taskId: string, prompt: string, context: TaskContext): string;
/**
* Full prompt builder override. If provided and returns a non-null string,
* replaces the default buildTaskPrompt output entirely.
* Receives the agent/task contracts and context so the plugin can build
* a completely custom prompt.
*/
promptBuilder?(args: PromptBuilderArgs): string | null;
customGuardrails?: {
evaluateCommand?(command: string): GuardrailResult[];
evaluateFilePath?(filePath: string): GuardrailResult[];
evaluateFileContent?(filePath: string, content: string): GuardrailResult[];
};
beforeWorkflow?(workflowId: string, userRequest: string): Promise<void>;
afterWorkflow?(workflowId: string, result: WorkflowResult): Promise<void>;
}
declare class PluginRegistry {
private plugins;
register(plugin: AgentPlugin): void;
getAll(): readonly AgentPlugin[];
runBeforeTask(taskId: string, context: TaskContext): Promise<TaskContext | null>;
runAfterTask(taskId: string, outcome: TaskOutcome): Promise<TaskOutcome>;
applyContextEnhancers(taskId: string, context: TaskContext): TaskContext;
/**
* Try plugin promptBuilder hooks. Returns custom prompt if any plugin provides one,
* or null to use the default buildTaskPrompt.
*/
applyPromptBuilder(args: PromptBuilderArgs): string | null;
applyPromptEnhancers(taskId: string, prompt: string, context: TaskContext): string;
evaluateCommandGuardrails(command: string): GuardrailResult[];
evaluateFilePathGuardrails(filePath: string): GuardrailResult[];
evaluateFileContentGuardrails(filePath: string, content: string): GuardrailResult[];
runBeforeWorkflow(workflowId: string, userRequest: string): Promise<void>;
runAfterWorkflow(workflowId: string, result: WorkflowResult): Promise<void>;
}
declare const pluginRegistry: PluginRegistry;
/**
* BoundResolver — runtime binding + policy resolution with per-agent isolation.
*
* Resolves artifact binding and guardrail/policy in one pass at runtime,
* producing a per-agent scoped ResolvedComponent. Reuses existing
* resolveBound(), buildGuardrailContext(), and agent scope resolution.
*/
interface ArtifactBindingConfig {
source: string;
mappings?: Record<string, string>;
}
interface BoundResolveOptions {
/** Component identifier — used for cache keying. */
id: string;
/** Pre-resolved DSL (e.g. embedded from codegen). */
embeddedDsl?: Record<string, unknown>;
/** Path to DSL file — resolved via agent-contracts when no embeddedDsl. */
dslPath?: string;
/** Paths to software binding YAML files (guardrail_impl, etc.). */
bindingPaths?: string[];
/** Active guardrail policy name from DSL guardrail_policies. */
activeGuardrailPolicy?: string;
/** Artifact binding config — resolved via agent-contracts resolveBound(). */
artifactBinding?: ArtifactBindingConfig;
/** Path substitutions for artifact binding. */
paths?: Record<string, string>;
}
interface ResolvedComponent {
id: string;
dsl: Record<string, unknown>;
registries: WorkflowRegistries;
guardrails: {
perAgent: Map<string, GuardrailRuleData>;
};
diagnostics: Array<{
path?: string;
message: string;
severity?: string;
}>;
}
/**
* Effective guardrail IDs for an agent: entity-side bindings ∪ scope-side bindings.
*/
declare function getEffectiveGuardrailIds(dsl: Record<string, unknown>, agentId: string): Set<string>;
declare function guardrailContextToRuleData(ctx: GuardrailContext, effectiveIds?: Set<string>): GuardrailRuleData;
declare function buildPerAgentGuardrails(dsl: Record<string, unknown>, guardrailCtx: GuardrailContext): Map<string, GuardrailRuleData>;
declare class BoundResolver {
private readonly cache;
/**
* Resolve binding + policy in one pass. Results are cached by component id.
*/
resolve(opts: BoundResolveOptions): Promise<ResolvedComponent>;
/** Clear cached components (e.g. for testing). */
clearCache(): void;
}
/**
* Get guardrail rules for a specific agent from a ResolvedComponent.
* Falls back to embedded _guardrailRules when no per-agent rules exist.
*/
declare function getGuardrailRulesForAgent(component: ResolvedComponent, agentId: string, fallbackDsl?: Record<string, unknown>): GuardrailRuleData | undefined;
/**
* Fluent Builder API for workflow invocation.
*
* Usage:
* const runtime = createRuntime({ adapter, registries });
* const result = await runtime
* .workflow("feature-implement")
* .handoff(handoffs.featureImplementationRequest({ ... }))
* .maxFollowUps(3)
* .onGate(async () => true)
* .run();
*/
interface RuntimeConfig {
adapter: SdkAdapter;
registries?: WorkflowRegistries;
}
interface Runtime {
workflow(workflowId: string): WorkflowBuilder;
}
declare function createRuntime(config: RuntimeConfig): Runtime;
declare class WorkflowBuilder {
private adapter;
private invocation;
private registries;
constructor(adapter: SdkAdapter, workflowId: string, registries?: WorkflowRegistries);
/** Set a typed handoff envelope as input. */
handoff<T extends string>(input: HandoffInput<T>): this;
/** Set a simple string request (convenience shorthand). */
request(userRequest: string): this;
maxFollowUps(n: number): this;
maxRetries(n: number): this;
onStepComplete(cb: (event: StepCompleteEvent) => void): this;
onGate(cb: (gateKind: string, description: string, stepResults: StepResult[]) => Promise<boolean>): this;
onOptionalStep(cb: (step: DelegateStep, context: TaskContext) => Promise<boolean>): this;
/** Set progress output sink for real-time execution visibility. */
progressOutput(sink: {
write(chunk: string): unknown;
}): this;
run(): Promise<WorkflowResult>;
}
/**
* Plugin Loader — auto-discovers and registers user plugin files
*
* Discovery strategies (in priority order):
* 1. Config-based: `plugins` array in agent-runtime.config.yaml
* 2. Convention-based: scan `src/plugins/` for files exporting AgentPlugin
*
* Each discovered module is dynamically import()'d. Exports that satisfy the
* AgentPlugin interface (have an `id` string property) are registered with
* the pluginRegistry singleton.
*/
interface PluginLoadResult {
loaded: number;
files: string[];
errors: Array<{
file: string;
error: string;
}>;
}
/**
* Load plugins from explicit paths (config-based discovery).
*/
declare function loadPluginsFromPaths(paths: string[], baseDir: string): Promise<PluginLoadResult>;
/**
* Load plugins using convention-based directory scanning.
* Scans `<projectDir>/src/plugins/` by default.
*/
declare function loadPluginsFromDirectory(dir: string): Promise<PluginLoadResult>;
/**
* Auto-discover and load plugins. Uses config-based paths if provided,
* otherwise falls back to convention-based scanning of src/plugins/.
*/
declare function loadPlugins(options: {
pluginPaths?: string[];
projectDir: string;
}): Promise<PluginLoadResult>;
/**
* Runtime DSL Context — builds WorkflowRegistries from embedded DSL data,
* optionally merging with project-level DSL extensions.
*
* Default flow:
* CLI binary embeds resolved DSL data (from code generation) →
* loadDslContext() builds registries from it.
*
* Extended flow:
* Project provides a DSL that extends/overrides the embedded base →
* loadDslContext() merges via agent-contracts' mergeDsl() →
* builds registries from the merged result.
*/
interface LoadDslOptions {
/**
* Pre-resolved DSL data embedded in the CLI binary.
* Produced by the code generator alongside TypeScript contracts.
* This is the default DSL — used as-is when no project extension exists.
*/
embeddedDsl: Record<string, unknown>;
/**
* Path to a project-level DSL file that extends/overrides the embedded DSL.
* When provided, the project DSL is resolved (including its own extends chain)
* and merged with embeddedDsl via mergeDsl().
*/
projectDslPath?: string;
/** Entities the CLI depends on — validated after merge. */
requiredEntities?: RequiredEntities;
}
interface RequiredEntities {
workflows?: string[];
tasks?: string[];
}
interface DslContext {
readonly registries: WorkflowRegistries;
readonly dsl: Record<string, unknown>;
readonly guardrailRules?: GuardrailRuleData;
}
declare class DslValidationError extends Error {
readonly missing: string[];
constructor(missing: string[]);
}
declare function buildGuardrailRulesFromDsl(dsl: Record<string, unknown>): GuardrailRuleData | undefined;
declare function buildRegistriesFromDsl(dsl: Record<string, unknown>): WorkflowRegistries;
/**
* Build a DslContext from embedded DSL data, optionally merging with
* a project-level DSL extension.
*
* Default (no projectDslPath):
* Builds registries directly from embeddedDsl — no filesystem access.
*
* Extended (with projectDslPath):
* Resolves the project DSL from filesystem, merges it over embeddedDsl
* via agent-contracts' mergeDsl(), then builds registries from the result.
*/
declare function loadDslContext(options: LoadDslOptions): Promise<DslContext>;
/**
* Runtime JSON Schema → Zod converter.
*
* Mirrors the compile-time zodType Handlebars helper in
* generator/template-engine.ts but produces live z.ZodType
* objects instead of code strings.
*/
/**
* Convert a JSON Schema definition to a Zod schema object at runtime.
*
* Supports the same subset as the compile-time zodType helper:
* object, array, string, number, integer, boolean, enum, const, allOf.
*/
declare function jsonSchemaToZod(schema: Record<string, unknown>): z.ZodType;
/**
* Progress Sink — configurable output target for agent execution progress.
*
* Supports writing to stderr, file, or both (tee). File naming strategies
* allow single-file append, per-invocation unique files, or daily rotation.
*/
interface ProgressSinkOptions {
/** Write to process.stderr. Default: false */
stderr?: boolean;
/** Base file path for log output. */
file?: string;
/**
* File naming strategy:
* - "single": always append to `file` (default)
* - "per-invocation": {dir}/{base}-{YYYY-MM-DDTHHmmss}-{label}{ext}
* - "daily": {dir}/{base}-{YYYY-MM-DD}{ext}
*/
naming?: "single" | "per-invocation" | "daily";
/** Label for per-invocation naming (e.g. workflow ID). */
label?: string;
}
interface ProgressSink {
write(chunk: string): void;
close(): void;
/** Resolved file path (undefined if file output is disabled). */
readonly filePath?: string;
}
declare function createProgressSink(options: ProgressSinkOptions): ProgressSink;
/**
* Adapter Factory — public API for creating SDK adapters by name.
*
* Centralises the adapter-name → SDK-class mapping so that consumers
* no longer need a local switch statement with dynamic imports.
*/
type PermissionMode = "default" | "acceptEdits" | "bypassPermissions" | "plan";
interface CreateAdapterOptions {
model?: string;
cwd?: string;
tools?: string[];
permissionMode?: PermissionMode;
guardrailHooks?: GuardrailHooks;
}
/**
* Create an SDK adapter instance by name.
*
* @param name - One of: "mock", "claude", "openai", "gemini"
* @param options - Adapter-specific configuration
*/
declare function createAdapter(name: string, options?: CreateAdapterOptions): Promise<SdkAdapter>;
/**
* High-level execution APIs — executeTask / executeWorkflow
*
* Encapsulates the common boilerplate shared across all CLI consumers:
* 1. Adapter creation
* 2. DSL context loading (registries)
* 3. Progress sink setup
* 4. Task/workflow invocation with retry policy
*
* CLI consumers only need to provide:
* - domain-specific context (user_request)
* - adapter name + options
* - DSL blob (resolvedDsl from codegen)
*/
interface ExecuteOptions {
adapter: string;
model?: string;
dsl: Record<string, unknown>;
projectDslPath?: string;
logFile?: string;
adapterOptions?: Partial<CreateAdapterOptions>;
maxFollowUps?: number;
maxRetries?: number;
/** Pre-resolved component from BoundResolver (skips runtime resolve). */
resolvedComponent?: ResolvedComponent;
/** Component id for BoundResolver cache keying. */
componentId?: string;
/** Software binding paths for runtime guardrail resolution. */
bindingPaths?: string[];
/** Active guardrail policy name. */
activeGuardrailPolicy?: string;
/** Artifact binding config for runtime resolveBound(). */
artifactBinding?: ArtifactBindingConfig;
/** Path substitutions for artifact binding. */
paths?: Record<string, string>;
}
interface TaskHookContext {
adapter: SdkAdapter;
registries: WorkflowRegistries;
taskId: string;
}
interface ExecuteTaskOptions extends ExecuteOptions {
request: string;
hooks?: {
beforeTask?: (ctx: TaskHookContext) => Promise<void>;
afterTask?: (result: TaskRunResult) => Promise<void>;
};
}
declare function executeTask(taskId: string, options: ExecuteTaskOptions): Promise<TaskRunResult>;
interface WorkflowHookContext {
adapter: SdkAdapter;
registries: WorkflowRegistries;
workflowId: string;
}
interface ExecuteWorkflowOptions extends ExecuteOptions {
request?: string;
handoff?: HandoffInput<string>;
context?: {
cwd?: string;
environment?: "local" | "ci" | "remote";
artifacts?: Record<string, string>;
variables?: Record<string, unknown>;
};
autoApproveGates?: boolean;
hooks?: {
beforeWorkflow?: (ctx: WorkflowHookContext) => Promise<void>;
afterWorkflow?: (result: WorkflowResult) => Promise<void>;
onStepComplete?: (event: StepCompleteEvent) => void;
onGate?: (gateKind: string, description: string, stepResults: StepResult[]) => Promise<boolean>;
onOptionalStep?: (step: DelegateStep, context: TaskContext) => Promise<boolean>;
};
}
declare function executeWorkflow(workflowId: string, options: ExecuteWorkflowOptions): Promise<WorkflowResult>;
export { type AdapterOrFactory, AgentContractLike, type AgentPlugin, type ArtifactBindingConfig, type BoundResolveOptions, BoundResolver, type CreateAdapterOptions, type DelegateStep, type DslContext, DslValidationError, type ExecuteOptions, type ExecuteTaskOptions, type ExecuteWorkflowOptions, type GateStep, GuardrailHooks, type GuardrailResult, GuardrailRuleData, type HandoffInput, type LoadDslOptions, type ModelAwareSdkAdapterFactory, type ModelClass, type ModelMappingConfig, type ModelResolver, type ModelResolverOptions, type PermissionMode, type PluginLoadResult, PluginRegistry, type ProgressSink, type ProgressSinkOptions, type PromptBuilderArgs, PromptBuilderOptions, type RequiredEntities, type ResolvedComponent, type ResolvedModel, type Runtime, type RuntimeConfig, SdkAdapter, type SdkAdapterFactory, type StepCompleteEvent, type StepResult, TaskContext, TaskContractLike, type TaskHookContext, TaskOutcome, TaskRunResult, WorkflowBuilder, type WorkflowContractLike, type WorkflowHookContext, type WorkflowInvocation, type WorkflowRegistries, type WorkflowResult, type WorkflowRunOptions, type WorkflowStep, buildGateEvaluationPrompt, buildGuardrailRulesFromDsl, buildPerAgentGuardrails, buildRegistriesFromDsl, createAdapter, createModelResolver, createProgressSink, createRuntime, executeTask, executeWorkflow, getEffectiveGuardrailIds, getGuardrailRulesForAgent, guardrailContextToRuleData, jsonSchemaToZod, loadDslContext, loadPlugins, loadPluginsFromDirectory, loadPluginsFromPaths, parseGateEvaluationResponse, pluginRegistry, runWorkflow };