A personal AI assistant built as a durable agent: every agent run is a Vercel workflow, so turns survive restarts, streams can be resumed mid-run, and tool calls can park indefinitely waiting for human approval.
Seal is an example app for the AI SDK for Python
(the ai package) and for
Workflows with Python
(vercel.workflow).
The agent (Claude via the AI Gateway) has three tools: bash,
web_fetch, and subagent. Bash runs are gated behind an approval UI
when run by the main agent, but not when run by a subagent. (That is
silly, but this is a demo app.)
- frontend/ — React + Vite chat UI using the AI SDK (
useChat) and AI Elements. Reconnecting to a session re-tails the in-flight stream (useChat({ resume: true })). - backend/app/ — FastAPI service.
POST /api/chatstarts (or resumes) a run and streams the AI SDK UI message protocol; other endpoints cover sessions, titles, and private blob attachments. Seeapp/server.pyfor the endpoint list. - backend/agent/ — the durable agent itself.
driver.pyruns arun_sessionworkflow that spawns one childrun_turnworkflow per agent turn and suspends on a hook until it finishes. Tool approvals are workflow hooks too: the turn parks until the user answers, then resumes with the decision. Model calls, stream writes, and session snapshots are all workflow steps, replay-safe via the workflow's deterministic RNG/clock. - Storage — durable streams and session snapshots are stored on the
workflow SDK's run streams (
agent/stream.py). Session metadata (app/sessions.py) uses Postgres whenDATABASE_URLis set, local JSON files otherwise. Uses Vercel Blob to store attachments when available.
Deployment is two Vercel services (see vercel.json): the frontend and the
backend, with the workflow worker declared in backend/pyproject.toml.
Prereqs: uv, pnpm, and the Vercel CLI.
./dev-setup.sh # sync backend deps (works around a vercel-worker version override)
cd frontend && pnpm install
vercel dev # serves frontend + backend + worker on :3000Environment: AI_GATEWAY_API_KEY (model access), optional DATABASE_URL
(Postgres storage), and a blob token for attachments.
make ci # everything below
make ci-backend # uv sync, ruff, mypy, ty, pytest
make ci-frontend # pnpm install, prettier, eslint, tsc, vitest, builde2e/ drives a real browser against a running instance:
cd e2e && pnpm install && pnpm run install-browser
pnpm test # expects the app at http://localhost:3000
pnpm run test:images # image latency: time to first image, time to all Ntest:images prompts "draw N pictures of things you find interesting"
(N=5 by default) and reports when each image actually painted, measured
from the submit click. Timings also land in
/tmp/seal-e2e-images-summary.json.
Deploy as a project to Vercel with vc deploy. DATABASE_URL must
point to a Postgres database, which can most easily be done by
configuring a marketplace integration with Neon or similar.
