# Attachments (/docs/advanced/attachments) Callers can attach images, audio, or files to a goal through `delta.send()`. Attachments give the agent access to content beyond plain text without changing the state-space, budget, or risk model: they are input, not action. ## Attachment kinds [#attachment-kinds] ```ts type AttachmentInput = { kind: "image" | "file" | "audio"; mimeType: string; data?: string; // base64-encoded content url?: string; // remote URL (image only: audio requires data) name?: string; // filename for audit and display }; ``` The kind is explicit and required: the caller states intent; it is never inferred from mimeType (a PDF could contain images; inference would be ambiguous). The engine assigns the attachment's ID: callers never self-assign one, the same rule that governs TaskIDs. ## Images [#images] Images are embedded as real vision content in the reasoner call. The model literally sees the image. The agent's resolved model must declare `vision: true`: the engine rejects an image attachment sent to a non-vision model before any task is created, with a deterministic failure rather than a silent drop. ## Audio [#audio] Audio attachments are embedded as `input_audio` content parts when the model declares `audio: true`. Audio has a narrower path than images: * Audio requires inline `data` (base64-encoded). The provider's audio content part accepts no URL, so an audio attachment with only `url` is rejected at `send()` time. * The mimeType must map to a supported format (wav or mp3). An unmappable mimeType is rejected before task creation. ## Files [#files] Files are never sent to the model as raw bytes. No assumption is made that every provider can ingest arbitrary file content as part of a chat request. Instead, the model sees a short reference (id, mimeType, name) and can read the file's contents through a tool built for that purpose, such as the builtin `document-extract` tool. Reading a file is a tool's job. Seeing or hearing image and audio content is the model's. ## Attaching content to a send [#attaching-content-to-a-send] ```ts import { loadAttachmentFromFile, loadAttachmentFromUrl } from "delta-agents"; const imageAttachment = await loadAttachmentFromFile("/path/to/photo.png"); const pdfAttachment = await loadAttachmentFromUrl("https://example.com/doc.pdf"); const result = await delta.send({ goal: "Extract and summarize the information from these files", agentName: "analyst-agent", attachments: [imageAttachment, pdfAttachment], budget: { tokens: 10000, durationMs: 120_000 }, }); ``` ## Attachment lifecycle [#attachment-lifecycle] Attachments persist for the duration of the task run. Every reasoner turn is a fresh reconstruction of context from current task state, so an attachment stays available to be shown again on a later turn rather than being consumed once and discarded. ## Resolving bytes [#resolving-bytes] The engine never touches the filesystem and never makes an outbound network call to resolve an attachment's content. Callers resolve bytes explicitly before calling `send()`, optionally through the built-in loaders. This keeps `send()` free of I/O side effects. ## Capability enforcement [#capability-enforcement] Model capability is declared, not assumed. A `ModelDef` may declare `vision: true` or `audio: true`. Sending an image or audio attachment to an agent whose model does not declare the matching capability is rejected before task creation: a deterministic failure, not a silent drop. The engine owns enforcement, not the model's actual capability at request time. # Channels (/docs/advanced/channels) Delta agents communicate through messaging platforms such as Slack, Teams, Discord, and Telegram. Agents operate independently of channel availability: execution is decoupled from delivery. ## How channels work [#how-channels-work] A channel is a bridge between the agent and an external messaging platform. The agent sends a message through the channel, and the channel delivers it to the platform. Responses from the platform arrive as new messages that the agent processes on its next turn. ```ts type Channel = { type: SupportedChannels; enabled: boolean; sendMessage: (message: string, ctx: ChannelContext) => Promise; retrieveMessages?: (ctx: ChannelContext) => Promise; replyMessage?: (id: string, message: string, ctx: ChannelContext) => Promise; }; ``` ## Defining channels on an agent [#defining-channels-on-an-agent] Channels are declared per agent. An agent can have multiple channels, each connecting to a different platform. ```ts const supportAgent = delta.agent({ name: "support-agent", channels: [ slackChannel, teamsChannel, discordChannel, ], }); ``` ## Chat SDK integration [#chat-sdk-integration] Delta provides a `createChatSdkChannel` adapter that bridges agents to the [Chat SDK](https://www.npmjs.com/package/chat) ecosystem. This single adapter works across multiple platforms: Slack, Teams, Discord, Telegram: without per-platform code. ```ts import { createChatSdkChannel } from "delta-agents"; const slackChannel = createChatSdkChannel({ type: "slack", adapter: slackAdapter, }); ``` ## Cross-platform continuity [#cross-platform-continuity] A conversation can flow across platforms. An agent that starts in Slack and continues in Telegram maintains context: messages are task-attributable and the engine tracks the conversation across channels. The agent does not lose state when a user switches platforms. ## Channel availability [#channel-availability] Agent execution is decoupled from channel delivery. If a channel is temporarily unavailable (network issue, platform outage), the agent continues working. Messages queue in the agent's outbox and deliver when the channel recovers. ## Communication from actions [#communication-from-actions] Actions can send messages through channels using `ctx.communicate()`: ```ts const notifyCustomer = delta.action({ name: "notify-customer", schema: z.object({ message: z.string() }), fn: async ({ message }, ctx) => { return ctx.communicate("slack", message); }, }); ``` The `communicate` method routes through the agent's configured channels and is governed by the same authorization and budget constraints as any other operation. # Memory (/docs/advanced/memory) Delta agents retrieve context from past tasks on demand and record completed work for future reference. Memory is retrieved, not carried: agents load relevant context when needed rather than maintaining unbounded working memory. ## How memory works [#how-memory-works] Before every reasoning turn, the engine retrieves relevant memory entries for the agent and injects them into context. This happens automatically: the agent does not need to request it. Memory entries are: * **Attributable to a TaskID**: every memory access traces back to the task that created or read it. * **Keyword-ranked**: retrieval uses keyword matching (no embeddings required). Semantic ranking is available for future embedding-based retrieval. * **Scoped to the agent**: an agent sees its own memories, not memories from other agents. ## Recording memory [#recording-memory] Actions can write to memory using `ctx.remember()`: ```ts const resolveIssue = delta.action({ name: "resolve-issue", schema: z.object({ issueId: z.string(), resolution: z.string() }), fn: async ({ issueId, resolution }, ctx) => { await ctx.remember(`Resolved issue ${issueId}: ${resolution}`, "resolution"); return Ok({ resolved: true }); }, }); ``` Memory writing is explicit: the agent declares what is worth remembering. The engine does not automatically capture execution outcomes as memories (though this is available as a future enhancement). ## Commits [#commits] Commits are structured records of completed work. Unlike free-form memory entries, commits carry workflow context (which workflow ran, which checkpoint) and optional notes from the agent. ### After a workflow [#after-a-workflow] When a workflow completes, the engine enters a commit step: a single constrained reasoning turn where the model can only choose to commit (with notes) or fail. If the model exhausts retries, the engine auto-commits with no notes: the task always converges to completed. ```ts // Task completes, engine runs commit step // Agent writes: "Processed order ORD-42: confirmed, charged, notified customer" ``` ### During a free loop [#during-a-free-loop] The agent can voluntarily commit at any point using the `system:commit` tool. Unlike the post-workflow commit, this does not end the task: the commit is recorded and execution continues. ### Commit history [#commit-history] Commits are persisted and retrievable. The last N commits (configurable via `commitContextLimit`, default 10) are injected into the agent's reasoning context on every turn, so the agent is aware of its recent history. ```ts // Recent commits appear in context: // - [fulfillment] Processed order ORD-42 (about 2 minutes ago) // - [refund] Issued refund for order REF-101 (about 1 hour ago) ``` ### Searching commits [#searching-commits] The agent can search its own commit history using the `system:search_commits` tool. This returns matching commits by keyword, workflow name, or time range. ## Time awareness [#time-awareness] The engine injects the current time into every reasoning turn so agents can make time-sensitive decisions. Current time includes a humanized description ("about 2 minutes ago"), an ISO timestamp, and the configured timezone. Prior messages from the conversation are also formatted with relative time labels, so the agent can perceive time gaps across the conversation: a message that arrived 4 hours ago reads differently than one that arrived moments ago. Configure the timezone at engine creation: ```ts const delta = await createDeltaEngine({ timezone: "Africa/Lagos", // ... }); ``` Defaults to the system timezone when omitted. # Multi-Agent (/docs/advanced/multi-agent) Delta agents coordinate through delegation, bounded supervision trees, mailbox-based communication, and a live team roster. Agents delegate to other agents; the engine governs every interaction. ## Team roster [#team-roster] Every agent's teammates and their current load are visible to the agent in its reasoning context. The roster reports per-agent activity: the current goal or phase of the major task, the number of active subtasks, and whether the agent is overloaded. An agent is overloaded when all execution slots are used or it carries a queued backlog. The roster helps agents route delegation and mentions toward idle teammates and away from overloaded ones. Developers can also read the roster: ```ts const teamStatus = await delta.roster({ team: "customer-support" }); ``` The roster is derived from live task and message state: never stored, always consistent, survives restarts with no bookkeeping to drift. ## Bounded delegation [#bounded-delegation] Agents delegate to other agents through a bounded supervision tree. The structure guarantees predictable cost and risk profiles: * One active parent task per supervisor. * Up to two active subtasks. * Additional work enters a FIFO queue. * No additional task may execute until a slot becomes available. Subtasks inherit scoped budgets and permissions from their parent. A subtask never gains authority beyond the parent's scope. If a parent task is aborted, all descendant tasks are aborted: the abort cascades down the tree. ## Agent mailboxes [#agent-mailboxes] Every agent has a durable inbox and outbox. Messages are queued and attributable to tasks. Delivery is turn-only: a message never interrupts an agent mid-task; it waits until the recipient's next turn folds it into context. ### Inbox [#inbox] Messages addressed to an agent, unread first then oldest-first. Recalled messages are excluded. ```ts const inbox = await delta.inbox({ agent: "support-agent" }); ``` ### Outbox [#outbox] Messages the agent sent, newest first, including recalled messages. ```ts const sentMessages = await delta.outbox({ agent: "support-agent" }); ``` ### Read receipts [#read-receipts] Reading stamps `readAt` on the message. The receiver sees it on their inbox; the sender sees it on their outbox. A sender can tell whether, and when, a message was read. ### Unsend [#unsend] An agent can unsend a message, but only while it is still unread. The turn-only model gives a clean window: the sender can retract a message before the recipient's next turn reads it. An unsent message is removed from the inbox and excluded from delivery, but remains visible as unsent in the sender's outbox. ```ts const recalled = await delta.unsend({ messageId: "msg_abc123" }); ``` ### Inbox capacity [#inbox-capacity] A configurable per-agent `inboxCap` bounds inbox growth. Once exceeded, the oldest read messages are evicted first. Unread messages are never dropped. ## Multi-agent communication [#multi-agent-communication] Agents can send messages to each other through the mailbox system. Communication is task-attributable, queued, and turn-only: consistent with the no-interrupt model. Messages carry payloads and are delivered when the recipient's next reasoning turn folds them into context. When an agent is busy, a new goal from another agent is queued as a message on its existing task rather than creating a new task. The engine never creates a new task for an agent that already has active or queued work. ## Teams [#teams] Agents can be grouped into named teams. Team membership scopes roster visibility and mailbox awareness. Agents on the same team see each other's load and can delegate intelligently. Team-less agents peer with everyone. # Observability (/docs/advanced/observability) Every action, decision, and token is traceable and attributable. Delta provides inspection, logging, diagnostics, and cleanup capabilities for understanding agent behavior and maintaining system health. ## Task inspection [#task-inspection] Call `delta.inspect(taskId)` to read the full governance state for any task: ```ts const state = await delta.inspect(taskId); if (state.isOk) { const { task, executions, latestCheckpoint, escalations, pendingApprovals } = state.value; console.log(task.status); // current execution status console.log(executions); // every action execution record console.log(latestCheckpoint); // latest recoverable state boundary console.log(escalations); // all escalation events console.log(pendingApprovals); // approvals waiting for a human decision } ``` All entries are TaskID-attributable: every execution, escalation, and approval traces back to the task that produced it. ### Task record [#task-record] The task record carries current status, budget state, risk estimate, trust score, and timestamps. Status values: * **`running`**: actively executing. * **`completed`**: all actions ran and terminated normally. * **`failed`**: non-recoverable failure (budget exhaustion, unrecoverable error). * **`blocked`**: waiting on a human decision (approval gate). * **`pendingCommit`**: waiting for the agent to commit after a workflow run. * **`aborted`**: terminated by an abort cascade. * **`paused`**: suspended by `delta.pause()`. ### Execution history [#execution-history] Every action execution is recorded with its start time, end time, and outcome. Executions are ordered by creation and include the action name, the task it belongs to, its cost, and its status. ## Trust and risk [#trust-and-risk] Trust and risk are derived from observed behavior. The engine continuously revises both from what actually happened: reliable agents gain autonomy, risky agents get more oversight. * **Trust** accumulates with successful executions and decays rapidly on failure. Unexpected failures incur substantially larger penalties than successful outcomes provide rewards. The system intentionally biases toward caution. * **Risk** is continuously estimated from predicted progress, observed progress, time consumption, token consumption, and tool outcomes. Large deviations between expected and observed outcomes increase risk. Call `delta.inspect(taskId)` to read the current trust score and risk estimate for any task. ## Logging [#logging] The engine creates a per-instance logger at construction time. The logger offers multiple output modes: * **Dev mode (default)**: colorized pino-pretty output to console at info level. * **Production mode**: configurable drain: file (daily rotation), SQLite (queryable), or custom. ```ts const delta = await createDeltaEngine({ logger: { mode: "prod", level: "warn", drain: { type: "file", dir: "./logs", }, }, }); ``` Log entries carry the full context needed to diagnose issues: module name, task ID, error cause, and function. Log drain failures never throw: a logger that destabilizes the system it audits is worse than a missing entry. ## Diagnostics [#diagnostics] Diagnostics provide structured, opt-in event emission for debugging the engine itself. Each module can be independently enabled: ```ts const delta = await createDeltaEngine({ diagnostics: { actions: true, workflows: true, governance: true, supervision: true, memory: true, comms: true, tools: true, engine: true, }, }); ``` When a module is enabled, it emits structured events (timing, decision traces, counts) to the logger at debug or trace level. When disabled, the path is provably zero overhead: the module never touches the logger. ## Commit history [#commit-history] Completed work is recorded as commits. The commit history tracks what was done, by whom, and when, enabling audit and learning from past runs. ## Agent performance and benchmarks [#agent-performance-and-benchmarks] Track agent and workflow performance with derived read-models computed on-demand from task history. Rank agents by success rate or trustworthiness, profile cost and duration patterns, and measure workflow efficiency per phase. ```ts // Top agents by completed tasks, success rate, or trust score const topAgents = await delta.topAgents({ by: "completedTasks", // "completedTasks" | "successRate" | "trustScore" limit: 10 }); // Performance metrics for one agent: costs, durations, success rate, trust trajectory const agentStats = await delta.agentStats({ agent: "support-agent" }); // Workflow benchmark: runs, per-phase durations, overall success rate and cost const workflowStats = await delta.workflowStats({ workflow: "refund-flow" }); ``` All three read-models return zero-valued stats for unknown agents or workflows, not errors. They are derived from persisted tasks and executions, never stored separately, so they always reflect the current state of the system. ## Events [#events] The engine emits typed events for significant occurrences: task lifecycle changes, human oversight signals, and execution milestones. Subscribe to react in real time instead of polling `inspect()`. Every event carries a `timestamp` (ms since epoch) and `agentName` in the envelope. ```ts const unsubscribe = delta.events.on("approval-requested", (data) => { console.log(`${data.agentName} at ${new Date(data.timestamp)}: Task ${data.taskId} needs sign-off for ${data.action}: ${data.reason}`); // Forward to Slack, update a dashboard, trigger a webhook }); // Later, unsubscribe when no longer needed unsubscribe(); ``` Events that involve human oversight (approvals, escalations, and task outcomes) always fire, regardless of configuration. Engine-internal events (step timing, action execution) respect the `diagnostics.engine` toggle so they can be enabled for debugging and disabled in production. Available event types: | Event | Payload | When it fires | | -------------------- | ------------------------------------------------------------ | ------------------------------------------------ | | `approval-requested` | `taskId, action, approvalId, reason, timestamp, agentName` | An action needs human sign-off | | `approval-resolved` | `taskId, action, approvalId, decision, timestamp, agentName` | Approve or reject resolves it | | `escalation-raised` | `taskId, trigger, reason, timestamp, agentName` | A reasoner failure or budget violation escalates | | `task-completed` | `taskId, agentName, goal, timestamp` | A task finishes successfully | | `task-blocked` | `taskId, agentName, reason, timestamp` | A task is waiting on human oversight | | `task-failed` | `taskId, agentName, reason, timestamp` | A task terminates with failure | Engine-internal events (gated by `diagnostics.engine`): `step-start`, `step-end`, `action-start`, `action-end`, `commit-step-attempt`, `commit-step-done`, `commit-step-auto-commit`. All include `timestamp` and `agentName`. ## Cleanup [#cleanup] The engine provides two levels of cleanup for managing accumulated state: **Opportunistic cleanup** runs automatically at the start of every `send()` and `inspect()` call, evicting expired cache entries. Synchronous and cheap. **Manual cleanup** prunes completed or failed tasks and consumed messages past their retention windows: ```ts await delta.cleanup({ taskRetentionMs: 7 * 24 * 60 * 60 * 1000, // keep tasks for 7 days messageRetentionMs: 30 * 24 * 60 * 60 * 1000, // keep messages for 30 days }); ``` Destructive store operations are opt-in. Missing retention parameters skip the corresponding pruning. Cache eviction runs by default but can be disabled with `evictCache: false`. # Actions (/docs/basics/actions) An action is the smallest executable unit in delta-agents. It has a name, a description, a Zod schema for input validation, and a function that returns an explicit `Ok` or `Err` Result. ## Action structure [#action-structure] ```ts type Action = { name: string; description: string; schema: ZodObject; risk?: 1 | 2 | 3 | 4 | 5; estimatedCost?: Cost; requiresApproval?: boolean; prerequisites?: { actions?: string[]; workflows?: string[]; }; hooks?: Hooks; tools?: string[]; fn: (data: ActionInput, ctx: ActionContext) => Promise; }; ``` ## Defining an action [#defining-an-action] ```ts const confirmOrder = delta.action({ name: "confirm-order", description: "Confirm an order with the customer", risk: 2, schema: z.object({ orderId: z.string() }), fn: async ({ orderId }) => Ok(await orders.confirm(orderId)), }); ``` The engine validates every call against the schema before the function runs. Malformed input never reaches the action function. ## Risk scoring [#risk-scoring] Actions may declare an anticipated risk level from 1 to 5. * **1**: safe, routine operation with no side effects. * **5**: dangerous or irreversible action. Risk seeds the Kalman state estimator so the engine starts from a calibrated expectation. The engine continuously refines its own estimate from observed behavior and may raise risk above the declared level when evidence warrants it. A low declared risk never overrides an observed danger. Actions at or above the task's risk threshold require human approval before execution. ## Prerequisites [#prerequisites] An action may declare prerequisites that must complete before the action becomes legal. Prerequisites can be other actions or entire workflows. ```ts const processOrder = delta.action({ name: "process-order", description: "Charge and process a confirmed order", risk: 4, requiresApproval: true, prerequisites: { actions: ["confirm-order"], }, schema: z.object({ orderId: z.string() }), fn: async ({ orderId }) => Ok(orders.process(orderId)), }); ``` With unsatisfied prerequisites, the action does not exist in the current state-space. The engine never exposes or executes it. An agent cannot process an order before confirming it: enforced structurally, not by instruction. ## Hooks [#hooks] Hooks allow observation and preparation around execution without bypassing governance. Three hook points are available: * **`before`**: runs before the action executes. Use for logging, setup, or validation that the action itself should not own. * **`after`**: runs after a successful action outcome. Receives `ctx` and the action's `result` value. * **`onError`**: runs when the action returns an `Err` outcome. Receives `ctx` and the failure `error` message. ```ts const processOrder = delta.action({ name: "process-order", hooks: { before: async (ctx) => audit.log("process-order:start", ctx), after: async (ctx) => metrics.record("process-order:success", ctx.result), onError: async (ctx) => alerts.page("process-order:failed", ctx.error), }, // ... }); ``` Hooks observe and prepare. They never authorize actions, never bypass schema validation, risk checks, budget checks, or approval gates, and never grant a capability the engine would otherwise deny. ## Action results [#action-results] Every action function returns a `Result`: either `Ok` carrying the success value or `Err` carrying the failure value. The engine never infers success from the absence of a thrown error. This explicit contract drives trust updates, conditional branching, supervision decisions, and cost friction detection: all of which require a clear pass or fail signal. ## Action context [#action-context] Action functions receive an `ActionContext` with runtime information: ```ts type ActionContext = { taskId: string; executionId: string; agentName: string; phase?: string; storyline?: string; phaseStoryline?: string; goal?: string; workflowName?: string; availableSkills?: Array<{ name: string; description: string; content?: string }>; attachments?: Attachment[]; communicate?: (channelType: string, body: string) => Promise; remember?: (content: string, kind?: string) => Promise; recall?: (query: string) => Promise; budget?: { spent: Cost; limit?: Cost }; }; ``` Key context fields: * **`goal`**: the task's goal, available to actions that need to understand the broader intent. * **`workflowName`**: the assigned workflow name (absent in free reasoner tasks). * **`attachments`**: any files, images, or audio passed with the task. * **`recall`**: read agent memories; resolves the same relevance-ranked context the reasoner uses for grounding. * **`budget`**: read-only cost snapshot (tokens and duration spent, optional limit) so long-running actions can self-limit. The engine assembles the context: the developer never constructs it. ## Action discovery [#action-discovery] The engine exposes only actions that are valid in the current state-space. The model sees actions whose prerequisites are satisfied, whose risk is within threshold, and whose budget permits execution. Invalid actions are not visible: the model cannot request what it cannot see. # Agents (/docs/basics/agents) An agent is a named role with a set of allowed actions, workflows, skills, and channels. The agent defines what the model can do; the engine defines what the model will be allowed to do at any given state. ## Agent structure [#agent-structure] ```ts type Agent = { name: string; description: string; role: string; rolePrompt: string; model?: string; contextWindow?: number; actions: Action[]; workflows?: Workflow[]; skills?: Skill[]; channels?: Channel[]; dataSources?: DataSource[]; team?: string; }; ``` * **`name`**: unique identifier for the agent. Referenced in `delta.send()` and delegation targets. * **`description`**: what this agent does. Visible to other agents for delegation decisions. * **`role`**: the agent's functional role (e.g. "Customer Support Specialist"). * **`rolePrompt`**: behavioral instructions that guide how the agent operates. * **`model`**: named model configuration from the engine's `models` list. Omit to use the default model. * **`actions`**: the operations this agent is permitted to perform. * **`workflows`**: structured multi-phase procedures the agent follows. * **`skills`**: [knowledge resources](/docs/basics/skills) the engine reads at runtime and injects into the agent's context. * **`channels`**: messaging platforms the agent communicates through. * **`dataSources`**: [data stores](/docs/basics/data-sources) the agent reads from and writes to. Each data source's operations become governed actions accessible to the agent. * **`team`**: group name for multi-agent coordination. Agents on the same team share a roster and mailbox awareness. ## Defining an agent [#defining-an-agent] ```ts const supportAgent = delta.agent({ name: "support-agent", description: "Handles customer support requests", role: "Customer Support Specialist", rolePrompt: "Help customers resolve their issues.", actions: [lookupCustomer, notifyCustomer], workflows: [customerSupportWorkflow], skills: [productKnowledge], team: "customer-support", }); ``` The engine validates the agent definition at registration time. Model names are checked against the configured models list, action and workflow names must be unique, and skill paths must reference valid files. ## Per-agent model selection [#per-agent-model-selection] Each agent can use a different model. Define multiple models in engine configuration and assign them per agent. ```ts const delta = await createDeltaEngine({ models: [ { name: "fast", model: "gpt-4o-mini", default: true }, { name: "reasoning", model: "o3-mini" }, ], }); const triageAgent = delta.agent({ name: "triage", model: "fast", // gpt-4o-mini: fast, cheap }); const complexAgent = delta.agent({ name: "analyst", model: "reasoning", // o3-mini: slower, more capable }); ``` Agents that do not specify a model use the engine's default. Switching models requires no code changes: change the configuration, not the agent definitions. ## Deploying an agent [#deploying-an-agent] An agent must be deployed before it can receive tasks. Deploying registers the agent and makes it active. ```ts delta.deploy(supportAgent); ``` Deploy validates that all referenced actions, workflows, and model names exist. If an action references an undeclared workflow or an unknown model, the engine throws at deploy time: not when a task arrives. ## Agent identity and task retrieval [#agent-identity-and-task-retrieval] Every task belongs to exactly one agent. The engine provides retrieval by agent identity so callers do not need to store TaskIDs. ```ts const lastTask = await delta.lastTask("support-agent"); ``` Returns the most recent active or completed task for the named agent. If the agent has no tasks, returns `Ok(null)`. ## Agent lifecycle [#agent-lifecycle] * An agent is defined via `delta.agent()`. * An agent is deployed via `delta.deploy()`. * A deployed agent receives goals via `delta.send()`. * An agent's tasks are inspectable via `delta.inspect()`. * An agent's current load is visible via `delta.roster()`. # Cost and Budget (/docs/basics/cost-and-budget) Delta agents operate within explicit budget ceilings. Every task declares a budget at `send()` time, and the engine enforces it structurally: agents cannot exceed what the task allows. ## Cost model [#cost-model] Cost is a multi-axis vector that tracks resource consumption across several dimensions: ```ts type Cost = { tokens: number; durationMs: number; memory?: number; latency?: number; money?: Money; content?: ContentCost; }; type Money = { value: number; currency: string; // ISO 4217, e.g. "USD", "EUR", "NGN" }; type ContentCost = { count: number; bytes: number; unitType?: "tokens" | "pages" | "images" | "bytes"; itemSize?: number; }; ``` * **`tokens`**: model API token consumption. Always tracked, always enforced. * **`durationMs`**: wall-clock execution time. Always tracked, always enforced. * **`memory`**: memory retrieved or stored during execution. Optional axis. * **`latency`**: time spent waiting on external operations. Optional axis. * **`money`**: financial cost as an explicit value and currency pair. Optional axis. A bare number would assume a single currency; the explicit pair means governance math never silently mixes regions. * **`content`**: attachment or content resource consumption. Populated but not yet enforced by any budget axis. Budget enforcement is per-axis. An axis that is not declared in the budget is unlimited on that axis. ## Setting a budget [#setting-a-budget] Budgets are set per task at `delta.send()`: ```ts const result = await delta.send({ goal: "Process order ORD-42", agentName: "support-agent", budget: { tokens: 5000, durationMs: 60_000, }, }); ``` The default budget is `{ tokens: 10_000, durationMs: 300_000 }` when none is specified. ## Budget enforcement [#budget-enforcement] The engine checks budget before every action execution. If the remaining budget is insufficient to cover the action's estimated or observed cost, the action is blocked. The task transitions to `failed` status with budget exhaustion as the reason. Budget enforcement operates at every governance gateway check, alongside schema validation, prerequisite checking, risk assessment, and approval gates. The sequence is deterministic: budget is checked before any action executes, not after. ## Anticipated cost [#anticipated-cost] Actions and workflows may declare an `estimatedCost`: the developer's expectation of what the operation should consume. ```ts const lookupCustomer = delta.action({ name: "lookup-customer", estimatedCost: { tokens: 500, durationMs: 2000 }, // ... }); ``` Anticipated cost seeds the engine's cost estimator with a prior, so the engine starts from a calibrated expectation instead of a cold one. The engine continuously refines its estimate from observed behavior and may adjust when actual consumption diverges from the estimate. ## Cost in delegation [#cost-in-delegation] When a parent delegates work to a subtask, it reserves a portion of its own budget for the child. The subtask receives a scoped budget and cannot exceed it. Any remaining budget at the child's completion is refunded to the parent. This prevents a runaway subtask from consuming the entire parent budget. ## Budget in tool execution [#budget-in-tool-execution] Tools may declare an optional budget for cost tracking. Unlike action budgets, tool budgets are purely advisory for cost awareness. When a tool's budget is exceeded, the engine blocks further calls and returns a humanized message. ## Observing costs [#observing-costs] The task state snapshot carries the full spent cost at any point. Call `delta.inspect(taskId)` to read the current budget state, including what has been spent and what remains. # Data Sources (/docs/basics/data-sources) Data sources bring external data stores into the governed action system. A data source is a named bundle of CRUD operations over one store. Every operation is a full action: schema validation, budget enforcement, risk gating, approval gates, and audit all apply identically. A data read or write is governed the same way as any other action the agent performs. ## Data source structure [#data-source-structure] ```ts type DataSource = { name: string; description: string; ownership: "internal" | "external"; contentType: string; authentication?: { type: string }; actions: { retrieve?: Action; create?: Action; update?: Action; delete?: Action; }; }; ``` * **`name`**: unique identifier. Agents reference this when listing their data sources. * **`description`**: what store the data source connects to and what data it holds. * **`ownership`**: who owns the data. `internal` means the system running the agent owns the store (its own database). `external` means a third party owns it (a partner API, a customer system). Recorded as audit metadata so an operator can see per task whether the agent touched data outside its own trust boundary. * **`contentType`**: free-form descriptor of the records the source holds (e.g. "customer-orders", "product-inventory"). * **`authentication`**: non-secret descriptor of how the source authenticates (e.g. `{ type: "oauth2" }`, `{ type: "api-key" }`). The engine never stores or transmits credentials; each operation function owns its own secrets through its closure. * **`actions`**: at least one of retrieve, create, update, or delete. Each is a full `Action` with schema, risk, and cost. ## Defining a data source [#defining-a-data-source] ```ts const customerDb = delta.dataSource({ name: "customer-db", description: "Customer records and account data", ownership: "internal", contentType: "customer-records", actions: { retrieve: lookupCustomer, update: updateCustomer, }, }); ``` The engine validates the data source at registration time. Operation names are checked against the agent's action space for conflicts, and at least one operation must be defined. ## Assigning data sources to agents [#assigning-data-sources-to-agents] An agent lists its data sources explicitly. The engine flattens each data source's defined operations into the agent's reachable action set. ```ts const supportAgent = delta.agent({ name: "support-agent", role: "Customer Support Specialist", rolePrompt: "Help customers resolve their issues.", actions: [notifyCustomer], dataSources: [customerDb, orderSystem], }); ``` A data operation is discovered and governed exactly like any other action. The agent proposes a data operation; the engine validates it through the same gateway: schema, prerequisites, budget, risk, approval gates. ## Ownership and risk [#ownership-and-risk] An external data source starts from a less-trusted risk prior. The engine treats a write to an external store with more caution until the action builds a track record of successful executions. Each operation declares its own `risk` on the `Action` as a starting estimate. The engine tightens or loosens that estimate based on how actual behavior compares to what was declared. The ownership field is descriptive: it does not automatically multiply risk. It ensures the operator can audit whether an agent touched data outside its own trust boundary. # Human Oversight (/docs/basics/human-oversight) Every autonomous system needs a circuit breaker. Delta provides a structured human-in-the-loop layer where the engine pauses, informs, and waits for a human decision before allowing sensitive actions to proceed or recovering from failures the agent cannot resolve alone. ## How it works [#how-it-works] The engine never assumes perfect autonomy. When an agent reaches a governance boundary it cannot cross alone (an action that requires approval, a budget violation, a persistent API failure), the engine pauses the task, records the reason, and waits. The task stays blocked until a human reviews the situation and decides what to do next. ```ts // Subscribe to approval requests as they happen delta.events.on("approval-requested", ({ taskId, action, approvalId, reason }) => { console.log(`Task ${taskId}: ${action} needs sign-off (${reason})`); // Forward to Slack, email, dashboard }); // List pending approvals for a task const { pendingApprovals } = await delta.inspect(taskId); // Approve and resume await delta.approve(approvalId); await delta.resume(taskId); // Reject with optional reason await delta.reject(approvalId, "Use the standard refund flow instead"); ``` ## Approval gates [#approval-gates] An action that declares `requiresApproval: true` blocks at the governance gateway until a human approves it. The task stays paused with a persistent `ApprovalRequest` that the engine re-checks on every resume attempt. ```ts const shipRefund = delta.action({ name: "issue-refund", description: "Issue a refund to a customer", requiresApproval: true, fn: async (input, ctx) => { /* ... */ }, }); ``` ### Trust-based waiver [#trust-based-waiver] Actions that need human oversight by default can waive it once the agent has earned enough trust. `requiresApproval: { untilTrust: 0.7 }` means the action requires approval only until the task's trust score reaches 0.7. After that it auto-approves with an auditable record. ```ts const action = delta.action({ name: "cancel-subscription", description: "Cancel a customer subscription", requiresApproval: { untilTrust: 0.8 }, fn: async (input, ctx) => { /* ... */ }, }); ``` A rejection is always final. Even with a trust waiver, a human-rejected action stays blocked. The waiver only governs the initial approval gate, not overrides. ### Approval lifecycle [#approval-lifecycle] 1. The engine creates an `ApprovalRequest` in `pending` status 2. The task transitions to `blocked` (persisted as `paused`) 3. The engine emits `approval-requested`. Subscribe via `delta.events.on()` 4. A human calls `delta.approve()` or `delta.reject()` 5. The engine emits `approval-resolved` with the decision 6. After approval, call `delta.resume(taskId)` to continue ## Escalations [#escalations] The engine escalates to a human when it hits a boundary it cannot resolve alone. Escalation is not an error. It is a designed recovery path. When escalation happens, the engine: 1. Saves an escalation record with the trigger and reason 2. Pauses the task so no further work runs 3. Emits `escalation-raised` for real-time awareness ### Escalation triggers [#escalation-triggers] * **Reasoner failure**: the model API fails after all retries are exhausted. The problem is likely transient, so the task pauses rather than fails * **Budget violation**: an action would exceed the remaining budget. The engine blocks before executing * **Risk threshold**: the task's assessed risk rises above the configured boundary * **Bayesian surprise**: observed outcomes deviate sharply from expected. This indicates novel or potentially unsafe conditions * **Supervision policy**: a workflow phase declares `escalate` as its supervision strategy for unrecoverable action failures ```ts // React to escalations in real time delta.events.on("escalation-raised", ({ taskId, trigger, reason }) => { console.log(`Task ${taskId} escalated (${trigger}): ${reason}`); notifyOnCallEngineer(taskId, reason); }); ``` ## Engine guidance [#engine-guidance] Before escalating, the engine warns the agent through advisory guidance lines injected into its reasoning context. On each turn, the engine checks governance signals (risk level, trust score, budget consumption, and Bayesian surprise) and computes warning-band text the model sees before it proposes its next action. Guidance works preventively: the agent sees "Risk rising above threshold" or "Budget at 80 percent" and can change course before the engine blocks the action. This reduces unnecessary escalations without reducing safety. Guidance is on by default. Set `guidance: false` in engine configuration to disable it entirely. ## Pause and resume [#pause-and-resume] The engine pauses a task automatically on escalations and approval blocks. You can also pause a running task manually: ```ts await delta.pause(taskId); // Later, resume from the latest checkpoint const result = await delta.resume(taskId); if (result.isOk) { console.log(`Resumed: ${result.value.status}`); } ``` Resume re-enters the governance loop from the last checkpoint. Completed work is never re-executed. If the task is still blocked on a pending approval, it blocks again at the same gate. ### Concurrent resume safety [#concurrent-resume-safety] Two callers cannot both resume the same task. The engine uses a compare-and-swap transition so exactly one resume wins. The other gets a clear error. This prevents duplicate execution when multiple reviewers respond to the same escalation. ## Rejection routing [#rejection-routing] When a human rejects an action, the engine feeds the rejection reason back to the model. The agent sees why its proposal was denied and can route around the blocked gate: choose a different approach, complete the task differently, or report what it could not do. ```ts await delta.reject(approvalId, "Use the standard refund flow. The customer is not eligible for manual override"); ``` The model receives this feedback on its next turn (free loop) or the workflow fails with the reason (deterministic path). A rejected action is never re-requested. The engine permanently blocks that specific approval. ## Supervision and HITL [#supervision-and-hitl] Workflow phases declare supervision policies that determine what happens when an action fails. Strategies range from fully automated to human-in-the-loop: * **Retry**: restart from the failed action (automated) * **Restart**: restart the phase from the beginning (automated) * **Resume**: continue from the latest checkpoint after human approval * **Escalate**: hand off to a human when automated recovery is inappropriate * **Abort subtree**: fail the current branch while keeping the workflow alive * **Abort entire tree**: fail the entire task and its descendants The `escalate` strategy is the designed HITL path for workflows: when automated recovery is exhausted or inappropriate, the engine pauses and waits for a human to assess the situation. ## Events reference [#events-reference] HITL events are always-on. They fire regardless of any diagnostics configuration because a silently dropped human-oversight notification is a safety failure. | Event | When | | -------------------- | -------------------------------------------------------------- | | `approval-requested` | An action requiring human approval needs sign-off | | `approval-resolved` | `delta.approve()` or `delta.reject()` resolves it | | `escalation-raised` | Reasoner failure, budget violation, or risk threshold exceeded | | `task-blocked` | Task is waiting on human action | ## End-to-end flow [#end-to-end-flow] ```txt Agent proposes sensitive action ↓ Engine checks requiresApproval gate ↓ No approval on record → creates ApprovalRequest ↓ Task pauses, emits "approval-requested" ↓ ───────────────────────────────────────── Human reviews (via dashboard, Slack, etc) ───────────────────────────────────────── ↓ Approve → delta.approve(id) | Reject → delta.reject(id, reason) ↓ ↓ Engine emits "approval-resolved" | ↓ ↓ delta.resume(taskId) Model sees rejection reason ↓ ↓ Agent continues Agent routes around block ``` # Skills (/docs/basics/skills) Skills keep knowledge separate from code. Actions and workflows stay general; skills carry the specific information an agent needs for a domain: product documentation, compliance rules, onboarding guides, API references. A skill is a markdown file on disk. The engine loads it on the turn that needs it and makes the content available alongside the agent's instructions. Changing what an agent knows means editing a markdown file, not redeploying code. ## Skill structure [#skill-structure] ```ts type Skill = { name: string; description: string; folder: string; }; ``` * **`name`**: unique identifier for the skill. Referenced in agent and tool definitions. * **`description`**: what the skill contains. Visible to the reasoner so the agent can decide when to use it. * **`folder`**: filesystem path to the skill directory. Must contain a `SKILL.md` file. ## Creating a skill [#creating-a-skill] Create a directory on disk with a `SKILL.md` file. The file content is what the agent reads. ```text skills/product-knowledge/SKILL.md ``` ```md # Product Knowledge The product catalog is organized into three categories: - Electronics: warranties are 2 years, returns accepted within 30 days. - Clothing: returns accepted within 60 days, items must be unworn. - Digital goods: no returns, replacement within 24 hours for defects. ## Refund policy by category | Category | Window | Condition | |---------------|--------|----------------------| | Electronics | 30 days| Original packaging | | Clothing | 60 days| Unworn with tags | | Digital goods | N/A | No returns | ``` Define the skill in code: ```ts const productKnowledge = { name: "product-knowledge", description: "Product catalog details and return policies by category", folder: "./skills/product-knowledge", }; ``` ## Assigning skills to agents [#assigning-skills-to-agents] Agents list their skills explicitly. Multiple agents can share the same skill. ```ts const supportAgent = delta.agent({ name: "support-agent", role: "Customer Support Specialist", rolePrompt: "Help customers resolve their issues.", actions: [lookupOrder, processRefund], skills: [productKnowledge, complianceRules], }); ``` ## Skills on tools [#skills-on-tools] Tools can declare skills when the tool needs domain knowledge that varies independently from any single agent. A tool that looks up orders might carry the order system's API guide as a skill, so the agent does not need to know the API details. Tool skills follow the same `SKILL.md` convention. ```ts const lookupOrder = { name: "lookup-order", description: "Look up an order by ID", schema: z.object({ orderId: z.string() }), skills: [orderSystemGuide], fn: async ({ data, ctx }) => { /* ... */ }, }; ``` ## Per-phase skills [#per-phase-skills] Workflow phases can override the agent-level skills with a phase-specific set. Each phase loads only the skills relevant to its work, keeping the agent's context focused on what matters for that stage. ```ts const phase = { name: "refund", actions: ["process-refund"], checkpoint: true, skills: [refundPolicy], }; ``` ## Skill availability in actions [#skill-availability-in-actions] Inside an action function, the context carries the resolved skills as `availableSkills`. Each entry contains the name, description, and loaded content from the `SKILL.md` file. ```ts const myAction: Action = { name: "check-policy", description: "Check applicable policy", schema: z.object({}), fn: async (data, ctx) => { const skill = ctx.availableSkills?.find(s => s.name === "refund-policy"); // skill.content contains the SKILL.md text return Ok({ checked: true }); }, }; ``` When no skills are active, `availableSkills` is absent. ## Skill lifecycle [#skill-lifecycle] Skills load lazily at first use. The engine reads the `SKILL.md` file on the turn that needs it, not at agent or engine startup. A skill with an invalid path or missing `SKILL.md` causes a runtime error at the point of use, not at declaration. # Tools (/docs/basics/tools) Tools are reusable, stateless utilities registered at the engine level. Unlike actions, tools have no prerequisites, no risk, and no state impact. They inform the model without changing business state. ## Tools vs actions [#tools-vs-actions] | Property | Tool | Action | | ------------- | ---------------------------------------- | ------------------------------------------- | | State impact | None. Tools are read-only utilities. | Changes task state. | | Prerequisites | None. | Declared prerequisites must be satisfied. | | Risk | Zero. | Declared risk 1-5, continuously estimated. | | Budget | Optional (cost tracking only). | Mandatory (task budget enforced). | | Visibility | Global. All tools visible to all agents. | Scoped by task state and prerequisites. | | Result type | Informs model reasoning. | Drives trust, risk, branching, supervision. | ## Declaring tools [#declaring-tools] Tools: both builtin and custom: are declared in one place in engine configuration. There is no `delta.tool()` authoring method. ```ts const delta = await createDeltaEngine({ apiKey: process.env.OPENAI_API_KEY, models: [{ name: "fast", model: "gpt-4o-mini", default: true }], tools: { builtin: { documentExtract: true, webSearch: { apiKey: process.env.EXA_API_KEY }, }, custom: [ { name: "lookup-inventory", description: "Check inventory levels for a product SKU", schema: z.object({ sku: z.string() }), fn: async ({ data, ctx }) => Ok(await db.inventory.check(data.sku)), }, ], }, }); ``` ### Builtin tools [#builtin-tools] Framework-provided tools the developer turns on through configuration rather than authoring by hand. * **`documentExtract`**: reads text from file or image attachments. Input is an `attachmentId` resolved against the task's own attachments. The tool never accepts a filesystem path or arbitrary URL: an agent cannot direct it to read a resource outside the task's declared attachments. * **`webSearch`**: queries Exa for live grounding. Requires an explicit API key (never read from the environment). Returns formatted results with titles, URLs, and query-relevant highlights. Builtin tools are opt-in. Declaring one registers it; leaving it undeclared loads none of its dependencies. When a declared builtin's optional peer dependencies are absent, engine construction fails immediately with an actionable install message. ### Custom tools [#custom-tools] Developer-authored tools for connecting agents to external systems. Every tool declares a name, description, Zod schema, and execution function. Tools may optionally declare skills, execution limits (cooldown, max calls per phase, max calls per task), and a cost budget. ```ts type Tool = { name: string; description: string; schema: ZodObject; skills?: (string | Skill)[]; fn: (ctx: { data: unknown; ctx: ToolContext }) => Promise>; limits?: { maxCallsPerPhase?: number; maxCallsPerTask?: number; cooldownMs?: number; }; cost?: Cost; budget?: Cost; }; type ToolContext = { agentName: string; taskId: string; goal?: string; phaseName?: string; toolHistory: ToolHistoryEntry[]; attachments?: Attachment[]; }; ``` ## Progressive disclosure [#progressive-disclosure] Tools use progressive disclosure to keep the model's context window efficient: * The model sees a menu of tool names and descriptions on every turn. * Schemas are not included by default. The model fetches them on demand using the internal tool `system:get_tool_schema`. * Tool execution history is also fetched on demand via `system:get_tool_history` and `system:get_tool_history_entry`. Actions use the opposite pattern: schemas are included by default because actions are task-specific and the model needs full schema information to execute correctly. ## Tool hints [#tool-hints] Phases and actions may declare advisory tool hints. These are suggestions to the model about which tools are useful in a given context. All tools remain visible regardless of hints. ```ts const phase = { name: "research", description: "Gather customer data", actions: ["lookup-customer"], checkpoint: true, tools: ["web-search", "database-query"], }; ``` ## Tool history [#tool-history] Every tool call is logged with full context for audit and provenance. History entries are persisted in the task state snapshot and recoverable through checkpoints. Entries are truncated by default (500 characters); the full output is retrievable on demand via `system:get_tool_history_entry`. ## Direct invocation [#direct-invocation] Tools can be invoked directly from developer code, not only by an agent mid-task. ```ts const text = await delta.tools.invoke({ tool: "document-extract", input: { attachmentId: "att_abc123" }, ctx: { attachments: [myAttachment] }, }); ``` Direct invocation validates input against the tool's schema and runs it. Unlike the agent path, it does not record tool history, charge budget, or apply loop detection: those belong to task-scoped governance. ## Loop detection [#loop-detection] The engine tracks per-agent tool usage: cooldown between consecutive calls, maximum calls per phase, and maximum calls per task. When a limit is hit, the engine blocks the call with a humanized message explaining why. The full context is logged for audit. The detector is created fresh per task run, so paused and resumed tasks get a clean detector. # Workflows (/docs/basics/workflows) A workflow is a structured execution graph composed of phases. Workflows run deterministically: the same workflow produces the same execution order regardless of the model behind it. ## Workflow structure [#workflow-structure] ```ts type Workflow = { name: string; description: string; storyline?: string; version: string; phases: Phase[]; estimatedCost?: Cost; hooks?: Hooks; }; ``` ## Phases [#phases] A phase is a stage within a workflow containing ordered actions or conditional branches. ```ts type Phase = { name: string; description: string; storyline?: string; actions: ActionRef[]; checkpoint: boolean; supervision?: SupervisionPolicy; hooks?: Hooks; tools?: string[]; }; ``` ### Sequential execution [#sequential-execution] Actions in a phase run in declared order by default. The next action begins only after the current action produces a terminal outcome. ```ts const fulfillment = delta.workflow({ name: "fulfillment", phases: [ { name: "settlement", checkpoint: true, actions: [ "confirm-order", "process-order", "notify-customer", ], }, ], }); ``` ### Conditional branching [#conditional-branching] A phase may declare branching based on action outcomes. Branching forms a decision tree where each node routes to the next action based on the result. ```ts const fulfillment = delta.workflow({ name: "fulfillment", phases: [ { name: "settlement", checkpoint: true, actions: [ "confirm-order", { action: "process-order", onSuccess: "notify-customer", onFailure: "escalate-to-human", }, ], }, ], }); ``` Branching is declared, not inferred. The engine never invents transitions. Workflow branching is determined solely by declared transitions and observed action outcomes. ### Guard conditions [#guard-conditions] An optional guard evaluated against task state determines the next action. The guard receives the action context plus the previous action's outcome in the phase. ```ts { action: "process-order", onSuccess: "fulfill-order", onFailure: "notify-failure", when: (ctx) => { // Route based on the previous action's result if (ctx.lastOutcome?.ok === false) return false; // Or based on task state return ctx.budget?.spent.tokens < ctx.budget?.limit?.tokens; }, } ``` The `ctx.lastOutcome` field carries the previous action's name, whether it succeeded (`ok: boolean`), and the error message if it failed. It is undefined before the first action in the phase. ## Checkpoints [#checkpoints] Checkpoints create recovery boundaries. When a phase declares `checkpoint: true`, the engine persists the full task state after every action in that phase completes. On failure, the engine can: * **Retry**: restart from the failed action with the same input (up to `maxRetries` attempts). * **Restart**: restart the phase from the beginning. * **Resume**: continue from the latest checkpoint after human intervention. * **Escalate**: hand off to a human when automated recovery is inappropriate. * **Abort subtree**: fail the current branch but allow other branches to continue. * **Abort entire tree**: fail the entire task. ## Supervision policy [#supervision-policy] Each phase can declare how failures are handled. ```ts type SupervisionPolicy = { strategy: "retry" | "restart" | "resume" | "escalate" | "abort-subtree" | "abort-tree"; maxRetries: number; }; ``` The default strategy is `retry` with 3 attempts. The configured strategy applies consistently for the lifetime of the task. ## Running a workflow [#running-a-workflow] Pass the workflow name to `delta.send()` to execute deterministically through the workflow engine rather than the free reasoner loop. ```ts const result = await delta.send({ goal: "Process order ORD-42", agentName: "support-agent", workflow: "fulfillment", input: { orderId: "ORD-42" }, budget: { tokens: 5000, durationMs: 60_000 }, }); ``` When a workflow is assigned, the task runs without the reasoner: phases execute in declared order. Workflow-less tasks use the reasoner loop, where the model plans and proposes actions. ## Storylines [#storylines] Workflows and phases accept an optional `storyline` field: free-prose narrative of the ideal user flow. Storylines are injected into `ActionContext` so action functions and hooks can adapt their behavior (tone, pacing, what to emphasize). A `description` says what a workflow does. A `storyline` says how it should feel to the user. The distinction lets a developer declare the experiential arc without coupling it to the functional contract. The engine enforces the functional contract regardless; the storyline guides behavior within that enforcement. # Core Concepts (/docs/getting-started/core-concepts) ## The engine owns enforcement [#the-engine-owns-enforcement] The agent may propose actions. Only the engine may authorize them. Safety, policy, budget, risk, and workflow validation are enforced by the engine rather than learned by the model. Models are probabilistic reasoners. Governance requires deterministic guarantees. Separating reasoning from enforcement means the model can focus on what it does best: understanding goals and planning: while the engine handles the constraints that require mathematical certainty. ## Actions [#actions] An action is the smallest executable unit. It has a name, a description, a Zod schema for input validation, and a function that returns an explicit `Ok` or `Err` Result. Actions may declare: * **Risk**: an integer from 1 (safe) to 5 (dangerous or irreversible). Seeds the engine's risk estimator; high-risk actions can require human approval. * **Prerequisites**: actions or workflows that must complete before this action becomes legal. The engine blocks execution until prerequisites are satisfied, exposing only valid actions to the model. * **Estimated cost**: anticipated token, time, or financial consumption. Seeds the cost estimator so the engine starts from a calibrated expectation. * **Hooks**: `before`, `after`, and `onError` callbacks that observe and prepare without authorizing actions or bypassing governance. ## Agents [#agents] An agent is a named role with a set of allowed actions, optional workflows, skills, channels, and data sources. The agent defines what the model can do. The engine defines what the model will be allowed to do at any given state. An agent carries: * **Role and role prompt**: the agent's identity and behavioral instructions. * **Actions**: the operations the agent is permitted to perform. * **Workflows**: structured multi-phase procedures the agent follows. * **Skills**: [knowledge files](/docs/basics/skills) the agent loads per turn alongside its instructions. * **Channels**: messaging platforms the agent communicates through. * **Data sources**: [governed CRUD stores](/docs/basics/data-sources) the agent reads from and writes to. * **Team**: a group name for multi-agent coordination. Agents on the same team share a roster and mailbox awareness. ## Workflows [#workflows] A workflow is a structured execution graph composed of phases. Each phase contains ordered actions or conditional branches. Workflows run deterministically: the same workflow produces the same execution order regardless of the model behind it. Phases support: * **Sequential execution**: actions in declared order. * **Conditional branching**: route to the next action based on an action's `Ok` or `Err` outcome, or a guard condition evaluated against task state. * **Checkpointing**: configurable recovery boundaries. On failure, the engine retries, restarts, resumes, or escalates from the latest checkpoint. * **Supervision**: retry, restart, resume, escalate, or abort strategies configured per phase. ## Governance layer [#governance-layer] Every action passes through the governance gateway before execution. The gateway performs these checks in sequence: 1. **Schema validation**: input must match the action's Zod schema. 2. **Prerequisite check**: declared prerequisites must be satisfied. 3. **Risk assessment**: current risk must not exceed the task's risk threshold. If it does, the action requires human approval. 4. **Budget check**: remaining budget must cover the action's estimated or observed cost. 5. **Approval gate**: if `requiresApproval` is set, or risk exceeds threshold, the action blocks until a human approves or rejects. If any check fails, the action is blocked with a deterministic outcome: the engine never silently drops or bypasses a governance check. ## Tasks [#tasks] A task is the unit of execution and the security boundary. Every action, memory access, message, and escalation belongs to exactly one TaskID. TaskIDs are cryptographically random and unguessable: authorization, budgeting, checkpointing, delegation, and supervision all attach to them. The engine never creates a new task for an agent that already has active or queued work. New goals attach to the existing task or wait. ## Delegation [#delegation] Agents delegate to other agents through a bounded supervision tree. Each parent can have at most two active subtasks; additional work enters a FIFO queue. Subtasks inherit scoped budgets and permissions from their parent: they never gain authority beyond the parent's scope. ## Tools [#tools] Tools are reusable, stateless utilities registered at the engine level. Unlike actions, tools have no prerequisites, no risk, and no state impact. They inform the model without changing business state. Tools use progressive disclosure: the model sees names and descriptions on every turn, but fetches schemas and execution history on demand. This keeps the context window efficient. ## Human oversight [#human-oversight] The system never assumes perfect autonomy. Escalation can occur from risk thresholds, Bayesian surprise, policy violations, budget violations, workflow failures, or explicit configuration. Every escalation is auditable and the engine emits typed events for approvals, escalations, and task lifecycle changes so external systems react in real time. # Installation (/docs/getting-started/installation) ## Requirements [#requirements] * Node.js 18 or later * TypeScript 5 or later * pnpm, npm, or yarn ## Install the package [#install-the-package] ``` pnpm add delta-agents ``` Or with npm: ``` npm install delta-agents ``` ## Optional peer dependencies [#optional-peer-dependencies] Delta ships with builtin tools that require additional dependencies. These are optional: the engine works without them and loads nothing until the tool is enabled. ### Document extraction [#document-extraction] The builtin `document-extract` tool reads text from PDFs, images, and Office files. It requires two optional peer dependencies: ``` pnpm add @llamaindex/liteparse sharp ``` Enable the tool in engine configuration and the engine validates the dependencies at construction time. If they are missing, construction fails with an actionable install message rather than a mid-task error. ### Web search [#web-search] The builtin `web-search` tool queries Exa for live grounding. It requires one optional peer dependency: ``` pnpm add exa-js ``` Enable the tool with an explicit API key. The key is required and declared at configuration time: it is never read from the environment. ## Storage adapters [#storage-adapters] Delta uses an in-memory store by default: no database setup needed for development. For persistence, install the optional Drizzle storage adapter. The Drizzle adapter requires additional dependencies. See the [adapters](/docs/reference/adapters) page for setup instructions. ## Verify installation [#verify-installation] ```ts import { createDeltaEngine } from "delta-agents"; const delta = await createDeltaEngine({ apiKey: process.env.OPENAI_API_KEY, models: [{ name: "fast", model: "gpt-4o-mini", default: true }], }); console.log("Engine ready"); ``` Run with `tsx` or `bun` for TypeScript, or compile first and run with Node. # Quick Start (/docs/getting-started/quick-start) This guide walks through defining an agent, giving it actions, deploying it, and sending a task. The full example runs in under 30 lines. ## Create the engine [#create-the-engine] The engine is the single entry point. It carries model configuration, persistence, and all governance machinery. ```ts import { createDeltaEngine, Ok } from "delta-agents"; import { z } from "zod"; const delta = await createDeltaEngine({ apiKey: process.env.OPENAI_API_KEY, models: [{ name: "fast", model: "gpt-4o-mini", default: true }], }); ``` ## Define an action [#define-an-action] An action is a named operation with a schema and an execution function. The engine validates every call against the schema before the function runs. ```ts const lookupCustomer = delta.action({ name: "lookup-customer", description: "Look up a customer account by ID", risk: 1, schema: z.object({ customerId: z.string() }), fn: async ({ customerId }) => Ok(await db.customer.find(customerId)), }); ``` Actions return explicit `Ok` or `Err` results. The engine never infers success from the absence of a thrown error. ## Define an agent [#define-an-agent] An agent is a role with a set of allowed actions, a description of its responsibilities, and behavioral instructions. ```ts const supportAgent = delta.agent({ name: "support-agent", description: "Handles customer support requests", role: "Customer Support Specialist", rolePrompt: "Help customers resolve their issues.", actions: [lookupCustomer], }); ``` An agent can also declare workflows, skills, channels, and a team for multi-agent coordination. ## Deploy and send [#deploy-and-send] Deploying makes the agent active. Sending a goal creates a task, runs the reasoner loop through governance checks, and returns the result. ```ts delta.deploy(supportAgent); const result = await delta.send({ goal: "Look up customer C-42", agentName: "support-agent", input: { customerId: "C-42" }, budget: { tokens: 5000, durationMs: 30_000 }, }); if (result.isOk) { console.log(result.value.status); // "completed" | "blocked" | "failed" | "queued" } ``` The `send` call returns a `Result`: use `result.isOk` / `result.isErr` to check, and `match(result, { Ok: ..., Err: ... })` for exhaustive handling. ## What the engine did [#what-the-engine-did] Behind the scenes, the engine: * Created a cryptographically random TaskID as the security boundary for governance. * Assigned the task to `support-agent`. * Resolved the model (gpt-4o-mini) and called the reasoner with the goal. * Received the agent's action proposal. * Validated the input schema against the action definition. * Checked budget (5,000 tokens, 30 seconds): the agent cannot exceed either. * Executed the action function. * Updated trust and risk estimates from the outcome. * Checkpointed the final state. * Returned the result. The model never has access to operations it was not assigned. It cannot call `lookup-customer` with missing or invalid parameters. It cannot exceed the token budget. All enforced structurally, not by prompt engineering. ## Reacting to human-in-the-loop events [#reacting-to-human-in-the-loop-events] Subscribe to approvals, escalations, and task outcomes in real time. Events fire at the moment the engine creates them, so notifications, dashboards, and webhooks react immediately without polling. Every event carries a `timestamp` and `agentName` in the envelope. ```ts const unsub = delta.events.on("approval-requested", (data) => { console.log(`${data.agentName} at ${new Date(data.timestamp)}: Task ${data.taskId} needs sign-off for ${data.action}`); // Forward to Slack, update dashboard, trigger webhook }); const unsub2 = delta.events.on("task-completed", (data) => { console.log(`${data.agentName} completed at ${new Date(data.timestamp)}: ${data.goal}`); }); ``` ## Next steps [#next-steps] * [Core concepts](/docs/getting-started/core-concepts): the mental model behind agents, actions, workflows, and governance. * [Human oversight](/docs/basics/human-oversight): approvals, escalations, pause and resume. * [Agents](/docs/basics/agents): defining agents with roles, skills, and teams. * [Skills](/docs/basics/skills): knowledge resources agents retrieve at runtime. * [Data sources](/docs/basics/data-sources): governed CRUD operations on named data stores. * [Actions](/docs/basics/actions): actions, prerequisites, hooks, and risk scoring. * [Workflows](/docs/basics/workflows): multi-phase deterministic SOPs. # Introduction (/docs) Delta Agents is a framework and runtime for building AI agents that cannot violate policies, exceed budgets, or execute unsafe actions. The model proposes; the engine validates, authorizes, and audits. Every operation runs through a deterministic governance layer that enforces constraints structurally rather than relying on prompt engineering. The model is responsible for reasoning. The engine is responsible for governance. ## What the engine prevents [#what-the-engine-prevents] Left unchecked, language models drift: they attempt actions that are invalid in the current state, exceed token or time budgets, skip prerequisite steps, or operate outside approved risk boundaries. The governance firewall catches all of this before execution. * **Prerequisite validation.** An agent cannot process an order before confirming it. The engine refuses actions with unsatisfied prerequisites structurally: the action does not exist in the current state-space. * **Budget enforcement.** Token, time, memory, latency, and financial budgets are enforced as ceilings, not suggestions. Agents cannot exceed what the task allows. * **Risk gating.** High-risk actions block behind human approval. The engine continuously estimates risk from observed behavior and escalates when unexpected patterns emerge. * **Loop detection.** Reasoning spirals and retry storms are caught before they burn budget. The engine terminates execution when consumption outpaces progress. * **Schema validation.** Every action input is validated against its declared schema. Malformed or unexpected parameters are rejected before the action function runs. * **Action legality.** Only actions valid in the current state-space are exposed. Invalid actions do not exist from the model's perspective: they cannot be requested, let alone executed. ## What the engine provides [#what-the-engine-provides] Beyond enforcement, the framework lifts the common plumbing that production agents need. * **Multi-agent orchestration.** Agents delegate to other agents with scoped budgets and bounded supervision trees. Teams coordinate through mailboxes with read receipts and a live roster that tracks per-agent load. * **Deterministic workflows.** Multi-phase SOPs run the same way every time, regardless of which model executes them. Conditional branching, declared prerequisites, and configurable supervision strategies keep execution predictable. * **Memory and context.** Agents retrieve context from past tasks on demand, take notes on completed work, and improve on the same tasks next time. Time awareness grounds agents in recency, urgency, and sequence. * **Messaging channels.** Agents communicate through Slack, Teams, Discord, or Telegram. Execution is decoupled from delivery: agents work independently of channel availability. * **Builtin and custom tools.** Web search and document extraction are available as opt-in builtin tools. Custom tools connect agents to external systems without changing the governance model. * **Full observability.** Every action, decision, and token is traceable and queryable. Trust and risk are derived from observed behavior, continuously revised from what actually happened. Typed events fire for approvals, escalations, and task lifecycle changes — dashboards and webhooks react in real time without polling. * **Commit history.** Agents record completed work with optional notes. The commit history tracks what was done, by whom, and when, enabling audit and learning from past runs. ## How it works [#how-it-works] An agent is defined with its allowed actions, workflows, and a role. The engine enforces governance at every step: 1. A goal arrives via `delta.send()`: the engine creates a task with a budget, risk estimate, and identity. 2. The agent reasons about the goal and proposes an action. 3. The engine validates the action against the current state-space: schema, prerequisites, budget, risk, and approval gates. 4. The action executes. The engine checkpoints state, updates trust and risk estimates, and continues. 5. On completion, the full execution trail is persisted: every action, decision, and token is attributable to the task. ## What delta-agents is not [#what-delta-agents-is-not] * Not a no-code agent builder. Delta is a TypeScript framework designed for developers building production systems. * Not a model provider. Bring your own API key: OpenAI, OpenRouter, or any OpenAI-compatible endpoint. * Not a hosted service. Delta runs in your own Node.js process as an imported library. * Not a replacement for human judgment. Human oversight is a first-class component: the engine escalates when uncertainty exceeds configured thresholds. # Adapters (/docs/reference/adapters) Delta is designed around port adapters for storage, model access, and messaging. Defaults let you work immediately; adapters let you connect real infrastructure. ## Storage adapters [#storage-adapters] ### In-memory store [#in-memory-store] Default. All data lives in memory for the lifetime of the engine. No setup required. ```ts import { createInMemoryStore } from "delta-agents"; const delta = await createDeltaEngine({ store: createInMemoryStore(), }); ``` Best for development and testing. Data is lost when the process exits. ### Drizzle SQLite store [#drizzle-sqlite-store] Persistent storage using Drizzle ORM with libsql (SQLite). Suitable for single-process deployments. ```ts import { createDrizzleStore } from "delta-agents"; import { createClient } from "@libsql/client"; const client = createClient({ url: "file:./delta.db" }); const delta = await createDeltaEngine({ store: createDrizzleStore({ client }), }); ``` The Drizzle adapter requires the Drizzle ORM packages and libsql client as dependencies. The store methods are optional: missing methods skip gracefully with a warning, so the adapter works even when a table has not been migrated yet. Both adapters support the full set of optional cleanup methods (`deleteTask`, `deleteMessages`, `getTasksOlderThan`, `getTaskIds`), enabling the `delta.cleanup()` lifecycle. ## Reasoner adapters [#reasoner-adapters] ### OpenAI reasoner [#openai-reasoner] Default when `models` are configured. Uses the OpenAI API or any OpenAI-compatible endpoint. The engine resolves the model from the agent's `model` field, looks up the `ModelDef` configuration, and calls the provider with the resolved endpoint, API key, and options. If the model's `vision` or `audio` flags are set, the reasoner embeds image or audio attachments as content parts. ### Mock reasoner [#mock-reasoner] For testing without a live model API. ```ts import { createMockReasoner } from "delta-agents"; const reasoner = createMockReasoner({ respondWith: { kind: "action", name: "lookup-customer", input: { customerId: "C-42" }, }, }); ``` The mock reasoner always returns the configured response, making tests deterministic and fast. ### Custom reasoner [#custom-reasoner] Implement the `ReasonerPort` interface to use any model provider. ```ts type ReasonerPort = { reason: (input: ReasonerInput) => Promise>; }; ``` ## Channel adapters [#channel-adapters] ### Chat SDK channel [#chat-sdk-channel] Bridges agents to messaging platforms using the Chat SDK ecosystem. ```ts import { createChatSdkChannel } from "delta-agents"; const slackChannel = createChatSdkChannel({ type: "slack", adapter: slackAdapter, }); const supportAgent = delta.agent({ name: "support-agent", channels: [slackChannel], }); ``` The Chat SDK adapter works with Slack, Teams, Discord, and Telegram through a single interface. Cross-platform conversation continuity is maintained: agents track context regardless of which channel the user switches to. # API Reference (/docs/reference/api-reference) ## createDeltaEngine [#createdeltaengine] The single factory that assembles the engine. Returns a `Promise`. ```ts import { createDeltaEngine } from "delta-agents"; const delta = await createDeltaEngine(config: DeltaEngineConfig); ``` ## DeltaEngine [#deltaengine] The unified facade for authoring and runtime operations. ### Authoring methods [#authoring-methods] #### `delta.action(def)` [#deltaactiondef] Define an executable operation. Validated and registered immediately. ```ts const action = delta.action(def: Action): Action ``` #### `delta.workflow(def)` [#deltaworkflowdef] Define an ordered procedure composed of phases. ```ts const workflow = delta.workflow(def: Workflow): Workflow ``` #### `delta.agent(def)` [#deltaagentdef] Define a role with its allowed actions, workflows, and data sources. ```ts const agent = delta.agent(def: Agent): Agent ``` #### `delta.dataSource(def)` [#deltadatasourcedef] Define a named, owned store of governed CRUD operations. See [Data sources](/docs/basics/data-sources) for usage. ```ts const ds = delta.dataSource(def: DataSource): DataSource ``` ### Runtime methods [#runtime-methods] #### `delta.deploy(agent)` [#deltadeployagent] Mark a defined agent as active. The agent must already be registered via `delta.agent()`. ```ts delta.deploy(agent: Agent): void ``` #### `delta.send(input)` [#deltasendinput] Hand a goal to a named agent and drive execution to completion or until blocked. Creates a new TaskID, runs the reasoner loop through governance checks, and returns the terminal result. ```ts const result = await delta.send(input: SendInput): Promise> ``` `SendResult.status` is one of: `"completed" | "failed" | "blocked" | "queued" | "pendingCommit"`. When the agent is already busy, returns `"queued"`: no new task is created, and the goal is queued as a message on the existing task. #### `delta.approve(approvalId)` [#deltaapproveapprovalid] Approve a pending human approval request. After approving, call `delta.resume(taskId)` to continue a blocked task. ```ts const approval = await delta.approve(approvalId: string): Promise> ``` #### `delta.reject(approvalId)` [#deltarejectapprovalid] Reject a pending human approval request. The action stays permanently blocked: the engine never re-opens a rejected approval. ```ts const approval = await delta.reject(approvalId: string): Promise> ``` #### `delta.pause(taskId)` [#deltapausetaskid] Suspend a running task and save its current state as a checkpoint. ```ts await delta.pause(taskId: string): Promise> ``` #### `delta.resume(taskId)` [#deltaresumetaskid] Resume a paused or blocked task from its latest checkpoint. ```ts const result = await delta.resume(taskId: string): Promise> ``` #### `delta.inspect(taskId)` [#deltainspecttaskid] Read the full governance state for a task. ```ts const state = await delta.inspect(taskId: string): Promise> ``` Returns task record, execution history, latest checkpoint, escalations, and pending approvals. #### `delta.lastTask(agentName)` [#deltalasttaskagentname] Return the most recent task for a named agent. No need to store TaskIDs. ```ts const task = await delta.lastTask(agentName: string): Promise> ``` #### `delta.roster(query?)` [#deltarosterquery] Team-awareness read-model: who is doing what and how loaded each agent is. ```ts const roster = await delta.roster(query?: { team?: string }): Promise> ``` #### `delta.inbox(args)` [#deltainboxargs] An agent's inbox: messages addressed to it, unread first then oldest-first. ```ts const messages = await delta.inbox(args: { agent: string }): Promise> ``` #### `delta.outbox(args)` [#deltaoutboxargs] An agent's outbox: messages it sent, newest first. ```ts const messages = await delta.outbox(args: { agent: string }): Promise> ``` #### `delta.unsend(args)` [#deltaunsendargs] Unsend a message, allowed only while unread. ```ts const message = await delta.unsend(args: { messageId: string }): Promise> ``` #### `delta.cleanup(options?)` [#deltacleanupoptions] Manually prune old tasks and messages past retention windows. ```ts await delta.cleanup(options?: CleanupOptions): Promise> ``` ### Events [#events] #### `delta.events` [#deltaevents] Subscribe to engine events with typed payloads. Each event name maps to a specific payload shape so subscribers receive correctly typed data without casting. Every event carries a `timestamp` (ms since epoch) and `agentName` in the envelope, so consumers no longer need to join against `delta.inspect()` for time and agent context. ```ts // Subscribe. Returns an unsubscribe function const unsub = delta.events.on("approval-requested", (data) => { // data is typed as { taskId: string; action: string; approvalId: string; reason: string; timestamp: number; agentName: string } console.log(`${data.agentName} at ${new Date(data.timestamp)}: ${data.action} needs approval`); }); // Unsubscribe when no longer needed unsub(); // Remove a specific handler delta.events.off("approval-requested", handler); ``` Available event types: `approval-requested`, `approval-resolved`, `escalation-raised`, `task-completed`, `task-blocked`, `task-failed`, `step-start`, `step-end`, `action-start`, `action-end`, `commit-step-attempt`, `commit-step-done`, `commit-step-auto-commit`. #### `delta.tools.invoke(args)` [#deltatoolsinvokeargs] Direct developer-facing invocation for any registered tool (builtin or custom). Does not record tool history or apply loop or budget governance. ```ts const result = await delta.tools.invoke(args: InvokeArgs): Promise> ``` ## Adapter factories [#adapter-factories] #### `createInMemoryStore()` [#createinmemorystore] Create an in-memory storage adapter. Default when no store is configured. ```ts import { createInMemoryStore } from "delta-agents"; const store = createInMemoryStore(); ``` #### `createDrizzleStore(config)` [#createdrizzlestoreconfig] Create a persistent SQLite storage adapter using Drizzle ORM. ```ts import { createDrizzleStore } from "delta-agents"; const store = createDrizzleStore({ client, schema }); ``` #### `createMockReasoner(options?)` [#createmockreasoneroptions] Create a mock reasoner for testing. ```ts import { createMockReasoner } from "delta-agents"; const reasoner = createMockReasoner({ respondWith: ... }); ``` #### `createChatSdkChannel(config)` [#createchatsdkchannelconfig] Create a channel adapter using the Chat SDK for multi-platform messaging. ```ts import { createChatSdkChannel } from "delta-agents"; const channel = createChatSdkChannel({ type: "slack", adapter: slackAdapter }); ``` ## Attachment loaders [#attachment-loaders] #### `loadAttachmentFromFile(path)` [#loadattachmentfromfilepath] Read a local file and return an `AttachmentInput` with base64-encoded data. ```ts const attachment = await loadAttachmentFromFile(path: string): Promise> ``` #### `loadAttachmentFromUrl(url)` [#loadattachmentfromurlurl] Fetch a remote URL and return an `AttachmentInput` with base64-encoded data. ```ts const attachment = await loadAttachmentFromUrl(url: string): Promise> ``` ## Result utilities [#result-utilities] Re-exported from `slang-ts`: ```ts import { Ok, Err, option, match, safeTry, pipe } from "delta-agents"; ``` # Configuration (/docs/reference/configuration) ## Engine configuration [#engine-configuration] Passed to `createDeltaEngine()`. | Option | Type | Default | Description | | --------------------------- | ----------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | | `store` | `StoragePort` | In-memory store | Persistence adapter for tasks, checkpoints, and messages. | | `endpoint` | `string` | OpenAI endpoint | Default base URL for model API calls. | | `apiKey` | `string` | None | Default API key for model calls. | | `options` | `ModelOptions` | None | Default provider options (temperature, topP, maxTokens). | | `models` | `ModelDef[]` | None | Named model configurations. At least one must have `default: true`. | | `maxStepsPerTask` | `number` | 100 | Maximum reasoner iterations per task. | | `providerRetry` | `Partial` | 3 attempts, 200ms base, 5s cap | Retry configuration for model API calls. | | `systemPrompt` | `string` | None | Global org instructions for all agents, baked into the cacheable system prompt prefix. | | `timezone` | `string` | System timezone | Timezone for humanized time in reasoner messages. | | `logger` | `LoggerConfig` | Dev mode, info level | Per-engine logger configuration. | | `cache` | `CacheConfig` | 1000 entries, 5min TTL | Read-through cache tuning. | | `diagnostics` | `DiagnosticsConfig` | All disabled | Per-module diagnostic event toggles. | | `commitContextLimit` | `number` | 10 | Recent commits to inject into agent context per turn. | | `commitMaxRetries` | `number` | 3 | Max reasoner attempts in commit step before auto-commit. | | `tools` | `ToolsConfig` | None | Builtin and custom tool declarations. | | `mailbox` | `{ inboxCap?: number }` | Unbounded | Per-agent inbox capacity cap. | | `guidance` | `boolean` | `true` | Warning-band advisory lines (risk, trust, budget, surprise) that reach the model so it can self-correct before escalation thresholds fire. | | `maxInvalidDecisionRetries` | `number` | 3 | Max consecutive invalid model decisions before the task fails. 0 fails immediately. | ## ModelDef [#modeldef] | Field | Type | Required | Description | | ---------- | -------------- | -------- | ------------------------------------------------------------ | | `name` | `string` | Yes | Identifier agents reference via `agent.model`. | | `model` | `string` | Yes | Model ID sent to the provider (e.g. "gpt-4o"). | | `default` | `boolean` | No | Exactly one must be `true`. Agents without a model use this. | | `endpoint` | `string` | No | Per-model base URL override. | | `apiKey` | `string` | No | Per-model API key override. | | `options` | `ModelOptions` | No | Per-model provider options merged over engine-level options. | | `vision` | `boolean` | No | Declares vision capability. | | `audio` | `boolean` | No | Declares audio capability. | ## ToolsConfig [#toolsconfig] | Field | Type | Description | | --------- | -------------------- | ----------------------------------- | | `builtin` | `BuiltinToolsConfig` | Framework-provided tools to enable. | | `custom` | `Tool[]` | Developer-authored tools. | ### BuiltinToolsConfig [#builtintoolsconfig] | Field | Type | Description | | ----------------- | ----------------------------------- | ---------------------------------------------------- | | `documentExtract` | `boolean \| DocumentExtractOptions` | Enable document extraction tool. | | `webSearch` | `WebSearchOptions` | Enable web search tool (requires explicit `apiKey`). | ## Provider retry options [#provider-retry-options] | Field | Type | Default | Description | | ------------- | -------- | ------- | ------------------------------------------------------------- | | `maxAttempts` | `number` | 3 | Maximum retry attempts per reasoner call. | | `baseDelayMs` | `number` | 200 | Base delay before first retry (jittered exponential backoff). | | `maxDelayMs` | `number` | 5000 | Maximum delay cap. | ## Send input defaults [#send-input-defaults] | Field | Default | | ------------------- | ------- | | `budget.tokens` | 10,000 | | `budget.durationMs` | 300,000 | ## Cache configuration [#cache-configuration] | Field | Type | Default | Description | | ------------ | -------- | ------- | ------------------------------------------ | | `maxEntries` | `number` | 1000 | Maximum cache entries before LRU eviction. | | `ttlMs` | `number` | 300,000 | Sliding TTL in milliseconds (5 minutes). | # FAQ (/docs/reference/faq) ## General [#general] ### What is delta-agents? [#what-is-delta-agents] Delta Agents is a framework and runtime for building production AI agents. It provides a deterministic governance layer that validates, authorizes, and audits every agent operation. The model proposes actions; the engine enforces constraints. ### How is this different from LangChain, CrewAI, or similar? [#how-is-this-different-from-langchain-crewai-or-similar] Delta treats governance as the primary concern, not an add-on. Actions run through a deterministic gateway that checks schema, prerequisites, budget, risk, and approval before execution: enforced structurally, not by prompt engineering. Delegation is bounded to prevent complexity explosion. Trust and risk are derived from observed behavior using control theory and Bayesian updating. ### Can I use my own model provider? [#can-i-use-my-own-model-provider] Yes. Delta works with any OpenAI-compatible endpoint: OpenAI, OpenRouter, Azure OpenAI, local Ollama instances, or any custom endpoint. Each model is configured as a named `ModelDef` and agents select by name. Per-agent model assignment allows mixing fast and reasoning models in the same engine. ### Do I need a database? [#do-i-need-a-database] No. Delta uses an in-memory store by default: no database setup for development. For production persistence, use the Drizzle SQLite adapter. ## Actions and workflows [#actions-and-workflows] ### Can an action run without the model? [#can-an-action-run-without-the-model] Yes. When a workflow is assigned via `send({ workflow: "..." })`, the task runs deterministically through phases without the reasoner. The engine executes actions in declared order with configured branching. Workflow-less tasks use the reasoner loop where the model plans and proposes. ### How are failures handled? [#how-are-failures-handled] Supervision strategies: retry, restart, resume, escalate, or abort: are configured per phase. Retries use jittered exponential backoff. When automated recovery is exhausted, the task escalates to a human. ### What happens when an agent is busy? [#what-happens-when-an-agent-is-busy] The engine never creates a new task for an agent that already has active or queued work. `delta.send()` returns `status: "queued"` and the goal attaches as a message on the existing task. When the agent is free, the message is folded into context. ## Budget and cost [#budget-and-cost] ### What happens when budget is exceeded? [#what-happens-when-budget-is-exceeded] The engine blocks the action and transitions the task to `failed` with budget exhaustion as the reason. The check happens before execution, not after: the agent cannot spend what it does not have. ### Does delta track financial costs? [#does-delta-track-financial-costs] Yes. The `Cost` type includes a `money` field with explicit value and currency (ISO 4217). Financial budget enforcement follows the same pattern as tokens and time. ## Tools [#tools] ### Can I add my own tools? [#can-i-add-my-own-tools] Yes. Custom tools are declared in the `tools.custom` array in engine configuration. Each tool declares a name, description, Zod schema, and execution function. Tools are global and visible to all agents. ### When should I use a tool vs an action? [#when-should-i-use-a-tool-vs-an-action] Use a tool for stateless operations that inform the model (web search, calculation, document extraction). Use an action for business operations that change state (process an order, update a record, send a notification). Tools have no prerequisites, no risk, and no state impact. ## Multi-agent [#multi-agent] ### How many agents can I have? [#how-many-agents-can-i-have] There is no hard limit on agent count. Each agent can have at most one active task at a time with up to two active subtasks. Additional work queues. The roster tracks load across all agents. ### Can agents communicate with each other? [#can-agents-communicate-with-each-other] Yes. Agents communicate through mailboxes (inbox and outbox) with read receipts and unsend support. Communication is turn-only: messages never interrupt an agent mid-task. ## Observability [#observability] ### Can I see what an agent is doing? [#can-i-see-what-an-agent-is-doing] Yes. `delta.inspect(taskId)` returns the full governance state: task record, execution history, latest checkpoint, escalation events, and pending approvals. Every action, decision, and token is traced. ### How do I debug agent behavior? [#how-do-i-debug-agent-behavior] Enable per-module diagnostics in engine configuration. Diagnostics emit structured events (timing, decision traces, counts) to the logger at debug or trace level. Each module (actions, workflows, governance, supervision, memory, comms, tools, engine) can be independently toggled. # Types (/docs/reference/types) ## Authoring types [#authoring-types] ### Action [#action] ```ts type Action = { name: string; description: string; schema: ZodObject; risk?: 1 | 2 | 3 | 4 | 5; estimatedCost?: Cost; requiresApproval?: boolean; prerequisites?: { actions?: string[]; workflows?: string[]; }; hooks?: Hooks; tools?: string[]; fn: (data: ActionInput, ctx: ActionContext) => Promise; }; ``` ### ActionContext [#actioncontext] ```ts type ActionContext = { taskId: string; executionId: string; agentName: string; phase?: string; storyline?: string; phaseStoryline?: string; goal?: string; workflowName?: string; availableSkills?: Array<{ name: string; description: string; content?: string }>; attachments?: Attachment[]; communicate?: (channelType: string, body: string) => Promise; remember?: (content: string, kind?: string) => Promise; recall?: (query: string) => Promise; budget?: { spent: Cost; limit?: Cost }; }; ``` ### Hooks [#hooks] ```ts type Hooks = { before?: (ctx: ActionContext) => Promise; after?: (ctx: ActionContext & { result: unknown }) => Promise; onError?: (ctx: ActionContext & { error: string }) => Promise; }; type AfterHookFn = (ctx: ActionContext & { result: unknown }) => Promise; type ErrorHookFn = (ctx: ActionContext & { error: string }) => Promise; ``` ### Workflow [#workflow] ```ts type Workflow = { name: string; description: string; storyline?: string; version: string; phases: Phase[]; estimatedCost?: Cost; hooks?: Hooks; }; ``` ### Phase [#phase] ```ts type Phase = { name: string; description: string; storyline?: string; actions: ActionRef[]; checkpoint: boolean; supervision?: SupervisionPolicy; hooks?: Hooks; tools?: string[]; }; ``` ### ActionRef / Branch [#actionref--branch] ```ts type ActionRef = string | Branch; type Branch = { action: string; onSuccess?: string; onFailure?: string; when?: (ctx: ActionContext & { lastOutcome?: { action: string; ok: boolean; error?: string } }) => boolean; }; ``` ### Agent [#agent] ```ts type Agent = { name: string; description: string; role: string; rolePrompt: string; model?: string; contextWindow?: number; actions: Action[]; workflows?: Workflow[]; skills?: Skill[]; channels?: Channel[]; dataSources?: DataSource[]; team?: string; }; ``` ### Tool [#tool] ```ts type Tool = { name: string; description: string; schema: ZodObject; skills?: (string | Skill)[]; fn: (ctx: { data: unknown; ctx: ToolContext }) => Promise>; limits?: { maxCallsPerPhase?: number; maxCallsPerTask?: number; cooldownMs?: number; }; cost?: Cost; budget?: Cost; }; ``` ### ToolContext [#toolcontext] ```ts type ToolContext = { agentName: string; taskId: string; goal?: string; phaseName?: string; toolHistory: ToolHistoryEntry[]; attachments?: Attachment[]; }; ``` ### Event types [#event-types] ```ts type DeltaEventEnvelope = { timestamp: number; agentName: string; }; type DeltaEventDelivered = DeltaEventPayloads[E] & DeltaEventEnvelope; ``` Every event delivered to `delta.events.on()` carries a `timestamp` (milliseconds since epoch, stamped when the event is emitted) and `agentName` (the agent executing the task). Handlers receive the full event payload merged with the envelope. ### Skill [#skill] ```ts type Skill = { name: string; description: string; folder: string; }; ``` ### Channel [#channel] ```ts type Channel = { type: SupportedChannels; enabled: boolean; sendMessage: (message: string, ctx: ChannelContext) => Promise; retrieveMessages?: (ctx: ChannelContext) => Promise; replyMessage?: (id: string, message: string, ctx: ChannelContext) => Promise; }; ``` ### DataSource [#datasource] ```ts type DataSourceOwnership = "internal" | "external"; type DataSourceAuthentication = { type: string; }; type DataSource = { name: string; description: string; ownership: DataSourceOwnership; contentType: string; authentication?: DataSourceAuthentication; actions: { retrieve?: Action; create?: Action; update?: Action; delete?: Action; }; }; ``` ### SupervisionPolicy [#supervisionpolicy] ```ts type SupervisionPolicy = { strategy: "retry" | "restart" | "resume" | "escalate" | "abort-subtree" | "abort-tree"; maxRetries: number; }; ``` ## Engine types [#engine-types] ### DeltaEngineConfig [#deltaengineconfig] ```ts type DeltaEngineConfig = { store?: StoragePort; endpoint?: string; apiKey?: string; options?: ModelOptions; models?: ModelDef[]; reasoner?: ReasonerPort; maxStepsPerTask?: number; providerRetry?: Partial; systemPrompt?: string; timezone?: string; logger?: LoggerConfig; cache?: CacheConfig; diagnostics?: DiagnosticsConfig; commitContextLimit?: number; commitMaxRetries?: number; tools?: ToolsConfig; mailbox?: { inboxCap?: number }; }; ``` ### ModelDef [#modeldef] ```ts type ModelDef = { name: string; model: string; default?: boolean; endpoint?: string; apiKey?: string; options?: ModelOptions; vision?: boolean; audio?: boolean; }; ``` ### SendInput [#sendinput] ```ts type SendInput = { goal: string; agentName: string; budget?: Cost; workflow?: string; input?: Record; actionInputs?: Record>; attachments?: AttachmentInput[]; }; ``` ### SendResult [#sendresult] ```ts type SendResult = { taskId: string; status: "completed" | "failed" | "blocked" | "queued" | "pendingCommit"; snapshot: TaskStateSnapshot; reason?: string; }; ``` ### InspectResult [#inspectresult] ```ts type InspectResult = { task: Task; executions: Execution[]; latestCheckpoint: Checkpoint | null; escalations: EscalationRecord[]; pendingApprovals: ApprovalRequest[]; }; ``` ### InvokeArgs [#invokeargs] ```ts type InvokeArgs = { tool: string; input: unknown; ctx?: Partial; }; ``` ## Shared types [#shared-types] ### Cost [#cost] ```ts type Cost = { tokens: number; durationMs: number; memory?: number; latency?: number; money?: Money; content?: ContentCost; }; ``` ### Money [#money] ```ts type Money = { value: number; currency: string; // ISO 4217, e.g. "USD", "EUR", "NGN" }; ``` ### Attachment [#attachment] ```ts type AttachmentInput = { kind: "image" | "file" | "audio"; mimeType: string; data?: string; url?: string; name?: string; }; type Attachment = AttachmentInput & { id: string }; ``` ### Task [#task] ```ts type Task = { id: string; rootId: string; parentId?: string; status: ExecutionStatus; goal: string; assignedAgent: string; workflow?: string; currentPhase?: string; budget: Cost; risk: RiskState; trust: TrustState; createdAt: Date; updatedAt: Date; }; ``` ### Message [#message] ```ts type Message = { id: string; taskId: string; sender: string; receiver: string; payload: unknown; createdAt: Date; deliveredAt?: Date; readAt?: Date; recalledAt?: Date; consumed: boolean; }; ``` ### Commit [#commit] ```ts type Commit = { id: string; agentName: string; taskId: string; workflowName: string | null; phaseName: string | null; checkpointId: string | null; notes: string; createdAt: Date; }; ``` ### RosterEntry [#rosterentry] ```ts type RosterEntry = { agentName: string; goal: string; status: string; activeSubtasks: number; queuedCount: number; overloaded: boolean; }; ``` ### CleanupOptions [#cleanupoptions] ```ts type CleanupOptions = { taskRetentionMs?: number; messageRetentionMs?: number; evictCache?: boolean; }; ```