Skip to main content

The programme runtime for creative work. Theatrical orchestration with red-line approval gates and async cancellation across signed-link channels.

Project description

The Show

A framework for running agents unattended — scenes, fallbacks, adaptive variations, urgent contact escalation, and a programme at the end.

Status: v1.1.0 — production-ready, on PyPI, used in real operations at Short+Sweet International. Open source, runtime free.


What it is

The Show is an orchestration runtime for creative work. It uses a theatrical metaphor — programmes, scenes, Stage Manager, Urgent Contact — because the metaphor maps onto how creative operators already think. You write a programme. The Stage Manager runs it, persisting state after every scene. When something needs a human decision, the Urgent Contact system sends a signed, authenticated request across Telegram, email, SMS or WhatsApp. You respond. The show continues.

The specific differentiator: async cancellation mid-LLM-call across four signed-link channels with typed response schemas and per-channel authentication. When the first valid response comes in, outstanding sends are cancelled. That combination doesn't exist elsewhere as a coherent system.

The v1.0 release was built by running The Show on itself — examples/build-v1.0-release.yaml is the programme that built v1.0. It's in the repo.


Here's what a programme looks like — three scenes from the Curiosity Cat launch announcement that ran on 23 April 2026:

running-order:

  - scene: draft_english
    title: "Draft English announcement"
    outputs:
      post: {type: object}
    principal:
      method: sub-agent
      agent: gemini
      brief: "Draft a 280-char social post for the Curiosity Cat launch."
      params: {model: gemini-flash}
    cut: {condition: escalate, reason: "Cannot proceed without English draft"}

  - scene: draft_arabic
    title: "Draft Arabic announcement"
    depends-on: [draft_english]
    inputs:
      english_post: from(draft_english.post)
    outputs:
      post: {type: object}
    principal:
      method: sub-agent
      agent: gemini
      brief: "Translate the English post to natural Arabic for the UAE market."
      params: {model: gemini-flash}
    cut: {condition: escalate}

  - scene: approve_drafts
    title: "Operator approval"
    depends-on: [draft_english, draft_arabic]
    outputs:
      decision: {type: string}
    principal:
      method: human-approval
      brief: "Reply APPROVE to publish, REJECT to abort."
    cut: {condition: escalate}

The full programme is at examples/curiosity-cat-launch-announcement.yaml. The operator guide explains the metaphor and the mechanics in detail — see docs/OPERATOR_GUIDE.md.


Current state

Version: v1.0.0 — 231 passing tests

What works

  • SQLite state (~/.the-show/state/<show-id>.db, WAL mode)
  • Crash recovery — interrupted shows prompt for resume on next run
  • Full scene state vocabulary including cascading-dependency-failure and played-fallback-N
  • Per-strategy success-when overrides (falls back to scene-level)
  • Basic schema validation in meets_success (list / dict / string / number)
  • Markdown-fence sanitisation on untrusted output (sanitise.py)
  • Field-validator hook — logs INFO, skips (real validators are a later session)
  • Idempotency key generation for side-effectful strategies (logged to event DB)
  • Urgent Contact — real dispatcher replacing the Session 2 stub:
    • Three auth methods: channel-native, reply-token (6-digit), signed-link (HMAC-SHA256)
    • Strict response parsing: APPROVE / REJECT / STOP / CONTINUE only; invalid format sends a correction prompt and keeps polling
    • Sequential and parallel dispatch modes; critical severity forces parallel
    • Cancellation of pending sends on first valid response
    • Exhaustion path (blocked-no-response) when all contacts fail to reply
    • DAG pruning: exhausted/blocked scene cascades cascading-dependency-failure to all transitive dependents
    • Frequency throttle: default 3 unplanned matters per show; human-approval scenes always exempt; critical always bypasses
    • Mock channel for testing (file-drop at ~/.the-show/urgent-mock/)
  • Real channel adapters (Session 4):
    • Telegram — dedicated urgent-contact bot, polling-based, channel-native auth
    • Email — SMTP send + signed action links, HMAC-SHA256 tokens, 24h expiry
    • WhatsApp — skeleton with setup checklist; send() raises NotImplementedError until Meta onboarding complete
    • SMS — Twilio, reply-token auth
    • Signed-link server — Flask app on port 5099; handles email link clicks + WhatsApp / Twilio webhooks
  • load_adapters() in dispatcher.py — registers only configured adapters; warns if credentials missing; mock always available
  • Programme reads from SQLite event log
  • tests/ — 146 passing pytest tests

Known stubs (addressed in later sessions)

  • WhatsApp send() raises NotImplementedError until Mark completes Meta Business API onboarding
  • Link server is localhost-only — needs reverse proxy or Cloudflare Tunnel for WhatsApp/Twilio webhooks
  • Execution Monitor — not running (Session 5)
  • STOP keyword — parsed and returned but does not yet abort the whole show (Session 5)
  • Field-level validators — hook exists, no real validators (later)
  • Basic schema validation only — no JSON Schema deep-validation (later)

Install

# From PyPI
pip install the-show

# Or from source
git clone https://github.com/markscleary/the-show.git
cd the-show
pip install -e ".[dev]"

See docs/QUICKSTART.md for a five-minute walkthrough.


How to run

# Validate a show file
the-show validate example_show.yaml

# Run a show (with crash-resume)
the-show run example_show.yaml

# Inspect current state
the-show peek outreach-enrichment-001

# Regenerate programme from saved state
the-show programme outreach-enrichment-001

# Print event log
the-show events outreach-enrichment-001 [--since=<ISO>] [--limit=N]

# Run tests (from a source checkout)
pytest tests/

State DB: ~/.the-show/state/<show-id>.db Programme output: ~/.the-show/state/<show-id>/programme.md and programme.json

Testing Urgent Contact end-to-end

Run a show that contains a human-approval scene. When the dispatcher raises a matter via the mock channel, drop a JSON response file to resolve it:

# 1. Start the show — it will block on the human-approval scene
uv run python cli.py run example_show.yaml

# 2. In another terminal, find the matter ID from the event log
uv run python cli.py events outreach-enrichment-001 --limit=5

# 3. Drop a response for that matter ID (replace <matter-id> with the real value)
mkdir -p ~/.the-show/mock_responses
echo '{"keyword": "APPROVE", "auth_token": null}' \
  > ~/.the-show/mock_responses/<matter-id>.json

# 4. The dispatcher will pick it up within the next poll interval (default 5 s)
#    and unblock the show.

The THE_SHOW_POLL_INTERVAL environment variable controls the poll delay (seconds, default 5). Set it to 0 or 0.01 in tests for instant resolution.


Files

  • models.py — core dataclasses (Strategy now has success_when)
  • loader.py — YAML loading and validation
  • executor.py — scene execution loop (resume, all v0.4.1 states, real urgent contact)
  • state.py — SQLite state layer (WAL, resume, event log, urgent matter / send tables)
  • sanitise.py — markdown fence stripper for untrusted output
  • programme.py — reads from SQLite, generates markdown + JSON
  • adapters.py — stub adapters + idempotency key utilities
  • cli.py — CLI: validate / run / peek / programme / events
  • example_show.yaml — 5-scene example (covers all Session 2 + 3 features)
  • urgent_contact/ — Urgent Contact subsystem
    • dispatcher.py — raise matters, fire sends, poll, cancel, return resolution
    • auth.py — channel-native / reply-token / signed-link auth
    • parser.py — strict APPROVE / REJECT / STOP / CONTINUE keyword parser
    • throttle.py — per-show matter frequency limit
    • degradation.py — DAG pruning for cascading-dependency-failure
    • channels/base.py — ChannelAdapter protocol and InboundResponse type
    • channels/config.py — env var config helpers for all channels
    • channels/mock.py — file-drop mock channel for testing
    • channels/telegram.py — polling Telegram Bot API adapter
    • channels/email.py — SMTP send + signed-link poll adapter
    • channels/whatsapp.py — WhatsApp Business API skeleton
    • channels/sms.py — Twilio SMS adapter
    • link_queue.py — shared SQLite for email/SMS/WhatsApp webhook responses
    • link_server.py — Flask server: /respond, /whatsapp-webhook, /twilio-webhook
  • tests/ — pytest suite (146 tests)

Channel configuration

Copy .env.example to .env and fill in credentials:

cp .env.example .env
# edit .env

Channels activate automatically when their env vars are set. Missing vars emit a warning and the channel is skipped; the mock channel is always available.

Telegram — fastest to activate: create a bot with BotFather and set URGENT_TELEGRAM_BOT_TOKEN plus the authorised user IDs.

Email — create a Gmail App Password, set URGENT_SMTP_* vars and URGENT_EMAIL_SIGNING_SECRET.

WhatsApp — requires Meta Business API onboarding (1–7 days). See channels/whatsapp.py class docstring for the setup checklist.

SMS — Twilio account required. Trial accounts can only send to verified numbers.

Running the link server

The link server receives email link clicks and Twilio/WhatsApp webhooks.

# Manual (for development)
.venv/bin/python -m urgent_contact.link_server

# Via launchd (production — after filling in the plist env vars)
launchctl load ~/Library/LaunchAgents/org.shortandsweet.urgent-link-server.plist

For WhatsApp and Twilio webhooks (which require HTTPS), use ngrok:

ngrok http 5099
# Use the resulting https://*.ngrok.io URL in Meta / Twilio dashboards

Documentation

  • Quickstart — get a programme running in 5 minutes.
  • Operator Guide — the theatrical metaphor, how scenes work, approval gates, urgent contact mechanics. The starting point for anyone running The Show.
  • Spec v0.4.2 — formal specification and patch notes.

Coming in Session 5

  • Execution Monitor — watches running scenes, triggers Urgent Contact on anomalies

Coming in Session 6

  • First real show end-to-end with live Telegram notifications

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

the_show-1.1.0.tar.gz (117.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

the_show-1.1.0-py3-none-any.whl (62.9 kB view details)

Uploaded Python 3

File details

Details for the file the_show-1.1.0.tar.gz.

File metadata

  • Download URL: the_show-1.1.0.tar.gz
  • Upload date:
  • Size: 117.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for the_show-1.1.0.tar.gz
Algorithm Hash digest
SHA256 b2f2b66de2f4ade56d34a6b402e33113470013e94f00721be77b8c02fc37116a
MD5 f9d6d52eb88a6ba1508957b898c6f7a5
BLAKE2b-256 b3ee87f5292ee79239a536c88114c7352636ddceeed6b4bb7d9cd27144f1bef6

See more details on using hashes here.

File details

Details for the file the_show-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: the_show-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 62.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for the_show-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b1c948f5a79419b35041f422e177d698ebe8f1658d8c2ac634df248395dfb8df
MD5 f4c52c5304ba6a78a4f2365284af2f52
BLAKE2b-256 9ff3cbbc9e75d550a897ae66c2ef25515caedba8953537ae128921784eb5b117

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page