Caura / docs
Integrations

Rail SDK

Give a Python or TypeScript agent memory around every turn. Rail recalls rules and facts before your agent runs and stores what the turn taught.

Rail is Caura's SDK for agents you write yourself. Before your agent runs, Rail fetches the governance rules and the facts relevant to the current message and hands you prompt-ready context. After the agent replies, Rail extracts facts worth keeping and writes them back. Your code stays in charge of the model call. Python and TypeScript share the same semantics, and both work against managed and self-hosted Caura.

Use the thin REST clients when you only need to call the API. Use Rail when an agent should remember and follow rules.

The complete Rail documentation lives in the public repository, not on this site: the guide, the API reference, and the reliability semantics. This page and the tutorial cover the first hour; those three pages cover everything else.

Install

pip install caura-rail        # Python 3.10+
npm install @caura/rail       # Node.js 22+, TypeScript or JavaScript

Rail reads CAURA_URL, CAURA_API_KEY, and optionally CAURA_TENANT from the environment. On managed Caura, CAURA_URL is https://caura.ai and the key is a tenant-scoped mc_ key from Settings → API Keys; Rail resolves the tenant from the key. On a self-hosted standalone server, use the server URL and the placeholder key standalone.

Your first turn

from caura_rail import MemoryScope, Rail, RestMemoryStore, Visibility

scope = MemoryScope(agent_id="support-1", fleet_id="support", visibility=Visibility.TEAM)

with RestMemoryStore.from_env() as store:
    rail = Rail(store, scope)
    with rail.turn("Remember: We deploy in eu-west-1.") as turn:
        # Call your model here. turn.context.text holds rules first, then facts.
        turn.reply = "Understood. Context used:\n" + turn.context.text
    print(turn.reply)
    print("degraded:", turn.degraded, "writes:", [w.status for w in turn.writes])

Asynchronous applications use AsyncRail with AsyncRestMemoryStore.

import { MemoryScope, Rail, RestMemoryStore } from "@caura/rail";

const scope = new MemoryScope({ agentId: "support-1", fleetId: "support", visibility: "scope_team" });
const rail = new Rail({ store: RestMemoryStore.fromEnv(process.env), scope });

const turn = await rail.turn("Remember: We deploy in eu-west-1.", async (message, context) => {
  // Call your model here. context.text holds rules first, then facts.
  return "Understood. Context used:\n" + context.text;
});
console.log(turn.reply);
console.log("degraded:", turn.degraded, "writes:", turn.writes.map(w => w.status));

Every turn does four things in order: recall, run your code, extract, write. If your code raises, nothing is written. If Caura is unreachable, the turn is marked degraded, your reply still returns, and retryable writes wait in an in-memory outbox that you replay with flush_outbox() / flushOutbox().

What you get on every turn

  • Context: keystone rules sorted by weight, then recalled facts, plus text formatted for a prompt. Rules are never dropped to make room for facts.
  • Writes: one result per extracted fact with status written, deduplicated, deferred, or rejected.
  • Diagnostics: errors, degraded, and process-local counters in rail.telemetry.

Under the hood each turn calls GET /api/v1/keystones, POST /api/v1/search with the agent as caller_agent_id, and POST /api/v1/memories with write_mode: "strong", so a stored fact is searchable by the next turn. Rail never calls the LLM-summary endpoint /recall.

The default extractor stores user lines that begin with Remember:, We use, We deploy, Our plan, or Our contract. Pass your own extractor to store anything else, or return an empty list to make Rail recall-only.

Scope, fleets, and rules

MemoryScope names the agent and says who may recall what it writes: agent-private by default, TEAM within a fleet, or ORG across the tenant. An agent belongs to the fleet of its first write; use one agent id per fleet. Rules are read on every turn; they are authored through the keystones API or the dashboard. On managed Caura the gateway refuses a tenant-scoped key for rule writes (HTTP 403, AGENT_NOT_REGISTERED); mint an agent-scoped credential at trust level 2 with one call to the per-agent keys endpoint and author with that. Team facts that other clients stored without a fleet are visible to every agent in the tenant and will appear in recall alongside your fleet's own. Set require_keystones / requireKeystones when an agent must not run without its rules.

Compatibility

Python 3.10 to 3.14 and Node.js 22 and 24, against managed Caura and open-source release backend-v2.47.0 or later. Earlier servers ignore the caller identity Rail asserts on search, so an agent cannot recall its own private facts; pin CAURA_VERSION in the server's .env rather than relying on the latest image. The open-source quick start ships a placeholder embedder; configure a real embedding provider before judging recall quality.

Step by step

Give a coded agent memory with Rail walks through a first turn, a teammate recalling it, a governance rule landing in the context, and a custom extractor, in both languages.

Complete example

Harborline in the Rail repository is a whole application: a web UI, a Python service that runs its turns through AsyncRail with governance rules required, and a TypeScript intake service on @caura/rail, all sharing one fleet. One environment variable switches it between the open-source server and caura.ai, and its ./demo.sh runs an asserted end-to-end scenario against either. Each turn in its UI shows the context Rail assembled, the reply, and the status of every stored fact.

Learn more

The Rail repository has the full guide, API reference, and reliability semantics. Every code block there is executed and type-checked in CI against a live Caura server.