camelAI Documentation

Start Here

Quickstart

An agent that runs in camelRun, calls a tool in your own process, and answers you

View as Markdown · Docs index for coding agents

Set up with your coding agent. Paste this into Claude Code, Codex, Cursor or any coding agent. It asks you what the agent should do, installs the SDK, connects your account without pasting keys into chat, and runs your first agent.

Read https://run.camelai.com/SKILL.md and set up camelRun in this project. Ask me what the agent should do.

In five minutes you will have an agent that runs in camelRun, calls a tool in your own process, and answers you. camelRun keeps the model loop, the agent's history and its files. Your tools stay in your code, with your credentials.

Credit

Signing in to the console with GitHub or Google creates your account. GitHub accounts older than 30 days start with free credit. Everyone else verifies a card on the Billing page to unlock starting credit: the card is saved, not charged. Runs use credit even when you bring your own provider key.

Try it from your coding agent (no key)

Connect the hosted MCP server and sign in. There is no key to create or copy.

1

Add the camelRun MCP server

claude mcp add --transport http camelrun https://run.camelai.com/mcp
# then run /mcp in Claude Code and sign in with GitHub or Google

Cursor, VS Code and Claude: see Connect your coding agent.

2

Ask for an agent

Use the camelrun MCP server to create my first camelRun agent: ask me what it should do, create it, run it once, and show me its reply.

Your agent appears under Agents in the console.

Build it into your app

1

Get an API key

Sign in to the console. Under API tokens, create a token and export it:

bash
export CAMELAI_API_KEY=art_...

The key can create and control every agent in your account. Keep it on your server and never ship it to a browser.

2

Install the SDK

# Node 22 or later, or Bun
npm install @camelai/run
3

Run an agent

import { Agents, schema, tool } from "@camelai/run";

const agents = new Agents(); // reads CAMELAI_API_KEY

// A tool is an ordinary function. It runs here, in your process.
const weather = tool({
  description: "Today's weather in a city",
  input: schema.Object({ city: schema.String() }),
  execute: ({ city }) => ({ city, forecast: "sunny", highC: 24 }),
});

// The same key is the same agent, with its history, every time you run this.
const agent = await agents.upsert("quickstart", {
  instructions: "You are a concise assistant.",
  tools: { weather },
});

const run = await agent.run("Should I bring an umbrella in Lisbon today?");
console.log(run.text);

await agents.close();

Run it with npx tsx quickstart.mts or python quickstart.py.

.mts makes the TypeScript an ES module, which top-level await needs; in a project with "type": "module", .ts works too. The Python SDK is async-only: call it from async code, and from synchronous code (a script, a Django view, a Celery task) with asyncio.run(...).

The examples name no model, so the agent gets your account's default: a model your account can use.

Choose a model (optional)

List the models your account can use, then name one:

npx -y @camelai/camelrun models --available
const agent = await agents.upsert("quickstart", {
  model: "<a model from the list>", // e.g. provider/model-id
  instructions: "You are a concise assistant.",
  tools: { weather },
});

If a run fails with code model_key_missing, the agent names a model your account has no key for, and the error says which key to set: pick a model from the list, leave model out, or add the provider's key under Models & keys.

With curl

Without an SDK, an agent can't call tools in your process, but everything else works over REST. Create an agent with a first prompt, then poll the run:

bash
BASE=https://run.camelai.com; AUTH="Authorization: Bearer $CAMELAI_API_KEY"

# The Idempotency-Key is the agent's key: the same key is the same agent.
CREATED=$(curl -s $BASE/v1/agents -H "$AUTH" -H "Content-Type: application/json" \
  -H "Idempotency-Key: quickstart-curl" \
  -d '{"systemPrompt": "You are a concise assistant.",
       "prompt": {"text": "Write a haiku about durable agents."}}')
AGENT=$(echo "$CREATED" | jq -r .id); REQ=$(echo "$CREATED" | jq -r .prompt.id)

# ?wait=25 answers as soon as the run ends (or after 25 s, still running: ask again).
until RUN=$(curl -s "$BASE/v1/agents/$AGENT/requests/$REQ?wait=25" -H "$AUTH") && jq -e '.state == "completed"' <<< "$RUN" > /dev/null; do :; done
jq -r 'if .status == "failed" then "failed: \(.error)" else .outcome.result.reply end' <<< "$RUN"

Send later messages with POST /v1/agents/{id}/prompt {"text": "..."}, which answers 202 with the request to poll. See REST API.

Stream the reply

run() waits for the whole run. To show it as it happens, stream() yields text as the model writes it, each tool call and result, and the finished run last:

for await (const part of agent.stream("And tomorrow?")) {
  if (part.type === "text") process.stdout.write(part.text);
  if (part.type === "tool_call") console.log(`\n[${part.name}(${JSON.stringify(part.arguments)})]`);
  if (part.type === "done") console.log(`\n(${part.run.status})`);
}

The run's result is the source of truth, and the stream is for display. If a stream breaks, the run isn't lost: run() (or stream.result()) still resolves with it.

What just happened

  • upsert("quickstart", ...) made a keyed agent, or found the one from last time and brought it to this configuration. It lives until you delete it, so running the file again continues the same conversation.
  • Because the agent has tools, this process served them. camelRun called weather here, over the connection the SDK holds. One process at a time serves an agent's tools, and only while it runs. Serverless functions, several web workers or task workers, and multi-user backends serve tools over HTTP instead, which also keeps them working through deploys; see Several processes, workers and deploys.
  • run() resolved with a Run: status (completed, input_required or failed), text, inputs, error, toolErrors and toolCalls (Python tool_errors, tool_calls). A failed run throws a RunError unless you pass throwOnError: false (throw_on_error=False). A completed run can still list tool errors, e.g. connection_lost when the process serving its tools went away mid-call: check them.
  • agents.close() let the process exit. The agent stays in camelRun.

Next steps