prepare for review and deploy
This commit is contained in:
@@ -3,7 +3,7 @@ LLM_MODEL=deepseek/deepseek-chat-v3-0324:free
|
||||
LLM_MOCK=false
|
||||
PORT=3000
|
||||
API_HOST_PORT=3000
|
||||
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/improv_court
|
||||
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/juryrigged
|
||||
TTS_PROVIDER=noop
|
||||
LOG_LEVEL=info
|
||||
VERDICT_VOTE_WINDOW_MS=20000
|
||||
|
||||
8
IDEA.md
8
IDEA.md
@@ -1,4 +1,4 @@
|
||||
# Improv Court ⚖️🎭
|
||||
# JuryRigged ⚖️🎭
|
||||
|
||||
*Roleplay comedy + audience verdict game show format*
|
||||
|
||||
@@ -213,9 +213,9 @@ Designed to be naturally robust with stream delay:
|
||||
|
||||
### Architecture Decision Record
|
||||
|
||||
See [docs/ADR-001-improv-court-architecture.md](docs/ADR-001-improv-court-architecture.md) for runtime boundaries, module ownership, phase-state contracts, and API/SSE/persistence contracts.
|
||||
See [docs/ADR-001-juryrigged-architecture.md](docs/ADR-001-juryrigged-architecture.md) for runtime boundaries, module ownership, phase-state contracts, and API/SSE/persistence contracts.
|
||||
|
||||
### Phase 1 — Improv Court = “Orchestration/Overlay Engine Test”
|
||||
### Phase 1 — JuryRigged = “Orchestration/Overlay Engine Test”
|
||||
|
||||
Proves:
|
||||
|
||||
@@ -285,6 +285,6 @@ Uses everything:
|
||||
|
||||
### Relative runtime cost (low → high)
|
||||
|
||||
Improv Court (medium) < Cipher (medium) < Writers’ Room (medium-high) ≈ Ghost (medium-high)
|
||||
JuryRigged (medium) < Cipher (medium) < Writers’ Room (medium-high) ≈ Ghost (medium-high)
|
||||
|
||||
---
|
||||
|
||||
2
Makefile
2
Makefile
@@ -13,7 +13,7 @@ DOCKER_COMPOSE ?= docker compose
|
||||
.PHONY: help install dev dev-dashboard lint build build-dashboard test test-spec ci smoke-staging start migrate migrate-dist docker-up docker-down docker-restart clean status
|
||||
|
||||
help: ## Show available commands
|
||||
@awk 'BEGIN {FS = ":.*##"; printf "\nImprov Court Make targets:\n\n"} /^[a-zA-Z0-9_.-]+:.*##/ { printf " %-18s %s\n", $$1, $$2 } END { printf "\n" }' $(MAKEFILE_LIST)
|
||||
@awk 'BEGIN {FS = ":.*##"; printf "\nJuryRigged Make targets:\n\n"} /^[a-zA-Z0-9_.-]+:.*##/ { printf " %-18s %s\n", $$1, $$2 } END { printf "\n" }' $(MAKEFILE_LIST)
|
||||
|
||||
install: ## Install Node dependencies
|
||||
$(NPM) install
|
||||
|
||||
287
README.md
287
README.md
@@ -1,137 +1,162 @@
|
||||
# Improv Court POC (standalone)
|
||||
# JuryRigged
|
||||
|
||||
[](https://github.com/subculture-collective/court/actions/workflows/ci.yml)
|
||||
|
||||
This is a **standalone root-level implementation** of the Improv Court proof of concept.
|
||||
It does **not** depend on `subcult-corp` at runtime.
|
||||
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.
|
||||
|
||||
## Documentation
|
||||
This repository is standalone and does not require `subcult-corp` at runtime.
|
||||
|
||||
| Document | Description |
|
||||
| -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| [docs/ADR-001-improv-court-architecture.md](docs/ADR-001-improv-court-architecture.md) | Architecture Decision Record: runtime boundaries, data contracts, and phase invariants |
|
||||
| [docs/architecture.md](docs/architecture.md) | System architecture, agent roles, and phase flow |
|
||||
| [docs/api.md](docs/api.md) | REST API endpoints, schemas, and SSE event contracts |
|
||||
| [docs/operator-runbook.md](docs/operator-runbook.md) | Setup, live controls, incident response, mistrial fallback, and operational monitoring |
|
||||
| [docs/moderation-playbook.md](docs/moderation-playbook.md) | Content moderation system and incident procedures |
|
||||
| [docs/event-taxonomy.md](docs/event-taxonomy.md) | Canonical event taxonomy, payload schemas, and logging guidelines |
|
||||
| [docs/phase5-6-implementation-plan.md](docs/phase5-6-implementation-plan.md) | Dependency-ordered implementation plan for roadmap phases 5 and 6 |
|
||||
## Highlights
|
||||
|
||||
## What is implemented
|
||||
- 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 (`noop` or `obs`) for production workflows
|
||||
|
||||
- Multi-agent courtroom roles (judge, prosecutor, defense, witnesses, bailiff)
|
||||
- Phase-based court flow:
|
||||
- `case_prompt`
|
||||
- `openings`
|
||||
- `witness_exam`
|
||||
- `closings`
|
||||
- `verdict_vote`
|
||||
- `sentence_vote`
|
||||
- `final_ruling`
|
||||
- Live SSE stream per session
|
||||
- Jury verdict and sentence voting endpoints
|
||||
- Deterministic phase-order and vote-window enforcement
|
||||
- Minimal stripped web UI (`public/index.html`)
|
||||
- Overlay shell with phase timer, active speaker, and live captions
|
||||
- Viewer layout showing current phase context and jury voting status
|
||||
- Verdict/sentence poll bars with live percentages and phase-gated voting
|
||||
- SSE analytics events for poll start/close and vote completion
|
||||
- **Operator Dashboard** (`/operator`)
|
||||
- Real-time session monitoring with live event feed
|
||||
- Vote tallies and witness cap tracking
|
||||
- Moderation queue for content review
|
||||
- Manual controls for session management
|
||||
- Analytics dashboard with event timelines
|
||||
- **Structured Logging Service**
|
||||
- JSON-formatted logs with session/phase/event correlation
|
||||
- Configurable log levels (debug/info/warn/error)
|
||||
- Child loggers with inherited context
|
||||
- Production-ready logging architecture
|
||||
## Tech Stack
|
||||
|
||||
## Environment
|
||||
- Node.js + TypeScript
|
||||
- Express (API + static serving)
|
||||
- React + Vite (operator dashboard)
|
||||
- Postgres (optional durable store)
|
||||
|
||||
Copy `.env.example` to `.env` and set values as needed.
|
||||
## Quick Start (Local)
|
||||
|
||||
Key variables:
|
||||
### 1) Install dependencies
|
||||
|
||||
- `OPENROUTER_API_KEY` (optional for local mock mode; required for real LLM calls)
|
||||
- `LLM_MODEL`
|
||||
- `LLM_MOCK` (set to `true` to force deterministic mock responses)
|
||||
- `PORT`
|
||||
- `DATABASE_URL` (Postgres connection string for durable persistence)
|
||||
- `TTS_PROVIDER` (`noop` or `mock`; defaults to `noop`)
|
||||
- `VERDICT_VOTE_WINDOW_MS`
|
||||
- `SENTENCE_VOTE_WINDOW_MS`
|
||||
- `WITNESS_MAX_TOKENS`
|
||||
- `WITNESS_MAX_SECONDS`
|
||||
- `WITNESS_TOKENS_PER_SECOND`
|
||||
- `WITNESS_TRUNCATION_MARKER`
|
||||
- `JUDGE_RECAP_CADENCE`
|
||||
- `LOG_LEVEL` (debug, info, warn, error; defaults to `info`)
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
If `OPENROUTER_API_KEY` is empty, the app falls back to deterministic mock dialogue.
|
||||
### 2) Configure environment
|
||||
|
||||
If `DATABASE_URL` is set, the app uses Postgres-backed persistence and runs migrations at startup.
|
||||
If `DATABASE_URL` is missing, the app falls back to in-memory storage (non-durable).
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
`TTS_PROVIDER=noop` keeps TTS silent (default). `TTS_PROVIDER=mock` records adapter calls for local/testing workflows without requiring an external speech provider.
|
||||
For a zero-dependency local run:
|
||||
|
||||
Witness response caps are controlled by `WITNESS_MAX_TOKENS` and `WITNESS_MAX_SECONDS`. The recap cadence uses `JUDGE_RECAP_CADENCE` (every N witness cycles). `WITNESS_TRUNCATION_MARKER` customizes the appended cutoff text.
|
||||
- leave `OPENROUTER_API_KEY` empty (mock dialogue fallback)
|
||||
- leave `DATABASE_URL` empty (in-memory session store)
|
||||
|
||||
## Run
|
||||
### 3) Start the API server
|
||||
|
||||
1. Install dependencies:
|
||||
- `npm install`
|
||||
2. (Optional but recommended) run DB migrations explicitly:
|
||||
- `npm run migrate`
|
||||
3. Start dev server:
|
||||
- `npm run dev`
|
||||
4. Build operator dashboard:
|
||||
- `npm run build:dashboard` (production build)
|
||||
- `npm run dev:dashboard` (development mode on port 3001)
|
||||
5. Open:
|
||||
- Main app: `http://localhost:3000`
|
||||
- Operator dashboard: `http://localhost:3000/operator`
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Run with Docker (API + Postgres)
|
||||
Default local URL: `http://localhost:3000` (from `.env.example`).
|
||||
|
||||
This repo includes a `docker-compose.yml` that starts both:
|
||||
### 4) Build operator dashboard assets
|
||||
|
||||
- `api` (the Improv Court server)
|
||||
```bash
|
||||
npm run build:dashboard
|
||||
```
|
||||
|
||||
Then open:
|
||||
|
||||
- Main app: `http://localhost:3000`
|
||||
- Operator dashboard: `http://localhost:3000/operator`
|
||||
|
||||
> The API serves `/operator` from `dist/dashboard`. If you haven’t built it yet, `/operator` will return a helpful 404 message.
|
||||
|
||||
## Dashboard Dev Mode (Hot Reload)
|
||||
|
||||
Run the dashboard separately while API dev server is running:
|
||||
|
||||
```bash
|
||||
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 the full stack:
|
||||
Start:
|
||||
|
||||
- `npm run docker:up`
|
||||
```bash
|
||||
npm run docker:up
|
||||
```
|
||||
|
||||
Or directly:
|
||||
Stop:
|
||||
|
||||
- `docker compose up --build`
|
||||
```bash
|
||||
npm run docker:down
|
||||
```
|
||||
|
||||
Stop the stack:
|
||||
Container behavior:
|
||||
|
||||
- `npm run docker:down`
|
||||
- API runs on container port `3001`
|
||||
- Host mapping defaults to `${API_HOST_PORT:-3001}`
|
||||
- Migrations run automatically on container startup (`npm run migrate:dist`)
|
||||
|
||||
The API container runs migrations on startup (`npm run migrate:dist`) before starting the server.
|
||||
Default compose endpoints:
|
||||
|
||||
Endpoints when running with compose:
|
||||
- App + API: `http://localhost:${API_HOST_PORT:-3001}`
|
||||
- Operator dashboard: `http://localhost:${API_HOST_PORT:-3001}/operator`
|
||||
|
||||
- Main app: `http://localhost:${API_HOST_PORT:-3000}`
|
||||
- Operator dashboard: `http://localhost:${API_HOST_PORT:-3000}/operator`
|
||||
- API: `http://localhost:${API_HOST_PORT:-3000}/api`
|
||||
- Postgres: internal-only by default (`db:5432` inside compose network)
|
||||
## Configuration
|
||||
|
||||
If port `3000` is already in use on your machine, set `API_HOST_PORT` in `.env` (for example `API_HOST_PORT=3002`) and restart compose.
|
||||
Copy `.env.example` and tune as needed.
|
||||
|
||||
If you need host access to Postgres, add a `ports` mapping to the `db` service in `docker-compose.yml` (for example `"5433:5432"` to avoid conflicts with local Postgres).
|
||||
### Core runtime
|
||||
|
||||
## Operations runbook (staging)
|
||||
| Variable | Purpose |
|
||||
| --- | --- |
|
||||
| `PORT` | API port for local non-Docker runs (default in `.env.example`: `3000`) |
|
||||
| `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` |
|
||||
|
||||
See `docs/ops-runbook.md` for the repeatable staging deploy path, GitHub Actions
|
||||
workflow (`Staging Deploy`), core SLI dashboard definitions, alert thresholds,
|
||||
and incident drill/recovery steps. Ops-related configuration is validated as part of the standard test suite (see `npm test` under "Local CI parity" below).
|
||||
### Voting + moderation safety
|
||||
|
||||
## API
|
||||
| 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.
|
||||
|
||||
## API at a Glance
|
||||
|
||||
- `GET /api/health`
|
||||
- `GET /api/court/sessions`
|
||||
@@ -141,18 +166,64 @@ and incident drill/recovery steps. Ops-related configuration is validated as par
|
||||
- `POST /api/court/sessions/:id/phase`
|
||||
- `GET /api/court/sessions/:id/stream` (SSE)
|
||||
|
||||
## Local CI parity
|
||||
Full schemas, error codes, and event contracts: `docs/api.md`.
|
||||
|
||||
Run the same checks as CI locally before pushing:
|
||||
## Development Commands
|
||||
|
||||
```sh
|
||||
npm run lint # type-check (tsc --noEmit)
|
||||
npm run build # compile TypeScript to dist/
|
||||
npm test # run all tests
|
||||
### 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 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 |
|
||||
|
||||
### Make targets
|
||||
|
||||
`Makefile` mirrors common workflows (`make dev`, `make test`, `make ci`, `make docker-up`, etc.).
|
||||
|
||||
## Local CI Parity
|
||||
|
||||
Run before opening a PR:
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
npm run build
|
||||
npm test
|
||||
```
|
||||
|
||||
## Repository Layout
|
||||
|
||||
- `src/` — server, orchestrator, store, moderation, broadcast, tests
|
||||
- `public/` — viewer UI
|
||||
- `dashboard/` — operator dashboard (React + Vite)
|
||||
- `db/migrations/` — SQL schema migrations
|
||||
- `docs/` — architecture, API, moderation, ops runbooks
|
||||
- `ops/` — 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/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 configuration |
|
||||
| `docs/phase5-6-implementation-plan.md` | Roadmap implementation plan |
|
||||
|
||||
## Notes
|
||||
|
||||
- The existing `subcult-corp` directory is used only as a **reference source** and is not imported.
|
||||
- Core reusable ideas were copied into `src/` as standalone modules.
|
||||
- Schema migration SQL is under `db/migrations/`.
|
||||
- Migrations run automatically when using Postgres-backed storage.
|
||||
- When `DATABASE_URL` is not set, sessions are non-durable and not recoverable after restart.
|
||||
- On restart with Postgres, interrupted `running` sessions are recovered and resumed.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Improv Court - Operator Dashboard</title>
|
||||
<title>JuryRigged - Operator Dashboard</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -56,7 +56,7 @@ function App() {
|
||||
<div className='flex items-center justify-between'>
|
||||
<div>
|
||||
<h1 className='text-2xl font-bold text-primary-400'>
|
||||
Improv Court
|
||||
JuryRigged
|
||||
</h1>
|
||||
<p className='text-sm text-gray-400'>
|
||||
Operator Dashboard
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
services:
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
container_name: improv-court-db
|
||||
container_name: juryrigged-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: improv_court
|
||||
POSTGRES_DB: juryrigged
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'pg_isready -U postgres -d improv_court']
|
||||
test: ['CMD-SHELL', 'pg_isready -U postgres -d juryrigged']
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
@@ -19,14 +19,14 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: improv-court-api
|
||||
container_name: juryrigged-api
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
PORT: 3001
|
||||
DATABASE_URL: postgresql://postgres:postgres@db:5432/improv_court
|
||||
DATABASE_URL: postgresql://postgres:postgres@db:5432/juryrigged
|
||||
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-}
|
||||
LLM_MODEL: ${LLM_MODEL:-deepseek/deepseek-chat-v3-0324:free}
|
||||
LLM_MOCK: ${LLM_MOCK:-false}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# ADR-001 — Improv Court Runtime Architecture
|
||||
# ADR-001 — JuryRigged Runtime Architecture
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-02-27
|
||||
**Author:** @copilot
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-02-27
|
||||
**Author:** @copilot
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Improv Court is a real-time multi-agent courtroom simulation.
|
||||
JuryRigged is a real-time multi-agent courtroom simulation.
|
||||
The runtime now includes an HTTP/SSE API layer, a phase-sequencing orchestrator, an LLM client, a content-moderation filter, a vote-spam guard, and a dual-backend session store.
|
||||
Boundary ownership between these modules needs a durable reference so that future contributors can understand which module is responsible for which concern and what contracts must be honoured across module boundaries.
|
||||
|
||||
@@ -16,7 +16,7 @@ Returns service liveness.
|
||||
**Response `200`**
|
||||
|
||||
```json
|
||||
{ "ok": true, "service": "improv-court-poc" }
|
||||
{ "ok": true, "service": "juryrigged" }
|
||||
```
|
||||
|
||||
---
|
||||
@@ -218,7 +218,7 @@ data: {"type":"turn","payload":{…}}\n\n
|
||||
|
||||
```ts
|
||||
{
|
||||
mode: "improv_court";
|
||||
mode: "juryrigged";
|
||||
casePrompt: string;
|
||||
caseType: "criminal" | "civil";
|
||||
sentenceOptions: string[];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Architecture Overview
|
||||
|
||||
Improv Court is a real-time multi-agent courtroom simulation.
|
||||
JuryRigged is a real-time multi-agent courtroom simulation.
|
||||
Six AI agents are assigned courtroom roles, run through a deterministic phase sequence, and stream every turn to connected viewers via SSE.
|
||||
Audience members serve as jury by voting through REST endpoints.
|
||||
|
||||
@@ -93,7 +93,7 @@ The store has two backends selected automatically:
|
||||
| `DATABASE_URL` is set | Postgres (durable) |
|
||||
| `DATABASE_URL` absent | In-memory (volatile)|
|
||||
|
||||
The Postgres schema is under `db/migrations/001_improv_court_core.sql`.
|
||||
The Postgres schema is under `db/migrations/001_juryrigged_core.sql`.
|
||||
Migrations are run automatically on startup when the Postgres backend is active.
|
||||
|
||||
The store exposes an event bus (`EventEmitter`) that routes session events to SSE subscribers.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Event Taxonomy
|
||||
|
||||
Canonical reference for all runtime events emitted by the Improv Court system.
|
||||
Canonical reference for all runtime events emitted by the JuryRigged system.
|
||||
Every event is represented as a [`CourtEvent`](../src/types.ts) and delivered
|
||||
over the Server-Sent Events stream (`GET /api/court/sessions/:id/stream`) and
|
||||
to any in-process `store.subscribe()` listener.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Moderation Playbook
|
||||
|
||||
This document describes the content moderation system and procedures for responding to incidents during live Improv Court sessions.
|
||||
This document describes the content moderation system and procedures for responding to incidents during live JuryRigged sessions.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Improv Court uses a layered moderation approach:
|
||||
JuryRigged uses a layered moderation approach:
|
||||
|
||||
1. **Curated + screened inputs** — case prompts (operator or API-supplied) are screened with the moderation filter; unsafe topics are rejected and unsafe prompt-bank entries are skipped.
|
||||
2. **LLM system prompt policy** — every agent prompt includes the Clean Courtroom Policy (see below).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Operator Runbook
|
||||
|
||||
This document covers everything needed to deploy, configure, and operate Improv Court.
|
||||
This document covers everything needed to deploy, configure, and operate JuryRigged.
|
||||
|
||||
---
|
||||
|
||||
@@ -65,7 +65,7 @@ Open `http://localhost:3001` in a browser.
|
||||
|
||||
The compose file starts two services:
|
||||
|
||||
- `api` — the Improv Court server (builds from `Dockerfile`)
|
||||
- `api` — the JuryRigged server (builds from `Dockerfile`)
|
||||
- `db` — Postgres 16
|
||||
|
||||
```bash
|
||||
@@ -218,7 +218,7 @@ There is no built-in metrics endpoint. For production observability, pipe logs t
|
||||
|
||||
### Schema
|
||||
|
||||
The schema is defined in `db/migrations/001_improv_court_core.sql`.
|
||||
The schema is defined in `db/migrations/001_juryrigged_core.sql`.
|
||||
The migration system tracks applied migrations in `court_schema_migrations`.
|
||||
|
||||
### Cleaning up old sessions
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Improv Court staging deploy + ops runbook
|
||||
# JuryRigged staging deploy + ops runbook
|
||||
|
||||
## 1) Repeatable staging deployment (Docker-first)
|
||||
|
||||
@@ -12,7 +12,7 @@ Run from the project root directory:
|
||||
- `npm run docker:up`
|
||||
3. Verify runtime health:
|
||||
- `curl -fsS http://localhost:${API_HOST_PORT:-3001}/api/health`
|
||||
- Expected response: `{"ok":true,"service":"improv-court-poc"}`
|
||||
- Expected response: `{"ok":true,"service":"juryrigged"}`
|
||||
4. Optional migration-only verification:
|
||||
- `docker compose exec api npm run migrate:dist`
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
## Overview
|
||||
|
||||
The Improv Court prompt bank provides a curated collection of case prompts organized by genre tags. The system automatically rotates through genres to avoid repetition and maintain audience engagement.
|
||||
The JuryRigged prompt bank provides a curated collection of case prompts organized by genre tags. The system automatically rotates through genres to avoid repetition and maintain audience engagement.
|
||||
|
||||
## Genre Taxonomy
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"dashboardId": "improv-court-runtime-health",
|
||||
"title": "Improv Court Runtime Health",
|
||||
"dashboardId": "juryrigged-runtime-health",
|
||||
"title": "JuryRigged Runtime Health",
|
||||
"description": "Core SLI/SLO proxy panels for Phase 5 operations readiness.",
|
||||
"datasources": {
|
||||
"postgres": "court",
|
||||
|
||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "improv-court-poc",
|
||||
"name": "juryrigged",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "improv-court-poc",
|
||||
"name": "juryrigged",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"dotenv": "^17.2.3",
|
||||
@@ -72,6 +72,7 @@
|
||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
@@ -1410,6 +1411,7 @@
|
||||
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.2.2"
|
||||
@@ -1646,6 +1648,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -2424,6 +2427,7 @@
|
||||
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
@@ -2784,6 +2788,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -3018,6 +3023,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
@@ -3507,6 +3513,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -3549,6 +3556,7 @@
|
||||
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "~0.27.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
@@ -3668,6 +3676,7 @@
|
||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
@@ -4245,6 +4254,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "improv-court-poc",
|
||||
"name": "juryrigged",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Improv Court POC</title>
|
||||
<title>JuryRigged</title>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
@@ -342,7 +342,7 @@
|
||||
<body>
|
||||
<div class="layout">
|
||||
<section class="panel">
|
||||
<h1>⚖️ Improv Court POC</h1>
|
||||
<h1>⚖️ JuryRigged</h1>
|
||||
<p class="muted">
|
||||
Text-first courtroom flow with multi-agent roles, live
|
||||
transcript, and jury voting.
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('Court State Machine - Phase Transitions', () => {
|
||||
topic: 'Test case for phase transitions',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt: 'Test case for phase transitions',
|
||||
caseType: 'criminal',
|
||||
roleAssignments: {
|
||||
@@ -91,7 +91,7 @@ describe('Court State Machine - Phase Transitions', () => {
|
||||
topic: 'Test case for skipping evidence reveal',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt: 'Test case for phase transitions',
|
||||
caseType: 'criminal',
|
||||
roleAssignments: {
|
||||
@@ -133,7 +133,7 @@ describe('Court State Machine - Phase Transitions', () => {
|
||||
topic: 'Test case for invalid jumps',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt: 'Test case for invalid jumps',
|
||||
caseType: 'criminal',
|
||||
roleAssignments: {
|
||||
@@ -181,7 +181,7 @@ describe('Court State Machine - Phase Transitions', () => {
|
||||
topic: 'Test case for no-op transitions',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt: 'Test case for no-op transitions',
|
||||
caseType: 'civil',
|
||||
roleAssignments: {
|
||||
@@ -215,7 +215,7 @@ describe('Court State Machine - Phase Transitions', () => {
|
||||
topic: 'Test case for phase events',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt: 'Test case for phase events',
|
||||
caseType: 'criminal',
|
||||
roleAssignments: {
|
||||
@@ -310,7 +310,7 @@ describe('Court State Machine - Phase Transitions', () => {
|
||||
topic: 'Test case for backward jump prevention',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt: 'Test case for backward jump prevention',
|
||||
caseType: 'criminal',
|
||||
roleAssignments: {
|
||||
@@ -359,7 +359,7 @@ describe('Court State Machine - Phase Transitions', () => {
|
||||
topic: 'Test case for poll events',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt: 'Test case for poll events',
|
||||
caseType: 'criminal',
|
||||
roleAssignments: {
|
||||
@@ -410,7 +410,7 @@ describe('Court Orchestrator - TTS integration', () => {
|
||||
topic: 'Did the defendant replace office coffee with soup?',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt:
|
||||
'Did the defendant replace office coffee with soup?',
|
||||
caseType: 'criminal',
|
||||
@@ -447,7 +447,7 @@ describe('Court Orchestrator - TTS integration', () => {
|
||||
topic: 'Did the defendant install a trampoline in the jury box?',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt:
|
||||
'Did the defendant install a trampoline in the jury box?',
|
||||
caseType: 'criminal',
|
||||
|
||||
@@ -353,7 +353,7 @@ export async function runCourtSession(
|
||||
}),
|
||||
);
|
||||
|
||||
const allRiseCue = `All rise. The Court of Improvised Absurdity is now in session. Case: ${session.topic}`;
|
||||
const allRiseCue = `All rise. The JuryRigged court is now in session. Case: ${session.topic}`;
|
||||
await safelySpeak('speakCue', () =>
|
||||
tts.speakCue({
|
||||
sessionId: session.id,
|
||||
|
||||
@@ -103,7 +103,7 @@ describe('Vote Lifecycle Integration', () => {
|
||||
topic: 'Test case for vote phase gating',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt: 'Test case',
|
||||
caseType: 'criminal',
|
||||
roleAssignments: {
|
||||
@@ -162,7 +162,7 @@ describe('Vote Lifecycle Integration', () => {
|
||||
topic: 'Test case for vote accumulation',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt: 'Test case',
|
||||
caseType: 'criminal',
|
||||
roleAssignments: {
|
||||
@@ -223,7 +223,7 @@ describe('Vote Lifecycle Integration', () => {
|
||||
topic: 'Test case for vote events',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt: 'Test case',
|
||||
caseType: 'criminal',
|
||||
roleAssignments: {
|
||||
@@ -275,7 +275,7 @@ describe('Vote Lifecycle Integration', () => {
|
||||
topic: 'Test case for invalid choices',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt: 'Test case',
|
||||
caseType: 'criminal',
|
||||
roleAssignments: {
|
||||
@@ -321,7 +321,7 @@ describe('Vote Lifecycle Integration', () => {
|
||||
topic: 'Test case for invalid sentence',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt: 'Test case',
|
||||
caseType: 'criminal',
|
||||
roleAssignments: {
|
||||
@@ -368,7 +368,7 @@ describe('Vote Lifecycle Integration', () => {
|
||||
topic: 'Test case for sentence voting',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt: 'Test case',
|
||||
caseType: 'criminal',
|
||||
roleAssignments: {
|
||||
|
||||
@@ -47,7 +47,7 @@ test('e2e round completes with witness caps and recap cadence', async () => {
|
||||
topic: 'Did the defendant replace all office coffee with soup?',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt:
|
||||
'Did the defendant replace all office coffee with soup?',
|
||||
caseType: 'criminal',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Structured logging service for Improv Court
|
||||
* Structured logging service for JuryRigged
|
||||
* Provides JSON-formatted logs with session/phase/event correlation
|
||||
*/
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ export async function createServerApp(
|
||||
app.use(express.static(publicDir));
|
||||
|
||||
app.get('/api/health', (_req, res) => {
|
||||
res.json({ ok: true, service: 'improv-court-poc' });
|
||||
res.json({ ok: true, service: 'juryrigged' });
|
||||
});
|
||||
|
||||
app.get('/api/court/sessions', async (_req, res) => {
|
||||
@@ -242,7 +242,7 @@ export async function createServerApp(
|
||||
topic,
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt: topic,
|
||||
caseType,
|
||||
sentenceOptions,
|
||||
@@ -478,7 +478,7 @@ export async function bootstrap(): Promise<void> {
|
||||
const port = Number.parseInt(process.env.PORT ?? '3000', 10);
|
||||
app.listen(port, () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Improv Court POC running on http://localhost:${port}`);
|
||||
console.log(`JuryRigged running on http://localhost:${port}`);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Operator Dashboard: http://localhost:${port}/operator`);
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ async function createRunningSession() {
|
||||
topic: 'Did the defendant replace all office coffee with soup?',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt:
|
||||
'Did the defendant replace all office coffee with soup?',
|
||||
caseType: 'criminal',
|
||||
@@ -273,7 +273,7 @@ test(
|
||||
topic: 'Did the defendant replace all office coffee with soup?',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt:
|
||||
'Did the defendant replace all office coffee with soup?',
|
||||
caseType: 'criminal',
|
||||
@@ -326,7 +326,7 @@ test(
|
||||
topic: 'Test case 1 for recovery',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt: 'Test case 1',
|
||||
caseType: 'criminal',
|
||||
sentenceOptions: ['Fine'],
|
||||
@@ -343,7 +343,7 @@ test(
|
||||
topic: 'Test case 2 for recovery',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt: 'Test case 2',
|
||||
caseType: 'criminal',
|
||||
sentenceOptions: ['Fine'],
|
||||
@@ -361,7 +361,7 @@ test(
|
||||
topic: 'Test case 3 for recovery',
|
||||
participants,
|
||||
metadata: {
|
||||
mode: 'improv_court',
|
||||
mode: 'juryrigged',
|
||||
casePrompt: 'Test case 3',
|
||||
caseType: 'criminal',
|
||||
sentenceOptions: ['Fine'],
|
||||
|
||||
@@ -82,7 +82,7 @@ export interface CourtTurn {
|
||||
}
|
||||
|
||||
export interface CourtSessionMetadata {
|
||||
mode: 'improv_court';
|
||||
mode: 'juryrigged';
|
||||
casePrompt: string;
|
||||
caseType: CaseType;
|
||||
sentenceOptions: string[];
|
||||
|
||||
Reference in New Issue
Block a user