Skip to main content

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).

  1. Scaffold the agent
  2. Write the instructions
  3. Add a tool with an approval gate
  4. Add a channel and test it over HTTP
  5. Add an automation
  6. Write two evals and run them
  7. 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.