Agents
Agents are reusable model configurations: identity (instructions), model, tools, memory binding, and optional structured output. One agent episode per run() — multi-step tool loops belong in workflow TypeScript.
createAgent
Section titled “createAgent”Registry modules import { adl } from "#adl" (src/adl.ts) and call adl.createAgent:
import { tool } from "@agent-dev-lab/core";import { openai } from "@ai-sdk/openai";import { z } from "zod";
import { adl } from "#adl";
export const researcher = adl.createAgent({ id: "researcher",
instructions: adl.createTemplate({ path: "./researcher.md", from: import.meta.url, inputData: z.object({}), }),
model: openai("gpt-4o"),
tools: { search: tool({ description: "Search for papers", inputSchema: z.object({ query: z.string() }), execute: async ({ query }) => ({ papers: [] as string[] }), }), },
outputSchema: z.object({ summary: z.string() }),
// memory.store defaults to runtime stores.message when omitted});id is the registry key listed in adl.config agents array.
Install a model provider package (e.g. @ai-sdk/openai) in your project for model.
Instructions
Section titled “Instructions”Declared as a template ref or static string. On every run() the instructions are resolved to text and passed to the AI SDK via the system option — not stored as a system message in the conversation.
This avoids the AI SDK system-in-messages prompt-injection warning and keeps the MessageStore free of system text. Any stray system messages (legacy stores or caller messages) are dropped before the model call.
Volatile turn context belongs in user messages, not in instructions.
Structured output
Section titled “Structured output”| Level | Field | Behavior |
|---|---|---|
| Agent default | adl.createAgent({ outputSchema }) | Every run / stream uses structured output unless overridden |
| Per call | agent.run({ outputSchema }) | Overrides agent default for one episode |
Implementation uses streamText with experimental_output when a schema is set — same path for run and stream. AgentRunResult includes output, text, messages, newMessages, and sdk.
What agents do not carry
Section titled “What agents do not carry”stopWhen/ step limits — workflow concern.- Memory pipeline — deferred; v1 uses load/append/save directly.
- Multi-step tool loops —
stopWhen: stepCountIs(1)limits each episode to one SDK step.
memoryScope
Section titled “memoryScope”Opaque string selecting a conversation message list in the store:
`run:${runId}:step:outline`;`user:${userId}:chat:${chatId}`;Same agent + same memoryScope → shared history. New scope → new conversation (new system bootstrap when store is empty).
This is ADL’s conversational resume path — independent of workflow step retry, which uses WorkflowStore step outputs. See Workflows — Resumability.
Run context
Section titled “Run context”Optional context on agent.run() forwards to tool execute via AI SDK experimental_context:
const handle = literatureReview.run({ topic: "CRISPR delivery" });const runId = handle.workflowRunId;
await researcher.run({ memoryScope: "scope-1", user: "Summarize this", context: { resourceId: "user-42", runId },});context is not stored in MessageStore and is not sent to the model unless a tool or workflow copies it into a message.
agent.run and agent.stream
Section titled “agent.run and agent.stream”Both use the same internal streamText implementation:
| API | Caller sees | Runner behavior |
|---|---|---|
agent.run | AgentRunHandle (result, cancel) | Drains stream internally; observers still get agent_text_delta |
agent.stream | AgentStreamHandle with SDK streams | Exposes textStream / fullStream; same persistence on finish |
import type { CoreMessage } from "@agent-dev-lab/core";import type { z } from "zod";
type AgentRunInput = { memoryScope: string; context?: unknown; user?: string; messages?: CoreMessage[]; outputSchema?: z.ZodType<unknown>; workflow?: { workflowRunId: string; stepId: string | null };};Per-run flow
Section titled “Per-run flow”store.load(memoryScope)(anysystemmessages are filtered out)- If
user/messages→ append to working list - Resolve
instructions→ pass as thesystemoption tostreamText streamText— forward text deltas to run events- Append
response.messagesto store viasave - Return
AgentRunResult
Tool calls and persistence
Section titled “Tool calls and persistence”ADL persists only CoreMessage lists. Tool usage round-trips through SDK message shape:
- Assistant parts with
tool-call - Tool role messages with
tool-result
After run(), extend the store with messages from result.response.messages — not from toolCalls alone.
Agents and workflows as tools
Section titled “Agents and workflows as tools”// agents/orchestrator.ts — register tools on an agent that runs inside a workflowimport { adl } from "#adl";
import { researcher } from "./researcher";import { literatureReview } from "../workflows/literature-review";
const literatureReviewTool = adl.createToolFromWorkflow(literatureReview, { description: "Run the full literature review workflow",});
const researcherTool = adl.createToolFromAgent(researcher, { description: "Run one research episode", mapRun: (toolArgs, { ctx }) => ({ memoryScope: ctx.memoryScope(`tool:${(toolArgs as { threadId: string }).threadId}`), user: (toolArgs as { query: string }).query, }),});These helpers require an active workflow context (ALS).
Events
Section titled “Events”The runner emits run events via RunRecorder: agent_started, agent_text_delta, agent_messages_committed, agent_finished, agent_failed. See RunEvent and WorkflowStore in the API reference.