Start Here
Quickstart
An agent that runs in camelRun, calls a tool in your own process, and answers you
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.
Get an API key
Sign in to the console, then:
- Under Models & keys, check that a model is marked Usable. New accounts start with credit for the platform's models, or you can add your own provider key.
- Under API tokens, create a token and export it:
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.
Install the SDK
# Node 22 or later, or Bun
npm install @camelai/runRun 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", {
model: "anthropic/claude-sonnet-5-5",
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.ts or python quickstart.py.
If you see No ... API key configured, pick a model your account can use: the
console's Models & keys page, or GET /v1/models?available=true.
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:
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 '{"model": "anthropic/claude-sonnet-5-5", "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)
until curl -s $BASE/v1/agents/$AGENT/requests/$REQ -H "$AUTH" | jq -e '.state == "completed"' > /dev/null; do sleep 1; done
curl -s $BASE/v1/agents/$AGENT/requests/$REQ -H "$AUTH" | jq -r '.outcome.result.reply // .error'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 calledweatherhere, over the connection the SDK holds. One process at a time serves an agent's tools. Serverless functions and multi-user backends serve tools over HTTP instead; see Tools. run()resolved with aRun:status(completed,input_requiredorfailed),text,inputs,errorandtoolErrors. A failed run throws aRunErrorunless you passthrowOnError: false(throw_on_error=False).agents.close()let the process exit. The agent stays in camelRun.