POC assistant engine for Appwrite Cloud /v1/assistant, built on LangGraph.
Stateless by design. Cloud (or any proxy) owns conversation persistence, Realtime, and auth. The UI/client owns MCP OAuth and credentials. This service is the agent runtime only — every turn receives the context it needs over the API.
- Supervisor + subagents (
appwrite,researcher,worker) via LangGraph - Appwrite expert with official agent-skills vendored under
.agents/skills/ - Safe tools by default — no host shell for the model
- Tools:
calculator,current_time,web_search,browser_fetch,appwrite_skill,sandbox_execstub, plus MCP tools from credentials on the turn - FastAPI + shadcn chat UI
cp .env.example .env
# set ASSISTANT_API_KEY and LLM_API_KEY
# Stamp the image so /health and startup logs show which build is running
export ASSISTANT_BUILD_ID="$(git rev-parse --short HEAD)"
export ASSISTANT_BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
docker compose up --build -d
curl -s http://127.0.0.1:8000/health
# → {"status":"ok","build_id":"abc1234","build_time":"2026-08-01T17:56:00Z"}
curl -sN -H "X-Session-API-Key: $ASSISTANT_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"message":"What is 17*19?","history":[]}' \
http://127.0.0.1:8000/api/turn- API docs: http://127.0.0.1:8000/docs
- UI: http://127.0.0.1:3001
cd /path/to/eldadfux/openhands
docker build \
--build-arg ASSISTANT_BUILD_ID="$(git rev-parse --short HEAD)" \
--build-arg ASSISTANT_BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-t ghcr.io/eldadfux/openhands:dev \
.
# Then recreate the cloud compose service that pulls that tag:
# cd cloud && docker compose --profile assistant up -d --force-recreate appwrite-assistant
# curl -s http://127.0.0.1:8000/health && docker logs appwrite-assistant 2>&1 | head -20| Method | Path | Notes |
|---|---|---|
| GET | /health |
Liveness |
| GET | /ready |
LLM configured? |
| GET | /api/meta |
Auth — runtime inspection |
| POST | /api/turn |
Auth — one turn (SSE) |
{
"message": "What is 17*19?",
"history": [
{ "role": "user", "content": "Hi" },
{ "role": "assistant", "content": "Hello!" }
],
"attachments": [
{ "name": "notes.txt", "mime": "text/plain", "content_base64": "..." }
],
"mcp_connections": [
{
"id": "appwrite",
"name": "Appwrite",
"url": "https://mcp.appwrite.io/",
"tokens": { "access_token": "...", "refresh_token": "..." },
"client_info": { "client_id": "..." }
}
]
}SSE events include route, subagent_*, tool_*, token, mcp_credentials (refreshed tokens for the client to store), done, complete.
The engine does not run OAuth. The UI (or production proxy) does:
- Discover protected-resource + authorization-server metadata
- Dynamic client registration (public client + PKCE)
- Browser authorize → callback at
{origin}/oauth/mcp/callback - Store
tokens+client_infoin localStorage - Send them on every
/api/turnasmcp_connections
Production Appwrite should own steps 1–4 and replay credentials on each turn the same way.
| Variable | Required | Purpose |
|---|---|---|
ASSISTANT_API_KEY |
yes (prod) | Clients send X-Session-API-Key |
LLM_API_KEY |
yes | Model provider API key |
LLM_MODEL |
no | Default openai/gpt-4o |
LLM_BASE_URL |
no | Optional OpenAI-compatible base URL |
WEB_SEARCH_ENABLED |
no | Headless browser web search (default true) |
ATTACHMENTS_MAX_BYTES |
no | Max inline attachment size (default 10MB) |
- Do not expose this container on a public Gateway/HTTPRoute.
- The model cannot run host shell commands.
- Unauthenticated mode (empty API key) is for local smoke tests only.
./scripts/update-appwrite-skills.sh
docker compose up --build -d assistantpython -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
playwright install chromium
export LLM_API_KEY=... ASSISTANT_API_KEY=...
uvicorn app.main:app --reload --port 8000