---
title: SDK reference
description: Everything the SDK gives your agent, and everything it asks of it.
---

## The CLI

| Command | What it does |
|---|---|
| `redline init` | Scaffolds the doorway file (and `redline.config.ts` in TypeScript). |
| `redline dev` | Connects: registers your agents and executes their runs. Leave it running. |
| `redline doctor` | Checks the wiring — key, URL, agent file, env files, tool injection — without spending a run. |

In a Node project, prefix with `npx`. Two environment variables matter:

| Variable | What it is |
|---|---|
| `REDLINE_API_KEY` | Your project's runner key (`rl_…`), created on the Agents page. |
| `REDLINE_URL` | Where the platform lives. Only needed if it is not the default. |

## `defineAgent`

```ts
import { defineAgent } from "@redlineai/sdk";

export const myAgent = defineAgent({
  id: "my-agent",           // a kebab-case slug: letters, digits, dashes
  name: "My Agent",         // the display name on the Agents page
  description: "…",         // optional
  run: async (task, ctx) => "the final answer",
});
```

`run` must return the final answer as a string (an object is serialised).
Throwing marks the run errored with that message.

In Python it is a decorator:

```python
from redline import agent

@agent(id="my-agent", name="My Agent")
def run(task, ctx):
    return "the final answer"
```

> **Two rules about the doorway file**
>
> Every file in `redline/` is imported by the CLI, so those files must only
> export agent definitions and must not start a server on import. And the `id`
> must not collide with a catalog agent's — registration is rejected if it does.

## `task` — what your function is given

| Field | What it holds |
|---|---|
| `task.prompt` | The instructions the experiment's task carries, plus a briefing of anything attached. |
| `task.name` | The task's name, for your logs. |
| `task.files` | What was attached: `id`, `name`, `kind`, and a source when there is one. |
| `task.assets` | The MCP servers, skills, CLIs, SDKs and plugins the experiment attached. |
| `task.attempt` / `task.maxAttempts` | Which attempt this is, of how many trials. |
| `task.experiment` | `{ id, name }` — the experiment this run belongs to. |

## `ctx` — the run's reporter

Everything here lands in the run's transcript live. All of it is optional; with
an OpenTelemetry-speaking framework most of it is captured for you.

| Method | What it records |
|---|---|
| `ctx.thinking(text)` | A line of reasoning. |
| `ctx.tool(name, input?)` | A tool call. |
| `ctx.toolResult(name, result?, isError?)` | What that call returned. |
| `ctx.log(text)` | Anything worth keeping that is neither. |
| `ctx.artifact(name, content)` | A named file or report, kept on the run. |
| `ctx.usage(tokens, costCents?)` | Model usage, when your agent knows it. Adds up across calls. |
| `ctx.cancelled` | `true` once the run has been cancelled. Long loops should check it. |

Python has the same surface with snake_case: `ctx.thinking()`, `ctx.tool()`,
`ctx.tool_result()`, `ctx.artifact()`, `ctx.usage()`, `ctx.cancelled`.

## `redlineTools(task, options?)`

Turns what the experiment attached into tools your agent can call.

```ts
const attached = await redlineTools(task);
```

| On the result | What it is |
|---|---|
| `attached.tools` | Ready to spread into a Vercel AI SDK `tools:` object. |
| `attached.list` | The same tools, framework-neutral, with plain JSON Schema. |
| `attached.langchain()` | LangChain `StructuredTool`s, when `@langchain/core` is installed. |
| `attached.skipped` | What could not be wired up, and why — so a run never silently lacks a tool. |
| `attached.close()` | Closes the MCP connections. Also runs at process exit. |

Options: `timeoutMs` (how long to wait for a server to connect and list its
tools, default 30s), `skillTool` (also expose skills as a `read_skill` tool —
off by default, since skills already arrive in the prompt), and `machine` (used
internally to attach the run's machine).

In Python: `attached = await redline_tools(task)`, then
`attached.for_pydantic_ai()`, `attached.for_langchain()` or
`attached.as_openai_schema()`, closed with `await attached.close()`. Needs
`pip install redlineai-sdk[mcp]`.

## `machine_run`

When the experiment attached something that has to physically exist — a
repository, an upload, a CLI, an SDK package, a plugin — the run is given a
container, and this tool appears in your agent's tool list automatically.

```ts
machine_run({ command: "ls -la", cwd: "billing-api" })
```

It is a real bash line on Ubuntu, starting in `/workspace`, with node, python3,
git and sudo available. `cwd` may be relative — it resolves against the
workspace. The box is built on the first call and destroyed once the run and its
judging are over; a run that never calls it never builds one.

## `installClis(task)`

Runs the install command of every CLI the experiment attached, **on your own
machine**. Redline never does this for you — see the warning in
[Bring your own agent](/agents/your-agent). Returns one
`{ name, ok, output }` per CLI.

## `redline.config.ts`

```ts
import { defineConfig } from "@redlineai/sdk";

export default defineConfig({
  url: "https://redline.example.com",   // usually left out
  apiKey: process.env.REDLINE_API_KEY,  // usually left out; the env var is read
  agents: ["redline/*.ts"],             // globs, from this file's directory
  envFiles: [".env", "server/.env"],    // usually left out — see below
});
```

Leave `envFiles` out and `redline dev` finds them itself: `.env` and `.env.local`
in the repository root and up to two levels down (`server/.env`,
`apps/api/.env`). Variables already in your shell are never overwritten. Set
`env: false` to load nothing, when your process is already configured.
