Bring Your Own Framework
CrewAI, LangGraph, AutoGen, your own Python script: any Docker image that speaks a three-endpoint contract gets the whole platform.
Overview
You do not have to use a Maritime template. Wrap your agent in a small HTTP server, and Maritime gives it everything the built-in frameworks get: a micro-VM per agent (or per end-user of your product), sleep at ~zero cost with memory intact, wake on the next message, channels, scheduled wakes, webhooks, and per-agent metering.
The contract
Three endpoints. That is the whole integration.
1. Bind a long-lived server on 0.0.0.0:$PORT (PORT is injected; never hardcode 8080)
2. GET /health -> any 2xx, fast, no side effects
3. POST /chat -> {"message": "...", "source": "front_door" | "cli" | "telegram" | ...}
reply within 30s with plain text or {"response": "..."}Accepted reply fields, in priority order: response, reply, message, text, output. Plain-text bodies are used verbatim. Slow work should run asynchronously; reply within 30 seconds with what you have.
CrewAI example
# main.py
import os
from fastapi import FastAPI
from crewai import Agent, Crew, Task
app = FastAPI()
researcher = Agent(role="researcher", goal="answer the user well", backstory="…")
@app.get("/health")
def health():
return {"ok": True}
@app.post("/chat")
async def chat(body: dict):
task = Task(description=body["message"], expected_output="a helpful answer", agent=researcher)
result = Crew(agents=[researcher], tasks=[task]).kickoff()
return {"response": str(result)}# Dockerfile
FROM python:3.12-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "start.py"]
# start.py
import os, uvicorn
uvicorn.run("main:app", host="0.0.0.0", port=int(os.getenv("PORT", "8080")))LangGraph, AutoGen, and friends are the same shape: build your graph or group chat once at boot, invoke it inside /chat, return a string.
The four gotchas
Every one of these has silently broken a real deployment. Check them before anything else.
- Bind $PORT. Maritime injects it (currently 18789 inside micro-VMs). Hardcoding 8080 collides with the VM's port forwarder and crash-loops the boot.
- Install ca-certificates. Slim base images ship without it and every outbound HTTPS call fails with confusing TLS errors.
- Use the injected LLM env vars.
OPENAI_API_KEYandOPENAI_BASE_URLpoint at Maritime's metered proxy (CrewAI and LangChain read both natively). If you bring your own key, set it as a secret env var and do not keep the proxy base URL. - Persist to /data only. It survives restarts, redeploys, and sleep/wake; the rest of the filesystem can vanish on a recreate. Your process is snapshotted and resumed, not restarted, so do not trust cached wall-clock time and re-establish long-lived connections lazily.
- No shell-string CMD. Micro-VM init re-executes your image's CMD as one flattened string, so
CMD ["sh", "-c", "uvicorn ... $PORT"]loses its quoting, the server starts with no arguments, and the VM kernel-panics on boot. Launch through a real program (astart.pythat reads $PORT and calls your server directly).
Deploying
# from a pre-built image
maritime create my-brain --image ghcr.io/acme/support-brain:latest --json
# or from a GitHub repo with a Dockerfile (framework: "custom", or "crewai"/"langgraph")
curl -X POST https://api.maritime.sh/api/agents \
-H "Authorization: Bearer $MARITIME_API_KEY" -H "Content-Type: application/json" \
-d '{"name": "my-brain", "framework": "crewai", "githubRepo": "https://github.com/acme/my-brain", "branch": "main"}'Repo and ZIP sources build on Maritime's build workers (a few minutes per build), so iterate on prompts and configuration through env vars or files under /data, and rebuild the image only when dependencies change. From here the front door applies unchanged: set the project's newChatPolicy: "spawn" and every end-user of your product gets their own micro-VM running your image.
Scheduled wakes
A sleeping VM's timers do not fire; let Maritime be the alarm clock. Either serve GET /schedules returning entries like the one below (Maritime polls while you are awake and registers real wake triggers), or push the list with one SDK line (observeScheduler / observe_scheduler). Maritime wakes the VM about 10 seconds before each occurrence and delivers prompt to POST /chat with source "scheduled".
{"id": "morning", "cron": "35 9 * * *", "tz": "America/New_York",
"prompt": "Send the morning summary", "enabled": true}Checklist
[ ] Binds 0.0.0.0:$PORT, not a hardcoded port
[ ] GET /health -> 2xx, fast
[ ] POST /chat -> reply within 30s, {"response": "..."} or plain text
[ ] ca-certificates installed (slim images)
[ ] State lives in /data; process tolerates snapshot/resume
[ ] Scheduled work goes through GET /schedules or the SDK push, never a sleeping-VM timer