Nobody likes spoilers, especially agents.

Show them the ending and they skip the plot. Curtain keeps the script backstage until it's time to act.

Physical token withholding for AI coding agent workflows. Executes multi-act Markdown playbooks one step at a time, keeping future instructions backstage until prior acts complete.

- How it works

- Features

- FAQ

- Installation

- Quick Start

- Authoring Playbooks

- Syntax & Delimiters

- Commands & Controls

- Architecture

- Development

- Changelog

- License

When an AI coding agent receives a multi-step task in a single prompt or file, it attempts to execute the entire plan at once: skipping tests, hallucinating downstream phases, cutting corners, and bypassing review gates.

Prompting cannot prevent this. If future tokens exist in the context window, the model attends to them.

Curtain enforces physical token withholding across a four-step lifecycle:

[ User invokes /db-migrate ]

│

▼

┌─────────────────────────────┐

│ Act 1: Schema & Draft SQL │ ◄── Only Act 1 tokens enter context

└─────────────┬───────────────┘

│

▼

┌─────────────────────────────┐

│ > [!INTERMISSION] Gate │ ◄── Turn stops. Control yields to user.

└─────────────┬───────────────┘

│

[ User enters /next ]

│

▼

┌─────────────────────────────┐

│ Act 2: Local Verification │ ◄── Act 2 tokens injected

└─────────────┬───────────────┘

│

▼

┌─────────────────────────────┐

│ > [!CURTAIN] Advance │ ◄── Turn completes. Next act begins immediately.

└─────────────┬───────────────┘

│

▼

┌─────────────────────────────┐

│ Act 3: Cleanup & Docs │ ◄── Act 3 tokens injected

└─────────────────────────────┘

Convert an existing skill with /curtain-adopt <skill>, or write a playbook in PLAYBOOK.md using callout delimiters to separate sequential phases:

# Database Migration

## Act 1: Schema Audit & Draft Migration

Audit existing tables in `src/db/schema.ts`. Write a migration script in `migrations/002_user_prefs.sql`.

Do not apply the migration yet.

> [!INTERMISSION] Review migration SQL

> Ensure all columns have default values and no destructive DROP operations exist.

## Act 2: Local Verification

Run the migration script against local test Postgres. Run the test suite to check for regressions.

> [!CURTAIN] Continue automatically to cleanup

## Act 3: Cleanup & Documentation

Update ORM models, export types, and update schema docs in `docs/db.md`.Trigger the workflow via its slash command (e.g. /db-migrate). Curtain intercepts the prompt, resolves PLAYBOOK.md, and injects Act 1 into the agent's context.

The agent prompt receives only the active instructions:

[STEP 1 OF 3]

Audit existing tables in src/db/schema.ts. Write a migration script in migrations/002_user_prefs.sql.

Do not apply the migration yet.

[INTERMISSION CRITERIA]

Review migration SQL. Ensure all columns have default values and no destructive DROP operations exist.

Downstream instructions for Act 2 and Act 3 remain on disk and do not exist in the context window.

When the agent finishes drafting the migration, it concludes its turn. Curtain's Stop hook detects the > [!INTERMISSION] delimiter, sets the runner status to paused, and yields control to the user.

If you send chat messages or request changes during an intermission, Curtain appends [INTERMISSION REVIEW] instructions to ensure the agent addresses your feedback without advancing.

When satisfied with the changes, type /next (Codex: $curtain:next). Curtain advances the step pointer and injects Act 2.

When Act 2 completes, its concluding > [!CURTAIN] delimiter triggers an automatic advance. The Stop hook intercepts turn completion, supplies Act 3 immediately, and lets the agent finish the workflow without manual confirmation.

- Physical token withholding: Injects only the active Act into the prompt. Downstream steps do not exist in the context window.

- Intermission review gates: Pauses execution at > [!INTERMISSION]delimiters, yielding control to the user and replaying criteria on feedback turns.

- Autonomous act advancement: Delimiters (> [!CURTAIN]) raise the curtain and feed the next Act immediately upon turn completion.

- Zero schema overhead: Standard Markdown playbooks parsed by a generic AST tokenizer without complex YAML workflow configs.

- Backstage file protection: Intercepts file-reading tools and runner commands to prevent agents from peeking backstage at PLAYBOOK.md.

- Universal environment support: Shared single-source architecture supporting Google Antigravity, Anthropic Claude Code, and OpenAI Codex.

- Skill adoption and ejection: Built-in skills to migrate single-file skills into playbooks (/curtain-adopt) and reverse them cleanly (/curtain-eject).

- Zero-dependency bundled shim: Packaged into a single ESM file (dist/curtain.mjs) executed directly by host harness hooks withoutnpm install.

Standard prompting tells the model: "Execute Step 1, then wait for approval before running Step 2."

This instruction frequently fails. Because all steps reside in the context window, the model attends to future instructions. It rushes ahead, prepares artifacts for subsequent phases, combines steps, or skips verification commands to reach the finish line sooner.

Curtain replaces prompt instructions with physical isolation. Playbooks stay on disk in PLAYBOOK.md. Curtain's lifecycle hooks intercept the agent's prompt submission (PreInvocation) and turn completion (Stop), managing execution state via a local JSON file keyed by session ID.

At any given turn, only the text of the active Act enters the prompt. Because future tokens do not exist in the context window, the model cannot attend to them or plan ahead.

Agent harnesses (Claude Code, Google Antigravity, OpenAI Codex) treat SKILL.md as public prompt context. When a user runs a slash command, the harness injects the entire file into turn 1.

If Act 1, Act 2, and Act 3 reside in SKILL.md, the full script enters context on the very first turn. A runner hook injecting [STEP 1 OF 3] cannot hide instructions the harness already displayed.

Curtain separates public metadata from execution scripts:

- SKILL.md: Public entry point loaded by the harness. It instructs the agent to follow injected prompt instructions and forbids inspecting local files.

- PLAYBOOK.md: The backstage multi-act script. It is withheld by Curtain and injected step by step.

When an Act ends with > [!INTERMISSION], Curtain's Stop hook allows the agent to conclude its turn and yield control to the user. The session enters paused status.

During an intermission:

- The user inspects code, runs local checks, or replies with feedback in chat.

- If the user sends a chat message, Curtain intercepts it and appends [INTERMISSION REVIEW]instructions reminding the agent to resolve the feedback and conclude its turn.

- The agent cannot advance to the next Act on its own.

- Only entering /next(Codex:$curtain:next) advances the step pointer, sets status torunning, and injects the next Act.

An agent might attempt to read PLAYBOOK.md using file-reading tools (view_file, cat, ReadFile) or call runner commands via tool invocations.

Curtain blocks these attempts:

- Pre-tool hooks intercept file-reading tools targeting PLAYBOOK.mdor any skill directory containing active playbooks, denying access before the tool executes.

- Tool hooks block calls attempting to invoke runner skills (curtain:nextor active skill tools) directly from the model.

- Downstream tokens remain on disk; the agent prompt contains only the current Act.

Harnesses differ in lifecycle event names, wire payloads (JSON vs protojson), property casing (snake_case vs camelCase), and egress formats:

- Google Antigravity: Dispatches events via .agents/plugins/curtain/hooks.json. NormalizesUserPromptSubmitandview_fileevents.

- Anthropic Claude Code: Declares hooks in hooks/claude-codex-hooks.json. UsesUserPromptSubmit,Stop, andPreToolUse.

- OpenAI Codex: Uses snake_casepayloads and process exit code signaling (exit 2for tool block decisions).

Curtain provides dedicated adapters in src/harnesses/ that normalize wire payloads into strongly typed domain events and format egress output per platform specification. A single bundled executable (dist/curtain.mjs) handles execution across all environments.

Google Antigravity

agy plugin install https://github.com/lukstei/curtainClaude Code

From your terminal:

claude plugin marketplace add lukstei/curtain

claude plugin install curtain@curtain-marketplaceOr inside an active session:

/plugin marketplace add lukstei/curtain

/plugin install curtain@curtain-marketplaceOpenAI Codex

codex plugin marketplace add lukstei/curtain

codex plugin add curtain@curtainConvert any existing single-file skill into a multi-act playbook:

/curtain-adopt <skill-name-or-path>Curtain automatically:

- Preserves original prose and headings verbatim without inventing artificial titles.

- Prunes redundant wait instructions (e.g. "Wait for user confirmation") handled natively by > [!INTERMISSION].

- Prunes forward-context leaks (e.g. "In Step 3 we will apply...") rendered obsolete by physical token withholding.

- Strips Tables of Contents to prevent leaking future step names into Act 1 context.

- Inserts > [!INTERMISSION]at approval boundaries and> [!CURTAIN]at automatic section boundaries.

- Previews the proposed playbook in the review sidebar before writing PLAYBOOK.mdand wrappingSKILL.md.

Invoke the adopted skill directly using its slash command:

- Claude Code & AGY: /<skill-name>

- Codex: $<skill-name>

When paused at an intermission, review the agent's work and resume with /next (Codex: $curtain:next).

Note

- Antigravity: Antigravity executes skills by instructing the agent to read SKILL.mdviaview_file. Curtain intercepts anyview_filecall on a skill backed by aPLAYBOOK.mdto begin Act 1. To inspect or edit a CurtainSKILL.mdin Antigravity without triggering playbook execution, open the file directly in your editor.

- Codex: Hook output is visible in chat until openai/codex#25403 is resolved.

Restore an adopted playbook back to a standard single-file skill with zero lock-in:

/curtain-eject <skill-name-or-path>- Recombines YAML frontmatter from SKILL.mdwith instructions fromPLAYBOOK.md.

- Strips all > [!CURTAIN]and> [!INTERMISSION]callouts.

- Previews the restored SKILL.mdin the review sidebar, deletingPLAYBOOK.mdupon confirmation.

To create a new multi-act skill from scratch without adopting an existing one:

Organize your workflow inside a skill directory with SKILL.md for manifest metadata and PLAYBOOK.md for instructions:

.agents/skills/db-migrate/ (or .claude/skills/db-migrate/)

├── SKILL.md # Public skill manifest

└── PLAYBOOK.md # Backstage multi-act script

In PLAYBOOK.md, separate sequential phases using callout alert delimiters:

# Database Migration

## Act 1: Schema Audit & Draft Migration

Audit existing tables in `src/db/schema.ts`. Write a migration script in `migrations/002_user_prefs.sql`.

Do not apply the migration yet.

> [!INTERMISSION] Review migration SQL

> Ensure all columns have default values and no destructive DROP operations exist.

## Act 2: Local Verification

Run the migration script against local test Postgres. Run the test suite to check for regressions.

> [!CURTAIN] Continue automatically to cleanup

## Act 3: Cleanup & Documentation

Update ORM models, export types, and update schema docs in `docs/db.md`.Playbooks are standard Markdown files divided into sequential Acts by GitHub-style callout blockquotes:

Delimiters accept optional instructions:

- > [!INTERMISSION] Criteria: Injected as- [INTERMISSION CRITERIA]during the step prompt, and replayed as- [INTERMISSION REVIEW]during review turns.

- > [!CURTAIN] Criteria: Injected as- [TRANSITION CRITERIA]during the step prompt.

Multi-line instructions are supported:

> [!INTERMISSION] Review migration SQL

> - Ensure all columns have default values.

> - Confirm no destructive DROP operations exist.- Top-level callouts: Delimiters must appear as top-level Markdown blockquotes.

- Code block isolation: Callout blockquotes inside fenced code blocks are ignored and never trigger act boundaries.

- Case-insensitive: Delimiters match case-insensitively (> [!curtain],> [!INTERMISSION]).

- Preamble support: Any Markdown text before the first delimiter or heading is preserved as playbook preamble context.

npm install

npm run verify # Runs tests, linter, and typecheck

npm run build # Builds dist/curtain.mjs

npm run test:watch # Runs test watcherSee CHANGELOG for release history and notable changes.

MIT © 2026 Lukas Steinbrecher