Building Agents
An Assembly Line agent is a folder. This page is a linear tutorial — your first real
agent — built 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 agent
Created 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, model, and runtime policies
gateway.ts # deploy/runtime/state/blob/sandbox/scheduler adapters
.env.example # provider env template
tools/
echo.ts # one typed model-callable action
Only 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 } from "@assemblyline-agents/core";
export default defineAgent({
model: "openai/gpt-5.4-mini",
name: "notes-agent",
description: "Records durable notes behind an approval gate."
});
The full folder map — context.ts, skills/, connections/, sandbox/,
subagents/, instrumentation.ts, and the rest — is in the
Agent Build Stack overview. Every defineAgent
option, including structured outputs (outputSchema), maxIterations,
selfImprovement, dynamicAutomations, and dynamicConnections, is on
agent.ts.
2. Write The Instructions
instructions.md is trusted, always-on guidance — 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 agent
Valid Assembly Line agent: /path/to/agent
What 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
that does — and gate it behind an 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 };
}
});
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 3000
Assembly Line runtime serving 4b0c9a17…
http://127.0.0.1:3000
In 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, and provider-facing routes should export normalizeHttp()
to verify and normalize the provider event before starting a turn. Provider
helpers — Slack, Discord, Telegram, Microsoft Teams, and Photon/Spectrum —
keep webhook wiring to one file and are installed
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 agent
Built 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 errored
Each 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-run
This 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 — what goes in which file, secrets handling, and untrusted-context boundaries — live in the Agent Build Stack overview.