Deploy¶
AgentServer is a drop-in FastAPI wrapper. It deploys anywhere FastAPI
runs. This guide covers the most common targets: a container, Kubernetes,
serverless, and a plain VM. In every case the agent authenticates to its
model provider with an API key supplied via an environment variable — no
cloud-specific identity wiring required.
The shape you ship¶
# server.py
from tulip.agent import Agent
from tulip.server import AgentServer
from tulip.memory.backends import S3Backend
agent = Agent(
model="anthropic:claude-sonnet-4-6",
tools=[...],
system_prompt="...",
checkpointer=S3Backend(bucket="tulip-threads"), # or RedisBackend(...)
)
server = AgentServer(
agent=agent,
title="Booking concierge",
api_key="...", # require this bearer token on every route except /health
)
if __name__ == "__main__":
server.run(host="0.0.0.0", port=8080)
You get out of the box:
POST /invoke— synchronous run, returns the finalmessageplus run metrics (success,stop_reason,iterations,tool_calls,duration_ms) as JSON.POST /stream— Server-Sent Events of every typed event.GET / DELETE /threads/{id}— conversation persistence.GET /health— liveness probe.
Provider keys are read from the environment (OPENAI_API_KEY,
ANTHROPIC_API_KEY). Inject them as secrets, never bake them into the
image.
Container — the universal target¶
The repo ships a multi-stage Dockerfile
(non-root user, HEALTHCHECK on /health). Build, push to any registry,
and run anywhere that runs containers:
docker build -t registry.example.com/tulip-concierge:0.1.0 .
docker push registry.example.com/tulip-concierge:0.1.0
docker run -p 8080:8080 \
-e OPENAI_API_KEY=sk-... \
registry.example.com/tulip-concierge:0.1.0
This single image drops straight into Cloud Run, ECS / Fargate, Fly.io, Azure Container Apps, or any other container host.
Serverless — scale to zero¶
Best for low-frequency or bursty traffic. Wrap the FastAPI app in an
adapter for your platform — Mangum for
AWS Lambda, or deploy the container image directly to a
scale-to-zero container runtime (Cloud Run, Container Apps). Agent is
constructed lazily, so cold starts stay cheap. Set the provider key as a
function secret.
Kubernetes — for production¶
Best for multi-replica, autoscaled, multi-region production. A minimal deployment:
apiVersion: apps/v1
kind: Deployment
metadata: { name: concierge }
spec:
replicas: 3
selector: { matchLabels: { app: concierge } }
template:
metadata: { labels: { app: concierge } }
spec:
containers:
- name: concierge
image: registry.example.com/tulip-concierge:0.1.0
ports: [{ containerPort: 8080 }]
env:
- name: OPENAI_API_KEY
valueFrom: { secretKeyRef: { name: tulip-secrets, key: openai-api-key } }
readinessProbe:
httpGet: { path: /health, port: 8080 }
resources:
requests: { cpu: 500m, memory: 1Gi }
limits: { cpu: 2, memory: 4Gi }
---
apiVersion: v1
kind: Service
metadata: { name: concierge }
spec:
type: LoadBalancer
selector: { app: concierge }
ports: [{ port: 80, targetPort: 8080 }]
For SSE streaming, ensure your ingress / load balancer doesn't buffer the
response (X-Accel-Buffering: no on nginx, or the buffering-off
equivalent on your load balancer).
Plain VM — full control¶
Best when you need raw VM access or run the agent alongside other local services.
pip install "tulip-agents[openai,server]"
git clone https://github.com/tuliplabs-ai/sdk-python.git ~/concierge
cd ~/concierge
# Launch under systemd
sudo tee /etc/systemd/system/concierge.service <<'EOF'
[Unit]
Description=Tulip concierge agent
After=network.target
[Service]
Type=simple
User=app
Environment=OPENAI_API_KEY=sk-...
ExecStart=/home/app/.local/bin/uvicorn server:app --host 0.0.0.0 --port 8080
Restart=always
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable --now concierge
Sessions — thread_id for chat UIs¶
When the underlying agent has a checkpointer, pass a thread_id in the
request body for cross-request continuity. Same browser tab → same
thread_id → same context. Omit it, and each request starts fresh.
POST /invoke
Content-Type: application/json
{"prompt": "What were we discussing?", "thread_id": "user-c42-support"}
When api_key is set, the authenticated principal is prefixed onto the
thread_id server-side, so threads are scoped to the caller that owns
the key. The server takes a single shared api_key, so this is
single-principal scoping — not per-tenant isolation.
Observability¶
Wire TelemetryHook to your OTLP collector for traces and metrics.
Set the exporter target via the standard OpenTelemetry environment
variables before the agent starts:
export OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
from tulip.hooks.builtin import TelemetryHook
agent = Agent(
...,
hooks=[TelemetryHook(service_name="my-agent")],
)
Datadog accepts OTLP. So do Honeycomb, Tempo, Grafana Cloud, and every other backend that speaks the spec. See Observability.
See also¶
- Agent Server — the FastAPI wrapper in detail.
- Conversation Management —
how
thread_idsurvives across requests and restarts. - Model providers — providers and configuration.