Skip to content

Repository files navigation

echo

echo is an agent observability workspace for watching AI coding agents in real time.

It has three main pieces:

  • Backend: a Bun server that ingests agent lifecycle events, stores them in SQLite, and streams them over WebSocket.
  • Envelope contract: shared TypeScript types and JSON Schema for every event adapter.
  • Web frontends: visual workroom dashboards for observing sessions, navigation, thoughts, and human-in-the-loop requests.

The project is intentionally small: one Bun workspace, no Redis/Postgres/Docker requirement for local development, and clean interfaces where cloud infrastructure can be swapped in later.

What you can do with it

  • Capture lifecycle events from tools such as Claude Code.
  • Watch active sessions in a browser-based workroom UI.
  • Stream recent events to any WebSocket subscriber.
  • Handle human-in-the-loop permission requests through the API or dashboard.
  • Build a PlayCanvas editor-compatible bundle for embedded 3D runtime experiments.

Requirements

Quick Start

Install dependencies:

bun install

Start the backend:

bun start

The backend listens on:

  • HTTP: http://localhost:4000
  • WebSocket: ws://localhost:4000/stream

Check that it is alive:

curl http://localhost:4000/health
# {"status":"ok"}

Start a web dashboard in another terminal:

# Three.js / React Three Fiber app
bun run web:dev:r3f

# PlayCanvas app
bun run web:dev:playcanvas

Workspace Layout

apps/
  server/                 Bun API + WebSocket backend
  adapter-claude-code/    Claude Code hook adapter
  hitl-helper-python/     Python helper for HITL polling flows
  web/
    package.json          Nested web workspace root
    app-r3f/              Three.js / React Three Fiber dashboard
    app-playcanvas/       PlayCanvas dashboard and ESM bundle target
    packages/             Web-only shared packages
packages/
  envelope/               EventEnvelope types, schema, and event vocabulary
docs/                     API, adapter, migration, and web planning docs
openspec/                 Product/spec governance and archived changes

The legacy sonar/ and sonar-playcanvas/ roots are retained outside this package as temporary validation references. New web work should happen under apps/web/*.

Common Commands

Backend:

bun start        # start the API server
bun dev          # start the API server with --watch
bun test         # run all workspace tests

Web apps:

bun run web:dev:r3f
bun run web:dev:playcanvas

bun run web:build:r3f
bun run web:build:playcanvas

bun run web:test:r3f
bun run web:test:playcanvas

bun run web:typecheck:r3f
bun run web:typecheck:playcanvas

PlayCanvas editor bundle:

bun run web:build:esm
# apps/web/app-playcanvas/dist/esm/echo-web-playcanvas.mjs

Send an Event

Every adapter sends an EventEnvelope to the backend. Here is the smallest useful example:

curl -s -X POST http://localhost:4000/events \
  -H 'Content-Type: application/json' \
  -d '{
    "envelope_version": 1,
    "agent_kind": "claude-code",
    "agent_version": "1.0.0",
    "source_app": "my-app",
    "session_id": "sess-001",
    "event_type": "session.start",
    "raw_event_type": "SessionStart",
    "payload": {}
  }'

Open a stream with wscat:

wscat -c ws://localhost:4000/stream

API Reference

See docs/api.md for the full endpoint reference.

Method Path Description
GET /health Liveness probe
POST /events Ingest an envelope
GET /events/recent Most recent events, oldest first
GET /events/filter-options Distinct filter values
POST /events/:id/respond Submit a human-in-the-loop response
GET /events/:id/response?wait=30 Long-poll for a HITL response
WS /stream Real-time event push

Human-In-The-Loop Polling

Use a polling callback when the agent cannot receive inbound requests, for example on a laptop, in CI, or behind NAT.

  1. Post a HITL request:
curl -s -X POST http://localhost:4000/events \
  -H 'Content-Type: application/json' \
  -d '{
    "envelope_version": 1,
    "agent_kind": "my-agent",
    "agent_version": "0.1.0",
    "source_app": "my-app",
    "session_id": "s1",
    "event_type": "hitl.request",
    "raw_event_type": "PermissionRequest",
    "payload": {},
    "human_in_the_loop": {
      "question": "Run rm -rf?",
      "type": "permission",
      "callback": { "kind": "polling" }
    }
  }' | jq .id
  1. Have the agent long-poll for the answer:
curl -m 35 'http://localhost:4000/events/<id>/response?wait=30'
  1. Submit the human response:
curl -X POST http://localhost:4000/events/<id>/respond \
  -H 'Content-Type: application/json' \
  -d '{"permission":true,"responded_by":"alice"}'

wait defaults to 30 seconds and is capped at 60 seconds. The response endpoint returns 200 once answered or 408 on timeout.

Envelope Contract

Every adapter must POST a JSON object that matches the v1 EventEnvelope shape.

See docs/adapter-guide.md for adapter examples in Python and TypeScript.

Claude Code Adapter

The Claude Code adapter translates Claude Code hook events into echo envelopes.

PermissionRequest becomes a real hitl.request with a polling callback, so Allow/Deny actions in the dashboard can unblock Claude Code permission prompts.

See apps/adapter-claude-code/README.md for installation instructions and copy-pasteable settings.json examples.

Configuration

Copy .env.example to .env to override local defaults.

Variable Default Description
SERVER_PORT 4000 HTTP + WebSocket listen port
DB_PATH events.db SQLite file path. Use :memory: for ephemeral runs.
CORS_ORIGINS * Comma-separated allowed origins, or * for open.
WS_SNAPSHOT_LIMIT 300 Max events in the initial /stream snapshot.

Cloud Migration

Local development runs in one Bun process. Multi-replica cloud deployment mainly requires swapping these two interfaces:

See docs/migration-cloud.md for the full checklist.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages