Skip to main content

Serve CARL reasoning chains as HTTP agents (FastAPI) — per-agent Swagger, hub with hot-reload from gigaevo Memory

Project description

carl-agent-server

Serve CARL reasoning chains as HTTP agents. One chain = one agent: a FastAPI facade with /invoke, /info, /healthz and its own Swagger (/docs) — the OpenAPI title, description and version come from the chain's metadata, so the docs page reads as that agent's documentation and users can try the agent right from /docs.

Part of the MMAR ecosystem: chains are authored in CARE / MAGE, stored & versioned in gigaevo Memory (channels: lateststable), executed by mmar-carl. In attached mode an agent follows a Memory channel and hot-reloads on promote/pin — promote a new version and the running agent picks it up with zero downtime; pin the old version to roll back. A hot-reloaded version that fails preflight is rejected and the healthy one keeps serving. Updates arrive via SSE push with a polling safety net (see Hot-reload).

Quickstart — the hub (recommended)

One lightweight process hosts N agents; each gets its own Swagger at /agents/<name>/docs. Deploy/undeploy at runtime via the control API:

uv sync --group dev

AGENT_LLM_API_KEY=sk-... AGENT_LLM_MODEL=openai/gpt-4o \
uv run carl-agent-hub serve --port 8080

# deploy a chain JSON from disk (offline source)
curl -X POST localhost:8080/deployments \
     -H 'content-type: application/json' \
     -d '{"name": "demo", "chain_file": "./chain.json"}'

# deploy a chain entity from Memory, following its stable channel
curl -X POST localhost:8080/deployments \
     -H 'content-type: application/json' \
     -d '{"name": "weather", "entity_id": "<chain-uuid>", "channel": "stable"}'

open http://localhost:8080/agents/demo/docs   # try POST /invoke from Swagger

Deployments persist to --state-file (default ~/.care/agent-hub.json) and are restored on restart; --no-persist disables that.

Hub control API

Endpoint Purpose
GET /deployments list deployments (name, url, version, ready, runs)
POST /deployments deploy (body = deployment spec; 409 duplicate, 422 unloadable)
GET /deployments/{name} one deployment
POST /deployments/{name}/reload re-fetch + preflight + swap now
DELETE /deployments/{name} undeploy (unmounts the agent)
GET /healthz hub liveness

Solo mode (one agent = one process)

AGENT_LLM_API_KEY=... AGENT_LLM_MODEL=openai/gpt-4o \
uv run carl-agent serve --chain-file ./chain.json --name demo --port 8001
# or attached: --entity-id <uuid> --channel stable

Agent API (under /agents/<name> in the hub, or the root in solo mode)

Endpoint Purpose
POST /invoke run the chain (?mode=sync default; ?mode=async → 202 + run_id)
POST /chat converse with the agent ({message, session_id?}); the dialogue so far is fed into the chain each turn — the chain is unchanged. Omit session_id to start a session (returned in the reply); sessions evict after the idle TTL
GET /runs/{id} run status/result (answer, steps, tokens, time)
POST /runs/{id}/input resume a run paused on a human_input step (status waiting) — {value}. Async-invoke flow: invoke ?mode=async, poll until waiting, then provide input
GET /runs/{id}/events SSE step stream (replays history, ends with result)
DELETE /runs/{id} cooperative cancel of a running run
GET /schedule the deployment's auto-invoke schedule + firing stats (D3)
POST /schedule/trigger fire one scheduled run now (manual trigger)
GET /metrics usage + cost: run counts, total tokens, total USD, budget + remaining (D4)
GET /info agent card (name, version, channel, required tools, readiness)
GET /healthz / GET /readyz liveness / readiness (with the reason when 503)
GET /docs this agent's own Swagger

Environment

Variable Purpose
AGENT_LLM_API_KEY / AGENT_LLM_MODEL / AGENT_LLM_BASE_URL OpenAI-compatible LLM the chains run on
AGENT_MEMORY_URL / AGENT_MEMORY_API_KEY gigaevo Memory (attached mode)
AGENT_WEB_SEARCH_API_KEY enables the web_search builtin tool (Tavily)

Per-deployment overrides (llm_model, llm_api_key, memory_url, …) exist on the deployment spec, but prefer env vars: hub specs persist to the state file verbatim, and secrets belong in the environment, not on disk.

Auth

Set a per-agent api_key on the deployment (CARE's /deploy generates one) and /invoke, /chat, /runs/* require it via X-API-Key: <key> (or Authorization: Bearer <key>); /healthz, /readyz, /info, /docs stay open. Loopback requests (127.0.0.1/::1) skip the check unless auth_allow_localhost=false. No api_key set → auth is off (localhost demo). Solo: carl-agent serve --api-key <key> (or AGENT_API_KEY). The hub's state file holds these keys and is written chmod 600.

Hot-reload (attached mode)

An attached agent follows its Memory channel through two mechanisms:

  • SSE watcher (fast path): a gigaevo_client subscription to /v1/events/stream filtered on the entity — a promoted/pinned event triggers a reload within ~a second.
  • Poll fallback (safety net): every poll_fallback_s (default 60s, 0 disables) the agent compares the channel's current version_id to the serving one and reloads on drift.

The fallback exists because the SSE subscription can die silently: gigaevo_client (≤0.3.0) retries a failing /v1/events/stream in a loop without ever surfacing the error — e.g. when a stale Memory deployment routes that path into the generic /v1/{entity_type}/{entity_id} handler (400), the watcher looks armed but no event is ever delivered. With the fallback, a missed promote is picked up within a minute instead of never; POST /deployments/{name}/reload remains the immediate manual lever.

To check the live events endpoint a deployment is watching: curl -N <memory-url>/v1/events/stream must hold the connection open and print entity_changed events on promote — an instant JSON error means the Memory deployment is broken/stale and only the poll fallback (and manual reload) will move versions.

Both paths funnel into one swap-safe reload: fetch → parse → preflight → swap, and a failed candidate never evicts the serving chain.

Timeouts

Two layers bound a run. chain_timeout_s (default 300s) is the agent's hard wall-clock deadline for the whole run. step_timeout_s (default 60s) is a default per-step timeout injected at load into any step the author left unbounded — capped never to exceed the chain-level timeout, so it tightens but never loosens authored intent. Together a single hung step fails fast at the step level instead of burning the whole run budget.

Schedules

A deployment can carry a schedule ({interval_s, input, enabled}) and the agent auto-invokes its chain on that cadence — the in-template scheduler, lifecycle-bound (starts on activation, stops on shutdown; survives a single run's failure, skips ticks while not ready). GET /schedule reports it, POST /schedule/trigger fires one run now. For external cron/batch use care run instead; an inbound HTTP trigger is just POST /invoke.

Cost & budgets

Set per-1k token prices on the deployment (price_per_1k_input_usd, price_per_1k_output_usd) and each run's USD cost is computed from its token usage and stamped on the run record; GET /metrics reports run counts, total tokens and total spend. An optional budget_usd cap refuses further runs with 402 once spent (needs pricing to take effect; the scheduler skips ticks while over budget).

Tools

Deployed agents ship a read-only builtin tool set: calculator, current_datetime, fetch_url, http_request (GET/HEAD only — mutating methods raise) and web_search (when a key is configured). Mutating tools (e.g. run_python) are deliberately not registered in deployments.

Development

uv run pytest tests/ -q
uv run ruff check src/ tests/
uv run mypy src/

Status: Phase A of the production-mode plan is nearly complete (agent core, async/SSE runs, attached hot-reload, the hub, CLIs). Next: run-records to Memory, then the CARE control-plane integration (/deploy from the TUI).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

carl_agent_server-0.1.1.tar.gz (82.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

carl_agent_server-0.1.1-py3-none-any.whl (38.4 kB view details)

Uploaded Python 3

File details

Details for the file carl_agent_server-0.1.1.tar.gz.

File metadata

  • Download URL: carl_agent_server-0.1.1.tar.gz
  • Upload date:
  • Size: 82.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for carl_agent_server-0.1.1.tar.gz
Algorithm Hash digest
SHA256 1b2a8a51627fbd42a9d7ec721a2b62d48f858a097881a06e4408dbbb0e74dda6
MD5 417577d3ce701e48b569cc9f345589e6
BLAKE2b-256 32a720f9b467da8506ad8c6b522ae22ca7680911596aa91d0e6096df6066b62c

See more details on using hashes here.

File details

Details for the file carl_agent_server-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for carl_agent_server-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 52fa5ea8431916b9bff6a7747208a9accbda4b818d85375d338c2aba707f998b
MD5 7fb75649019df0a0836674f21c19587e
BLAKE2b-256 4493e75a9e456b4d040a9c5202b52fc7cda3e69fe2782d8f418a065a4b40125a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page