Building Agents
Build an agent from scaffold to gated tools, a channel, an automation, and passing evals.
An Assembly Line agent is a folder. This tutorial builds a complete agent one
file at a time, from assembly-line init to passing evals. Each
step gives the exact command, the exact file content, and the expected output,
then links to the Agent Build Stack page that owns
that file's full option surface.
Commands use the installed assembly-line form. In a source checkout, run them as
pnpm assembly-line <command> (see Getting Started).
- Scaffold the agent
- Write the instructions
- Add a tool with an approval gate
- Add a channel and test it over HTTP
- Add an automation
- Write two evals and run them
- Ship it
1. Scaffold The Agent
assembly-line init agentCreated Assembly Line agent at /path/to/agent
Next: export OPENAI_API_KEY, then: pnpm assembly-line run /path/to/agent --message "hello"The scaffold is the smallest useful agent:
agent/
instructions.md # trusted, always-on guidance
agent.ts # identity/policy plus runtime capability hooks
gateway.ts # deploy/runtime/state/blob/sandbox/scheduler adapters
.env.example # provider env template
tools/
echo.ts # one typed model-callable actionOnly instructions.md and agent.ts are required; everything else is
optional. Give the agent a name and description in agent.ts with
defineAgent:
// agent.ts
import { defineAgent, useModel, useTool } from "@assemblyline-agents/core";
export default defineAgent({
name: "notes-agent",
description: "Records durable notes behind an approval gate.",
setup() {
useModel("openai/gpt-5.4-mini");
useTool("echo");
}
});The full folder map, including context.ts, skills/, connections/,
sandbox/, subagents/, and instrumentation.ts, is in the
Agent Build Stack overview. Every defineAgent
static policy and built-in hook, including useOutputSchema(), persistent
workflow state, maxIterations, selfImprovement, dynamicAutomations, and
dynamicConnections, is on
agent.ts.
2. Write The Instructions
instructions.md is trusted, always-on guidance. It is the one prose file the
model always sees. Replace the scaffold line with an identity and three rules:
You are a concise note-taking agent.
- Use tools when they are available instead of describing what you would do.
- Ask before recording anything durable.
- Keep replies to a few sentences.Validate after every step:
assembly-line validate agentValid Assembly Line agent: /path/to/agentWhat belongs in instructions versus skills, tool descriptions, or memory is covered in instructions.md.
3. Add A Tool With An Approval Gate
Each file in tools/ becomes one model-facing tool. The filename is the tool
name. The scaffolded tools/echo.ts has no side effects. Add a second tool
with a side effect and require approval:
// tools/record_note.ts
import { approvalRequired, defineTool } from "@assemblyline-agents/core";
export default defineTool({
description: "Record a durable note after explicit approval.",
inputSchema: {
type: "object",
properties: {
note: { type: "string" }
},
required: ["note"]
},
needsApproval: approvalRequired("Recording a note is a durable side effect."),
async execute(input: { note: string }, ctx) {
await ctx.emit("note.recorded", {
note: input.note,
idempotencyKey: ctx.idempotencyKey("record-note")
});
return { recorded: true, note: input.note };
}
});Enable the compiled tool by adding useTool("record_note") to agent.ts
setup(). Tool files define how actions work; hooks decide which actions are
available in the current capability snapshot.
Run it without approval to watch the gate pause the run:
assembly-line run agent --tool record_note --input '{"note":"Ship Friday"}'{
"run": {
"id": "3f9d2b1e-…",
"status": "waiting_for_approval",
...
},
"waitingForApproval": true,
...
}Approve it and it completes:
assembly-line run agent --tool record_note --input '{"note":"Ship Friday"}' --approve{
"run": {
"id": "8a41c6d0-…",
"status": "completed",
...
},
"response": "{\"recorded\":true,\"note\":\"Ship Friday\"}",
...
}Approval policies and side-effect classes, durable steps, sandbox execution,
and toModelOutput projections that keep rich results out of model context
are on tools/.
4. Add A Channel And Test It Over HTTP
Channels normalize external events into agent turns and deliver replies back to the provider. Add a raw HTTP channel for local development:
// channels/http.ts
import { defineChannel } from "@assemblyline-agents/core";
export default defineChannel({
description: "Receive a local/dev HTTP message.",
transport: "http",
route: "/message",
methods: ["POST"]
});A channel turn is a full model turn, so set the provider key for the model in
agent.ts, then serve the agent:
export OPENAI_API_KEY=sk-...
assembly-line serve agent --port 3000Assembly Line runtime serving 4b0c9a17…
http://127.0.0.1:3000In a second terminal, post to the route:
curl -X POST http://127.0.0.1:3000/message \
-H "content-type: application/json" \
-d '{"message":"hello"}'{
"runId": "3f9d2b1e-…",
"status": "completed",
"response": "Hello! How can I help you today?",
"waitingForApproval": false,
"waitingForInput": false,
"waitingForConnection": false,
"eventCount": 8,
"toolCallCount": 0
}This raw shape is open in dev mode only. In production, generic HTTP channels
require host auth. Provider-facing routes should export normalizeHttp() to
verify and normalize the provider event before starting a turn. Provider
helpers for Slack, Discord, Telegram, Microsoft Teams, and Photon/Spectrum keep
webhook wiring in one file. Install them with
assembly-line add <channel> agent; see channels/.
5. Add An Automation
Files in automations/ compile into schedule- or event-triggered durable work:
// automations/morning_brief.ts
import { defineAutomation } from "@assemblyline-agents/core";
export default defineAutomation({
description: "Run a small daily brief.",
trigger: {
type: "schedule",
cron: "0 8 * * *",
timezone: "America/Chicago"
},
idempotencyKey: "notes-agent:morning-brief",
message: "Summarize yesterday's notes."
});Rebuild and inspect the compiled schedule table:
assembly-line build agentBuilt Assembly Line agent revision 9e5d2c80…
Artifact: /path/to/agent/.assembly-line.assembly-line/automations.json now lists the automation. Event triggers, trusted
lifecycle handlers, and runtime-created dynamic automations are covered in
automations/.
6. Write Two Evals And Run Them
evals/*.json is the agent's golden dataset, run through the same compiled
runtime path used in production. Start with one normal case and one high-risk
case. Both force a tool run, so they are deterministic and need no provider
key.
evals/echo_roundtrip.json:
{
"name": "Echo round trip",
"input": {
"message": "ping",
"tool": "echo",
"toolInput": { "message": "ping" }
},
"expect": {
"status": "completed",
"toolsCalled": ["echo"]
}
}evals/approval_gate.json:
{
"name": "Note requires approval",
"tags": ["high-risk"],
"input": {
"message": "Record that we ship Friday.",
"tool": "record_note",
"toolInput": { "note": "Ship Friday" },
"approve": false
},
"expect": {
"status": "waiting_for_approval",
"toolsCalled": ["record_note"]
}
}Run the suite:
assembly-line eval agent✓ Echo round trip 84ms $0.000000
✓ Note requires approval 41ms $0.000000
2/2 executions passed (100.0%); 0 failed; 0 errored
2 unique cases; 1 repetition requested
Cost: runs $0.000000 + judges $0.000000 = $0.000000
Tags:
high-risk 1/1 passed, 0 failed, 0 erroredEach case gets isolated state, blob, and sandbox roots, and channel senders are never invoked. The full case contract, multi-turn conversations, output and JSON Schema assertions, tool trajectories, tool mocks, state fixtures, custom evaluators, LLM-as-judge, repetitions, baselines, and CI gates, is on evals/.
7. Ship It
gateway.ts declares where the agent runs. The scaffold pins every slot
local:
// gateway.ts
import { adapter, defineGateway } from "@assemblyline-agents/core";
export default defineGateway({
deploy: adapter("local"),
runtime: adapter("node"),
state: adapter("local"),
blob: adapter("local"),
sandbox: adapter("local"),
scheduler: adapter("local")
});Swap slots without touching agent code, for example,
assembly-line add postgres agent sets state: adapter("postgres"). Preview the
deploy plan without publishing:
assembly-line deploy agent --dry-runThis prints the deploy plan JSON: target, environment, required env, and any missing requirements. Adapter roles and gateway conventions are on gateway.ts; deploy targets, host auth, secrets, and the production checklist are in Runtime And Deployment.
Design Rules
The canonical authoring rules cover file ownership, secrets, and untrusted-context boundaries. They live in the Agent Build Stack overview.