Share a chat with your AI coding agent like a Google Doc — viewers watch it think live, editors drive it on your keys.
Bring your own agent. Bring your own keys. Keep your secrets.
Harness is a thin, fast control plane over whatever coding agent you want to drive — Claude Code, Codex CLI, or Cursor, each in its own cloud sandbox — plus a built-in 10-model chat for when you don't want an agent at all. Your model bill stays on your account, your credentials are encrypted write-only, and every sensitive command surfaces an approval card before it runs.
Share an agent session the way you share a Google Doc — one link, viewer or editor.
- Viewers (even logged-out) watch the agent think, call tools, and write code live as it streams, fanned out over Redis.
- Editors do something Docs can't: they type a new prompt straight into your conversation and the agent runs it on your harness, your credentials, your budget. The editor's browser sends only the message and the share token — your MCP secrets, sandbox, and real cost never leave the server.
Collaborative agent sessions, without ever handing over the keys.
Every coding agent, one chat. Claude Code, Codex CLI, or Cursor — each in its own cloud sandbox — plus a built-in 10-model chat.
Harnesses. Swap your whole toolset — model, MCP servers, skills, prompt, agent — mid-conversation.
One-click MCP. GitHub, Notion, and Linear over OAuth, plus Exa, Context7, AWS, and Princeton TigerApps.
Secrets stay server-side. Your tokens never enter the agent's sandbox.
Real Linux boxes. Live terminal, file explorer, and a git panel per session.
Approvals-first. Permissions, plans, and questions render as inline cards.
Live sharing & collaboration. Viewers follow token-by-token; editors drive on the owner's harness and keys.
Rewind & fork. Branch or roll back any conversation; fork any shared chat into your own account.
Background-agent observability. Subagents, workflows, and long-running commands as live task cards.
Workspaces, skills & a command palette. Color-tinted workspaces, importable playbooks, and Cmd/Ctrl-K for everything.
Encrypted, bring-your-own credentials. Write-only secrets; usage bills to your own account.
Harness is a three-tier monorepo: a TanStack Start frontend, a Convex realtime backend, and a FastAPI agent/stream gateway. The browser holds a live Convex WebSocket for data (chats, harnesses, workspaces, shares, usage) and an SSE stream to FastAPI for token-by-token model and agent output. Clerk issues the JWT that authenticates both.
┌──────────────────────────────────────────────────────────┐
│ Browser (SPA) │
│ TanStack Start · React 19 · xterm.js │
└───────┬───────────────────────────────────────┬──────────┘
Convex WS │ │ SSE (token / agent stream)
(data) ▼ ▼
┌────────────────────────┐ ┌──────────────────────────────┐
│ Convex backend │ Clerk JWT │ FastAPI gateway │
│ realtime DB · 19 │◄────────────►│ /api/chat · /api/agents │
│ tables · shareGrants │ │ /api/chat/follow (read) │
└────────────────────────┘ └──┬────────────┬──────────┬───┘
│ │ │
OpenRouter ◄────────┘ │ │ ACP over
(10 models) │ │ sandbox URL
│ ▼
stream_bus.tee() │ ┌─────────────────────────────┐
(allowlist + sanitize, │ │ Daytona sandbox │
1s breaker / fail-soft) │ │ ┌───────────────────────┐ │
│ XADD │ │ │ acp_shim.mjs (stdio↔ │ │
▼ │ │ │ HTTP/SSE bridge) │ │
┌──────────────────────────────────┐ │ │ │ │ │ │
│ REDIS STREAM (shared instance) │ │ │ │ ▼ child proc │ │
│ harness:stream:{conversationId} │ │ │ │ Claude Code / Codex /│ │
│ + harness:turn:{id} marker │ │ │ │ Cursor (ACP agent) │ │
└──────────────────────────────────┘ │ │ └───────────┬───────────┘ │
▲ ▲ ▲ (blocking XREAD, │ └──────────────┼─────────────┘
│ │ │ own cursor each) │ relay_request (MCP call)
GET /api/chat/follow (auth as shared read) │ ▼
│ │ │ │ ┌────────────┐
Follower Follower Follower └─────────►│ MCP servers│
(other tab / sharee / late joiner) relay_response │ (OAuth) │
w/ your tokens └────────────┘
- Convex realtime layer. All durable state lives in Convex (19 tables:
messages,conversations,harnesses,workspaces,sandboxes,shareGrants,usageLedger, and more), pushed to the browser over a WebSocket and authenticated with a Clerk JWT. - FastAPI gateway. Streams the default OpenRouter chat loop and orchestrates ACP agent sessions. It brokers MCP OAuth server-side, fans display-only events out to followers via Redis Streams, and resolves editor-collaborator runs to the owner's harness/credentials/billing.
- ACP in Daytona. Each agent runs as a child process inside an isolated sandbox. An in-box
acp_shim.mjsbridges the agent's stdio JSON-RPC to HTTP/SSE, reached by the FastAPI ACP client over the sandbox's preview URL. - MCP relay. Because sandbox egress is restricted, the shim emits each agent MCP call as a
relay_request; FastAPI executes it with your OAuth tokens and returns arelay-response— so secrets never enter the box. - Live follow (Redis). Each turn streams SSE 1:1 to its initiator and tees every display event into a per-conversation Redis Stream. Passive viewers hit
GET /api/chat/follow— authorized exactly like a shared read — which replays the current turn from its start, then block-tails for new tokens. An allowlist relays only the transcript a viewer can already see; owner-only signals (MCP URLs, sandbox id, per-turn cost) are stripped. Fan-out is pull-based, so N viewers — and producers on other workers — all read the same shared stream. Redis is fail-soft: unset, and turns simply stream to the initiator only.
| Layer | What's in it |
|---|---|
Frontend (apps/web) |
TanStack Start 1.132 · React 19 · Tailwind CSS v4 · xterm.js 6 · cmdk · Clerk · Convex client · Arcjet · Vitest · TypeScript 5.9 · deployed to Cloudflare Workers via Wrangler 4 |
Realtime backend (packages/convex-backend) |
Convex 1.31.7 · Clerk JWT auth · 19-table schema |
Agent gateway (packages/fastapi) |
FastAPI · Python 3.11+ · OpenRouter · MCP (OAuth 2.1) · Daytona SDK · ACP agents (Claude Code / Codex / Cursor) · Redis Streams · cryptography (AES-256-GCM) · sse-starlette |
| Tooling | Turborepo · Bun 1.3.5 · Biome · Husky · pytest · CI on GitHub Actions · CD via convex deploy + Wrangler (Cloudflare) and rsync/systemd (EC2) |
Prereqs: Bun
1.3.5, Python3.11+, and accounts for Convex, Clerk, OpenRouter, and Daytona.
bun installcd packages/convex-backend
bun install
npx convex login
npx convex dev # creates a cloud deployment + .env.local hereCopy the generated *.convex.cloud URL into apps/web/.env.local as VITE_CONVEX_URL:
VITE_CONVEX_URL=https://your-deployment.convex.cloudCreate a Clerk project and copy its keys into apps/web/.env.local:
VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...Then set the JWT issuer on the Convex dashboard (Settings → Environment Variables):
cd packages/convex-backend && npx convex dashboard
# set:
CLERK_JWT_ISSUER_DOMAIN=https://your-clerk-deployment.clerk.accounts.devcd packages/fastapi
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
cp .env.example .env # then fill in the values below
.venv/bin/uvicorn app.main:app --reload --port 8000Fill in packages/fastapi/.env (OPENROUTER_API_KEY, CONVEX_URL, DAYTONA_API_KEY, …) and set FRONTEND_URL=http://localhost:3000 to match the web dev port. Then point the web app at the gateway in apps/web/.env.local:
VITE_FASTAPI_URL=http://localhost:8000A Redis URL is optional locally — live multi-viewer "follow" fan-out is fail-soft and no-ops when Redis isn't configured. Credential encryption requires
AGENT_CREDENTIALS_KEYon the FastAPI service (any non-empty secret; FastAPI SHA-256-derives the 32-byte AES-256-GCM key from it) — it isn't in.env.example, so add it yourself.
turbo devThe web app comes up on http://localhost:3000, Convex syncs in the background, and FastAPI listens on :8000.
Harness/
├─ apps/
│ └─ web/ # TanStack Start (React 19) frontend → Cloudflare Workers
├─ packages/
│ ├─ convex-backend/ # Convex realtime DB (19-table schema, Clerk JWT)
│ └─ fastapi/ # FastAPI agent/stream gateway
│ └─ app/
│ ├─ routes/ # chat, agents, sandbox, terminal, harness_suggest …
│ └─ services/ # agents (ACP registry/client/shim), mcp_oauth,
│ # daytona_service, stream_bus, secrets_crypto
├─ deploy/ # EC2 systemd units + setup scripts
└─ turbo.json # Turborepo task graph
Pull requests target staging, not main. A Husky pre-commit hook runs Biome — before committing TS/TSX, run:
cd apps/web && bun x biome check --write src/CI runs pytest (FastAPI gateway) and vitest (frontend and Convex backend); keep all three green.
Built by Ibraheem Amin (lead), Abu Ahmed, Cole Ramer, Richard Wang, and John Wu.
Copyright © 2026 the Harness authors.
Harness is free software, licensed under the GNU General Public License v3.0. You may redistribute and/or modify it under those terms; it comes with no warranty. See ./LICENSE for the full text.







