@sema-agent/core
Version:
Stateless, task-oriented AI agent core
127 lines (126 loc) • 8.29 kB
TypeScript
import type { RunnerDeps, TaskSpec } from "../core/types.js";
/**
* design/98 §1.2 / §C — is LLM self-orchestration ACTIVE for this task? `true` ONLY when the task opted in
* AND the deployment provides BOTH a hard sandbox (`workflowScriptRunner.safeForUntrustedScripts`) AND a
* governance baseline (`workflowGovernanceBaseline`). This is the SINGLE gate that drives BOTH the
* orchestration-prompt injection (S8a) and the `run_workflow` tool mount (S8c) — kept in lockstep so the
* prompt never claims a capability the task lacks (§6.3). FAIL-CLOSED: any missing piece ⇒ off.
*/
export declare function isSelfOrchestrationActive(spec: TaskSpec, deps: RunnerDeps): boolean;
/**
* design/98 §C — the DEPLOYMENT-level capability: does this deployment SUPPORT self-orchestration / workflows
* AT ALL? `true` ⟺ it provides BOTH a hard sandbox (`workflowScriptRunner.safeForUntrustedScripts`) AND a
* governance baseline (`workflowGovernanceBaseline`). This is the deps half of {@link isSelfOrchestrationActive}
* with NO per-task `selfOrchestration` opt-in — the canonical predicate a control plane projects onto a
* capability surface (e.g. service `GET /v1/capabilities`.`workflows`) so a shell can HONESTLY gate `/workflows`
* + ultracode affordances: dark when `false` (the deployment can never run a workflow), available when `true`.
*
* SINGLE SOURCE so the surfaced capability can never drift from the gate that actually mounts `run_workflow`
* (a shell that shows `/workflows` as available while the engine fail-closes the tool = the exact mismatch
* design/98 §6.3 forbids). Per-principal/tier gating does NOT exist today — this is deployment-wide.
*/
export declare function workflowsCapability(deps: RunnerDeps): boolean;
/**
* design/98 §1.2 — the task OPTED IN (`selfOrchestration:true`) but the deployment did not meet the bar (a
* hard sandbox + a governance baseline), so self-orchestration is FAIL-CLOSED. Returns a human-readable
* reason for an operator warning, or `null` when not in this state (off, or fully active). The runner emits
* ONE operator-visible warning so a misconfigured opt-in is never silent.
*/
export declare function selfOrchestrationFailClosedReason(spec: TaskSpec, deps: RunnerDeps): string | null;
/**
* design/98 §D.3/§E.1 — the `WorkflowScriptRunner` SEAM for running an LLM-AUTHORED workflow script
* (S8 self-orchestration). A deployment provides the runner; **core does NOT ship a hard sandbox** (the
* isolated-vm / separate-process implementation is a deployment concern or a separate
* `@ai-only/workflow-sandbox` package). Core defines this contract + a TRUSTED-DEV vm runner
* ({@link import("./dev-vm-script-runner.js").devWorkflowScriptRunner}, S8b) that is explicitly NOT a
* security boundary, plus a conformance contract a hard runner must pass.
*
* 🔴 **Membrane = STRUCTURAL constraint, not a behavioral promise.** The seam's
* `run()` accepts ONLY {@link WorkflowPrimitives} — a flat, sterile record of bound callbacks + a budget
* DATA snapshot. It does NOT accept a `WorkflowRunContext` (a host object whose prototype chain the script
* could walk via `ctx.constructor.constructor("return process")()` to escape). The engine extracts the bound
* functions FROM the context and constructs the primitives; the host context object structurally never
* enters the runner — there is no prototype chain into the host reachable from the script. A hard runner
* bridges each primitive as an isolated-vm `Reference`/`Callback` and copies `scriptArgs` via `ExternalCopy`.
*/
/** The `export const meta = {...}` a workflow script declares (a PURE literal — parsed by the meta-AST,
* never `eval`'d; design/98 §2.4). Only `name`/`description` are required; the rest is observability. */
export interface WorkflowMeta {
name: string;
description: string;
/** PARITY-SPOT-WORKFLOW B7 (CC card pretty.js:448042): `model` on a phase entry documents that phase's
* model override — parsed + stored + displayed on the pre-registered run phase (no behavior yet). */
phases?: Array<{
title: string;
detail?: string;
model?: string;
}>;
whenToUse?: string;
}
/**
* The FLAT, STERILE primitives a workflow script may call — bound functions + a budget DATA snapshot, and
* NOTHING else from the host (design/98 §2.1). NO `ctx` object, NO host prototype chain. The HARD runner
* bridges each function as an isolated-vm `Reference`/`Callback` (the script `await`s them, round-tripping
* back to the main isolate where the real {@link import("./workflow.js").WorkflowRunContext} executes them).
*
* The `spec`/`opts`/stage/thunk shapes are `unknown` at this boundary on purpose: an LLM-authored `spec` is
* UNTRUSTED input that the engine's `agent` primitive validates + governs (whitelist construction +
* `tightenTaskSpec`, design/98 §2.5) BEFORE it ever reaches `runner.runTask`. The seam must not promise a
* trusted `TaskSpec` here.
*/
export interface WorkflowPrimitives {
/** Spawn one governed sub-agent. `spec` is an UNTRUSTED `WorkflowAgentSpec` (whitelist-constructed by the
* engine, design/98 §2.5); `opts` carries `label`/`phase`/`schema`. Resolves to the task result. */
agent(spec: unknown, opts?: unknown): Promise<unknown>;
/** Run thunks concurrently (BARRIER); a thrown thunk resolves to `null`. */
parallel(thunks: unknown[]): Promise<unknown[]>;
/** Run each item through all stages independently (no barrier between stages). */
pipeline(items: unknown[], ...stages: unknown[]): Promise<unknown[]>;
/** Group work under a named phase (observability). */
phase(title: string, body: unknown): Promise<unknown>;
/** Emit a narrator log line. */
log(message: string): void;
/** LIVE budget accessors, mirroring {@link import("./workflow.js").WorkflowBudget} (NOT a one-shot data
* snapshot — a static snapshot would break the `while (budget.remaining() > N)` budget-scaled-depth loop
* the orchestration guidance promotes). `total` is a stable number/null; `spent()`/`remaining()` are bound
* functions the hard runner bridges (an isolated-vm `Reference`/`Callback`, like the other primitives) so
* each call re-reads the current spend. */
budget: {
total: number | null;
spent(): number;
remaining(): number;
};
}
/**
* The seam a deployment implements to execute an LLM-authored workflow script. The engine calls `run()` with
* the sterile {@link WorkflowPrimitives} + the script source + (copied) args; the runner compiles/executes
* the script in its isolation domain and returns the script's value + parsed {@link WorkflowMeta}.
*/
export interface WorkflowScriptRunner {
/**
* Whether this runner can SAFELY execute UNTRUSTED (LLM-authored) scripts. `true` = a hard sandbox
* (isolated-vm / separate process + container/seccomp). `false` = trusted-DEV authors only (the Node `vm`
* runner — NOT a security boundary, see {@link import("./dev-vm-script-runner.js").devWorkflowScriptRunner}).
*
* 🔴 The S8 gate (design/98 §C / §1.2) mounts the `run_workflow` tool + injects the orchestration prompt
* ONLY when this is `true`. `selfOrchestration:true` with no hard runner ⇒ FAIL-CLOSED (tool not mounted,
* prompt not injected, operator warning). Core NEVER runs an LLM script in the dev `vm`.
*/
readonly safeForUntrustedScripts: boolean;
run(input: {
scriptSource: string;
primitives: WorkflowPrimitives;
scriptArgs: unknown;
/**
* A HOST `AbortSignal` for ABORT CONTROL (the workflow aborting cancels the script run). 🔐 The runner MUST
* NOT expose this raw host object to the untrusted script — a host object's `.constructor.constructor` climbs
* to the host `Function`/`process`. Use it to abort isolate execution; if you surface abort-awareness to the
* script at all, wrap it isolate-side. {@link assertWorkflowSandboxConformance} supplies a host signal and
* probes for this leak (CORE-1~9 audit MAJOR).
*/
signal?: AbortSignal;
}): Promise<{
result: unknown;
meta: WorkflowMeta;
}>;
}