> ## Documentation Index
> Fetch the complete documentation index at: https://docs.burn0.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Bring Your Own Agent

> The contract between your agent and the Silo runtime

Silo does not give you an agent. It gives you a world and a boundary, and you plug your agent into it.

That boundary is one function. Silo calls it once, hands it the job and the means to do it, and waits for an answer. Everything in between is yours — a model loop, a framework, or a hard-coded line of calls.

## The contract

Your agent module default-exports a function. Silo calls it with one object.

**What Silo hands you:**

|                         |                                                                                                                                                     |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `task`                  | The instruction, as a string. Just the objective — no solution path, no list of tools to use, no answer.                                            |
| `tools`                 | Every tool this environment exposes: `name`, `description`, and a JSON Schema `inputSchema`. This is what you feed a model as its tool definitions. |
| `callTool(name, input)` | Executes a tool against the live world and returns `{ output, isError? }`.                                                                          |
| `signal`                | An `AbortSignal`, tripped when a run exceeds its time or tool-call limit.                                                                           |

**What you return:** an object with an `output`. A string is used as-is; anything else is JSON-stringified. That text is what an answer-producing verifier grades.

<Note>
  The agent never sees the environment's state. It sees the instruction, the tool schemas, and whatever tools return. If it should be able to look something up, a tool exists for it — and if no tool exposes it, the agent cannot know it.
</Note>

## Pointing Silo at it

Your agent is an ordinary file in your project. Name it whatever suits you and pass `--agent`:

```bash theme={null}
npx @burn0/silo run --env demo --task TASK-004 --agent ./erp.agent.js
npx @burn0/silo run --env demo --task TASK-004 --agent ./demo.ts
npx @burn0/silo run --env demo --task TASK-004 --agent ./agents/claude-loop.ts
```

Because it is just a path, you can keep several agents side by side and run the same task against each to compare them against the same seeded world.

There is one shortcut: name it `silo.agent.ts` in the directory you run from and the flag is optional.

```bash theme={null}
npx @burn0/silo run --env demo --task TASK-004
```

<Warning>
  That shortcut matches `./silo.agent.ts` only — the `.ts` extension specifically. A `silo.agent.js` still needs `--agent ./silo.agent.js`, or the run fails with `Cannot find module`.
</Warning>

Agents are plain ES modules, so your project needs `"type": "module"` in its `package.json`. Without it the file loads as CommonJS, its exports end up nested under `.default`, and you get a misleading error about a missing export.

## Failed tool calls come back as values

`callTool` does not throw when a tool rejects the call. It returns `isError: true` alongside a machine-readable code and a message written to be handed straight back to a model:

```json theme={null}
{
  "output": {
    "code": "invalid_input",
    "message": "Invalid arguments for \"get_opportunity\": \"opportunityId\" is required; \"opportunityID\" is not a parameter; expected one of: opportunityId."
  },
  "isError": true
}
```

That message names both the mistake and the real parameter, which is what gives a model a chance to correct itself on the next turn rather than failing silently. Unknown tools come back the same way, with `"code": "tool_not_found"`.

This is deliberate. A harness that throws on a bad tool call ends the rollout and scores a failure; returning the error as a value turns a typo into a recoverable turn. Feed it back into your loop and let the model try again.

## Where the model goes

Swap the straight line for your own loop. The shape is the same in any framework:

```javascript theme={null}
export default async function agent({ task, tools, callTool }) {
  const messages = [{ role: "user", content: task }];

  while (true) {
    const reply = await yourModel({ messages, tools });   // your SDK, your provider

    if (!reply.toolCalls?.length) {
      return { output: reply.text };
    }

    for (const call of reply.toolCalls) {
      const result = await callTool(call.name, call.arguments);
      messages.push({ role: "tool", content: JSON.stringify(result.output) });
    }
  }
}
```

Silo does not care which model or library you use. It only cares that something calls `callTool` and eventually returns an `output`.

Note that the loop pushes `result.output` back into the conversation without checking `isError` — a rejected call is just another observation for the model to read and act on.

## Respecting limits

A run can be capped:

```bash theme={null}
npx @burn0/silo run --env demo --task TASK-001 --max-tool-calls 50 --timeout-ms 60000
```

When either limit trips, `signal` aborts and further `callTool` calls stop. A long-running loop should check `signal.aborted` and return what it has rather than spinning.
