Phase 7 features: - Ace Attorney-style PixiJS renderer with camera presets, character poses, dialogue typewriter, and effects engine - Render directives inferred from role, phase, and dialogue keywords - Structured case file generated at session start with witness roster, evidence inventory, and charge sheet - Witness statement events emitted during examination - Audience interaction endpoints: POST /press and /present with rate limiting - Twitch chat adapter (press, present, objection commands) - NDJSON session recording and deterministic replay (CLI flags + env vars) - SSE fixture recorder for browser overlay replay - Prometheus metrics endpoint (GET /api/metrics) with session lifecycle, vote latency, SSE connection, and phase transition telemetry - Docker: TRUST_PROXY support, 127.0.0.1 bind, network isolation Quality review fixes: - C1: Rate-limit /press and /present endpoints (10 req/IP/10s) - C2: Fix Twitch objection to read current count and increment properly - I1: Document /api/metrics in README, docs/api.md, operator-runbook - I2: Comment out unused TWITCH_EVENTSUB_SECRET in .env.example - I3: Fix event-taxonomy render_directive schema (poses/faces maps) - I4: Deduplicate dashboard event mapping into session-snapshot.ts - M1: Match Ace Attorney keywords with or without trailing punctuation - M3: Add SSE fixture recording docs to docs/api.md
13 KiB
JuryRigged
JuryRigged is a real-time, multi-agent courtroom simulation. An Express API orchestrates agent dialogue across deterministic phases, streams live events via Server-Sent Events (SSE), and supports jury voting for verdict and sentence outcomes.
This repository is standalone and does not require subcult-corp at runtime.
Highlights
- Multi-agent role orchestration (judge, prosecutor, defense, witnesses, bailiff)
- Strict, forward-only phase progression:
case_prompt→openings→witness_exam→evidence_reveal→closings→verdict_vote→sentence_vote→final_ruling- Optional skip:
witness_exam→closings
- Live per-session SSE stream (
/api/court/sessions/:id/stream) - Jury voting APIs with phase gating + anti-spam/rate-limiting
- Main viewer UI (
public/) and React operator dashboard (/operator) - In-memory or Postgres-backed persistence (auto-selected by
DATABASE_URL) - Optional broadcast hook integration (
nooporobs) for production workflows - Ace Attorney–style renderer (Phase 7): PixiJS overlay with camera presets, character poses, dialogue typewriter, effects engine, and evidence presentation
- Structured case file: immutable case context with witness roster, evidence inventory, and charge sheet generated at session start
- Audience interaction:
/pressand/presentAPI endpoints + Twitch chat commands (!press,!present,!objection) - Render directives: backend-inferred visual cues (camera, pose, face, effects) streamed alongside dialogue turns
- NDJSON recording and deterministic replay: record live sessions to NDJSON, replay at configurable speed
Tech Stack
- Node.js + TypeScript
- Express (API + static serving)
- React + Vite (operator dashboard)
- Postgres (optional durable store)
Quick Start (Local)
1) Install dependencies
npm install
2) Configure environment
cp .env.example .env
For a zero-dependency local run:
- leave
OPENROUTER_API_KEYempty (mock dialogue fallback) - leave
DATABASE_URLempty (in-memory session store)
3) Start the API server
npm run dev
Default local URL: http://localhost:3000 (from .env.example).
4) Build operator dashboard assets
npm run build:dashboard
Then open:
- Main app:
http://localhost:3000 - Operator dashboard:
http://localhost:3000/operator
The API serves
/operatorfromdist/dashboard. If you haven’t built it yet,/operatorwill return a helpful 404 message.
Dashboard Dev Mode (Hot Reload)
Run the dashboard separately while API dev server is running:
npm run dev:dashboard
- Dashboard dev URL:
http://localhost:3001/operator/ - API proxy target in Vite is
http://localhost:3000
If your API is not on port 3000, update vite.config.ts proxy settings.
Docker Compose (API + Postgres)
The compose stack includes:
api(JuryRigged server)db(Postgres 16)
Start:
npm run docker:up
Stop:
npm run docker:down
Container behavior:
- API runs on container port
3001 - Host mapping defaults to
${API_HOST_PORT:-3001} - Migrations run automatically on container startup (
npm run migrate:dist) TRUST_PROXYdefaults to1in compose so IP-based rate limits remain accurate behind one proxy hop
Default compose endpoints:
- App + API:
http://localhost:${API_HOST_PORT:-3001} - Operator dashboard:
http://localhost:${API_HOST_PORT:-3001}/operator
Configuration
Copy .env.example and tune as needed.
Core runtime
| Variable | Purpose |
|---|---|
PORT |
API port for local non-Docker runs (default in .env.example: 3000) |
TRUST_PROXY |
Express proxy trust setting (true, false, hop count like 1, or CIDR/subnet list) used for accurate client IP detection behind reverse proxies |
OPENROUTER_API_KEY |
Required for live LLM calls; empty enables deterministic mock fallback |
LLM_MODEL |
OpenRouter model identifier |
LLM_MOCK |
Force mock mode (true/false) |
DATABASE_URL |
Enables Postgres-backed durable store; omit for in-memory |
LOG_LEVEL |
debug, info, warn, error |
Voting + moderation safety
| Variable | Purpose |
|---|---|
VERDICT_VOTE_WINDOW_MS |
Verdict poll window duration |
SENTENCE_VOTE_WINDOW_MS |
Sentence poll window duration |
VOTE_SPAM_MAX_VOTES_PER_WINDOW |
Vote rate cap per window |
VOTE_SPAM_WINDOW_MS |
Rate-limit window size |
VOTE_SPAM_DUPLICATE_WINDOW_MS |
Duplicate-vote suppression window |
Witness / token controls
| Variable | Purpose |
|---|---|
WITNESS_MAX_TOKENS |
Max witness response tokens before truncation |
WITNESS_MAX_SECONDS |
Max witness response duration |
WITNESS_TOKENS_PER_SECOND |
Duration↔token heuristic |
WITNESS_TRUNCATION_MARKER |
Marker appended after truncation |
JUDGE_RECAP_CADENCE |
Emit recap every N witness cycles |
ROLE_MAX_TOKENS_* |
Per-role token budget overrides |
TOKEN_COST_PER_1K_USD |
Cost estimation coefficient |
Broadcast integration
| Variable | Purpose |
|---|---|
BROADCAST_PROVIDER |
noop or obs |
OBS_WEBSOCKET_URL |
OBS WebSocket endpoint |
OBS_WEBSOCKET_PASSWORD |
OBS auth password (optional but recommended) |
See docs/broadcast-integration.md for setup details.
Twitch integration (Phase 7)
| Variable | Purpose |
|---|---|
TWITCH_CHANNEL |
Twitch channel to monitor for audience commands |
TWITCH_BOT_TOKEN |
OAuth token for the Twitch bot account |
TWITCH_CLIENT_ID |
Twitch application client ID |
When any Twitch variable is unset, the adapter runs in noop mode (no connection).
Replay + recording (Phase 7)
| Variable | Purpose |
|---|---|
RECORDINGS_DIR |
Directory for NDJSON session recordings (default: recordings/) |
REPLAY_FILE |
Path to NDJSON file to replay instead of live orchestration |
REPLAY_SPEED |
Playback speed multiplier (1 = real-time, 4 = 4×) |
API at a Glance
GET /api/healthGET /api/metrics(Prometheus-format telemetry)GET /api/court/sessionsGET /api/court/sessions/:idPOST /api/court/sessionsPOST /api/court/sessions/:id/votePOST /api/court/sessions/:id/press(Phase 7 — audience press)POST /api/court/sessions/:id/present(Phase 7 — present evidence)POST /api/court/sessions/:id/phaseGET /api/court/sessions/:id/stream(SSE)
Full schemas, error codes, and event contracts: docs/api.md.
Development Commands
npm scripts
| Command | Description |
|---|---|
npm run dev |
Start API in watch mode (src/server.ts) |
npm run dev:dashboard |
Start Vite dashboard dev server |
npm run build |
Compile TS to dist/ + build dashboard |
npm run build:dashboard |
Build dashboard only |
npm run start |
Run compiled server (dist/server.js) |
npm run migrate |
Run migrations from source (tsx) |
npm run record:sse |
Record SSE session fixture |
npm run migrate:dist |
Run migrations from compiled output |
npm test |
Run Node test suite |
npm run test:ops |
Run ops config tests |
npm run smoke:staging |
Run staging smoke script |
SSE fixture record + replay
Record a live SSE stream to a fixture file:
npm run record:sse -- --session <SESSION_ID>
Defaults:
- base URL:
http://127.0.0.1:${PORT} - output:
public/fixtures/sse-<SESSION_ID>-<timestamp>.json
Optional flags:
--base <url>--out <absolute-or-relative-path>--max-events <number>--duration-ms <number>
Replay a fixture in the browser overlay by adding a query param:
http://localhost:3000/?replayFixture=/fixtures/<fixture-file>.json
When fixture replay mode is enabled, live SSE is disabled and recorded events are replayed with their captured offsets.
Server-side NDJSON recording and replay (Phase 7)
During normal live runs, session events are recorded to NDJSON files in recordings/<SESSION_ID>.ndjson (override with RECORDINGS_DIR).
Start server replay mode from an existing NDJSON file:
npm run dev -- --replay recordings/<SESSION_ID>.ndjson --speed 4
Equivalent environment-variable mode:
REPLAY_FILE=recordings/<SESSION_ID>.ndjson REPLAY_SPEED=4 npm run dev
Notes:
--speed/REPLAY_SPEEDcontrols playback rate (1= real-time,4= 4× faster).- In replay mode, orchestration is disabled and SSE emits recorded events with captured inter-event timing.
- Existing viewer and operator UIs connect to replay mode using the same SSE endpoint.
Make targets
Makefile mirrors common workflows (make dev, make test, make ci, make docker-up, etc.).
Local CI Parity
Run before opening a PR:
npm run lint
npm run build
npm test
Repository Layout
src/— server, orchestrator, store, moderation, broadcast, Twitch adapter, testspublic/— viewer UI + overlaypublic/renderer/— modular PixiJS renderer (stage, layers, camera, dialogue, effects)public/assets/— placeholder-first assets (backgrounds, characters, UI, fonts, SFX)dashboard/— operator dashboard (React + Vite)db/migrations/— SQL schema migrationsdocs/— architecture, API, moderation, ops runbooksops/— alert thresholds + runtime health dashboard artifacts
Documentation Map
| Document | Description |
|---|---|
docs/ADR-001-juryrigged-architecture.md |
Core architectural decisions and invariants |
docs/architecture.md |
System components and phase sequencing |
docs/api.md |
REST + SSE contracts and schemas |
docs/coding-conventions.md |
Team coding style and maintainability conventions |
docs/operator-runbook.md |
Operator procedures and incident response |
docs/ops-runbook.md |
Staging deploy path, SLI/alert definitions |
docs/moderation-playbook.md |
Moderation policy and handling |
docs/event-taxonomy.md |
Event taxonomy and payload expectations |
docs/broadcast-integration.md |
OBS/broadcast automation + Twitch integration |
docs/phase5-6-implementation-plan.md |
Roadmap implementation plan |
Notes
- Migrations run automatically when using Postgres-backed storage.
- When
DATABASE_URLis not set, sessions are non-durable and not recoverable after restart. - On restart with Postgres, interrupted
runningsessions are recovered and resumed.