Skip to content

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.

Registry modules import { adl } from "#adl" (src/adl.ts) and call adl.createAgent:

agents/researcher.ts
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.

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.

LevelFieldBehavior
Agent defaultadl.createAgent({ outputSchema })Every run / stream uses structured output unless overridden
Per callagent.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.

  • stopWhen / step limits — workflow concern.
  • Memory pipeline — deferred; v1 uses load/append/save directly.
  • Multi-step tool loopsstopWhen: stepCountIs(1) limits each episode to one SDK step.

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.

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.

Both use the same internal streamText implementation:

APICaller seesRunner behavior
agent.runAgentRunHandle (result, cancel)Drains stream internally; observers still get agent_text_delta
agent.streamAgentStreamHandle with SDK streamsExposes 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 };
};
  1. store.load(memoryScope) (any system messages are filtered out)
  2. If user / messages → append to working list
  3. Resolve instructions → pass as the system option to streamText
  4. streamText — forward text deltas to run events
  5. Append response.messages to store via save
  6. Return AgentRunResult

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/orchestrator.ts — register tools on an agent that runs inside a workflow
import { 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).

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.