Everything in the request. Nothing in the engine.
No database, no configs, no tenant registry. Your backend decides what the assistant is — on every single call.
stateless by designOne POST /runs is one run. You send the provider, model, key, prompt, history and tools. theloop goes model → tools → model, streams the text, calls you back with usage you can actually bill — and then forgets it ever met you.
curl localhost:8080/runs \
-H "Authorization: Bearer $ENGINE_TOKEN" \
-d '{
"contract_version": "1",
"llm": { "provider": "anthropic",
"model": "claude-sonnet-5" },
"messages": [ { "role": "user",
"content": [ { "type": "text",
"text": "Hello!" } ] } ],
"wait": true
}'
# → result + usage. No DB was harmed.
Built for teams shipping assistants to lots of users. Multi-tenant by construction, because there's no tenant anything to configure.
No database, no configs, no tenant registry. Your backend decides what the assistant is — on every single call.
stateless by designCallbacks retry, dedupe by run_id, and stay readable at GET /runs/{id} for recovery. Late results land by history revision.
it will find youA write tool answers "deferred" with an operation id. The mutation happens in your system, after approval — never inside a tool call.
effect: "write"One record per provider attempt — failed and interrupted ones too. Uncached input, cache read/write, an honest "estimated" flag, optional cost and budgets.
finance will like youHTTPS only, host allowlist, no private IPs, no redirects. Provider keys never show up in logs, SSE, callbacks or error bodies.
keys stay keysAnthropic, OpenAI, DeepSeek, GLM, GigaChat, MiniMax or anything OpenAI-compatible — with per-key queues and AIMD rate limiting.
per-request keysTRUST MODEL
A prompt-injected model can request a refund all day long. Nothing happens until your system says yes. It's a two-sided deal, though — the engine enforces what it can see, your endpoints carry the rest.
Tests run without network or keys. The rest needs one provider key and a coffee you won't finish.
git clone https://github.com/paveler/theloop.git
cd theloop && npm install
cp .env.example .env # ENGINE_TOKEN + a key
npm run dev # → :8080
docker run -p 8080:8080 \
-e ENGINE_TOKEN=change-me \
-e ANTHROPIC_API_KEY=sk-ant-… \
ghcr.io/paveler/theloop:1
The short answers. The long ones live in the SPEC, which is the contract.
theloop is an open-source agent loop that runs as a stateless HTTP service. One POST /runs is one run: the request carries the provider, model, API key, prompt, full history, tools (URL + JSON schema) and limits. The engine runs model → tools → model until a final answer, streams text over SSE, delivers the result and billing-grade usage by callback, and keeps no state between calls. It is written in TypeScript for Node 22 and licensed under Apache 2.0.
theloop is a service you call over HTTP, not a framework or SDK you import. Your backend (the harness) decides what the assistant is on every request: prompt, tools, model and budget. The engine decides how the loop runs: steps, retries, limits, streaming, cancellation and accounting. Any language or low-code platform that can send an HTTP request can drive it.
In your system. History is a list of messages that your harness stores and sends in full with every run; theloop appends the new messages and returns them. Resuming a conversation means starting a new run with a longer history. That is why theloop is multi-tenant by construction: there is no tenant state to configure.
Anthropic, OpenAI, DeepSeek, GLM (z.ai), GigaChat, MiniMax and any OpenAI-compatible endpoint. Provider keys are passed per request, with per-key queues and AIMD rate limiting. More providers, including OpenRouter, native Gemini and local models, are proposed in the roadmap.
Write tools are intents. A tool marked effect: "write" only registers an operation and answers "deferred" with its id; the mutation happens in your system after approval, never inside a tool call. So a prompt-injected model can ask for an action but cannot carry it out. This holds only if your write endpoints change nothing, tools marked "read" have no side effects, and data is scoped by meta (tenant, user) rather than by ids in the arguments.
theloop returns one usage record per provider attempt, including failed and interrupted attempts, with uncached input, cache read and cache write tokens and an explicit estimated flag. It can optionally compute cost from your price list and enforce token and cost budgets.
Not in v1. Today a tool is an HTTPS endpoint with a JSON schema that theloop calls through its egress allowlist. MCP tools are proposed for v1.1 in the roadmap, with the same rule that only read-only tools are executed by the engine.
The v1 contract is frozen: semver, no breaking changes within 1.x. The implementation is young, so expect fixes rather than contract changes. v1 runs as a single instance and keeps runs in memory for a retention period; durable state stays in your system.