curtain keeps the script backstage until it's time to act - reliable step-by-step skill execution and human gates for agents
TypeScript
2
69 commits
updated Sep 27, 2026
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.
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.
/nextWhen 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.
> [!INTERMISSION] delimiters, yielding control to the user and replaying criteria on feedback turns.> [!CURTAIN]) raise the curtain and feed the next Act immediately upon turn completion.PLAYBOOK.md./curtain-adopt) and reverse them cleanly (/curtain-eject).dist/curtain.mjs) executed directly by host harness hooks without npm 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:
[INTERMISSION REVIEW] instructions reminding the agent to resolve the feedback and conclude its turn./next (Codex: $curtain:next) advances the step pointer, sets status to running, 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:
PLAYBOOK.md or any skill directory containing active playbooks, denying access before the tool executes.curtain:next or active skill tools) directly from the model.Harnesses differ in lifecycle event names, wire payloads (JSON vs protojson), property casing (snake_case vs camelCase), and egress formats:
.agents/plugins/curtain/hooks.json. Normalizes UserPromptSubmit and view_file events.hooks/claude-codex-hooks.json. Uses UserPromptSubmit, Stop, and PreToolUse.snake_case payloads and process exit code signaling (exit 2 for 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.
agy plugin install https://github.com/lukstei/curtain
From your terminal:
claude plugin marketplace add lukstei/curtain
claude plugin install curtain@curtain-marketplace
Or inside an active session:
/plugin marketplace add lukstei/curtain
/plugin install curtain@curtain-marketplace
codex plugin marketplace add lukstei/curtain
codex plugin add curtain@curtain
/curtain-adopt)Convert any existing single-file skill into a multi-act playbook:
/curtain-adopt <skill-name-or-path>
Curtain automatically:
> [!INTERMISSION].> [!INTERMISSION] at approval boundaries and > [!CURTAIN] at automatic section boundaries.PLAYBOOK.md and wrapping SKILL.md.Invoke the adopted skill directly using its slash command:
/<skill-name>$<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.
/curtain-eject)Restore an adopted playbook back to a standard single-file skill with zero lock-in:
/curtain-eject <skill-name-or-path>
SKILL.md with instructions from PLAYBOOK.md.> [!CURTAIN] and > [!INTERMISSION] callouts.SKILL.md in the review sidebar, deleting PLAYBOOK.md upon 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:
| Delimiter | Type | Behavior |
|---|---|---|
> [!CURTAIN] | Automatic | Concludes the active Act. Intercepts turn completion via the Stop hook, raises the curtain, and feeds the next Act immediately. |
> [!INTERMISSION] | Review Gate | Concludes the active Act. Pauses execution and yields control to the user. Feedback messages replay review criteria. Resumes on /next. |
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.
> [!curtain], > [!INTERMISSION]).| Command (Claude Code / AGY) | Command (Codex CLI) | Description |
|---|---|---|
/<skill-name> | $<skill-name> | Start execution of a multi-act skill. |
/next | $curtain:next | Advance to the next Act when paused at an intermission. |
/curtain-adopt <skill> | $curtain-adopt <skill> | Convert an existing single-file skill into a multi-act Curtain playbook. |
/curtain-eject <skill> | $curtain-eject <skill> | Reverse an adopted playbook back into a standard single-file skill. |
npm install
npm run verify # Runs tests, linter, and typecheck
npm run build # Builds dist/curtain.mjs
npm run test:watch # Runs test watcher
See CHANGELOG for release history and notable changes.
MIT © 2026 Lukas Steinbrecher
65 commits
4 commits
TypeScript
97.3%
JavaScript
2.7%
curtain keeps the script backstage until it's time to act - reliable step-by-step skill execution and human gates for agents
TypeScript
2
69 commits
updated Sep 27, 2026
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.
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.
/nextWhen 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.
> [!INTERMISSION] delimiters, yielding control to the user and replaying criteria on feedback turns.> [!CURTAIN]) raise the curtain and feed the next Act immediately upon turn completion.PLAYBOOK.md./curtain-adopt) and reverse them cleanly (/curtain-eject).dist/curtain.mjs) executed directly by host harness hooks without npm 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:
[INTERMISSION REVIEW] instructions reminding the agent to resolve the feedback and conclude its turn./next (Codex: $curtain:next) advances the step pointer, sets status to running, 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:
PLAYBOOK.md or any skill directory containing active playbooks, denying access before the tool executes.curtain:next or active skill tools) directly from the model.Harnesses differ in lifecycle event names, wire payloads (JSON vs protojson), property casing (snake_case vs camelCase), and egress formats:
.agents/plugins/curtain/hooks.json. Normalizes UserPromptSubmit and view_file events.hooks/claude-codex-hooks.json. Uses UserPromptSubmit, Stop, and PreToolUse.snake_case payloads and process exit code signaling (exit 2 for 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.
agy plugin install https://github.com/lukstei/curtain
From your terminal:
claude plugin marketplace add lukstei/curtain
claude plugin install curtain@curtain-marketplace
Or inside an active session:
/plugin marketplace add lukstei/curtain
/plugin install curtain@curtain-marketplace
codex plugin marketplace add lukstei/curtain
codex plugin add curtain@curtain
/curtain-adopt)Convert any existing single-file skill into a multi-act playbook:
/curtain-adopt <skill-name-or-path>
Curtain automatically:
> [!INTERMISSION].> [!INTERMISSION] at approval boundaries and > [!CURTAIN] at automatic section boundaries.PLAYBOOK.md and wrapping SKILL.md.Invoke the adopted skill directly using its slash command:
/<skill-name>$<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.
/curtain-eject)Restore an adopted playbook back to a standard single-file skill with zero lock-in:
/curtain-eject <skill-name-or-path>
SKILL.md with instructions from PLAYBOOK.md.> [!CURTAIN] and > [!INTERMISSION] callouts.SKILL.md in the review sidebar, deleting PLAYBOOK.md upon 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:
| Delimiter | Type | Behavior |
|---|---|---|
> [!CURTAIN] | Automatic | Concludes the active Act. Intercepts turn completion via the Stop hook, raises the curtain, and feeds the next Act immediately. |
> [!INTERMISSION] | Review Gate | Concludes the active Act. Pauses execution and yields control to the user. Feedback messages replay review criteria. Resumes on /next. |
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.
> [!curtain], > [!INTERMISSION]).| Command (Claude Code / AGY) | Command (Codex CLI) | Description |
|---|---|---|
/<skill-name> | $<skill-name> | Start execution of a multi-act skill. |
/next | $curtain:next | Advance to the next Act when paused at an intermission. |
/curtain-adopt <skill> | $curtain-adopt <skill> | Convert an existing single-file skill into a multi-act Curtain playbook. |
/curtain-eject <skill> | $curtain-eject <skill> | Reverse an adopted playbook back into a standard single-file skill. |
npm install
npm run verify # Runs tests, linter, and typecheck
npm run build # Builds dist/curtain.mjs
npm run test:watch # Runs test watcher
See CHANGELOG for release history and notable changes.
MIT © 2026 Lukas Steinbrecher
65 commits
4 commits
TypeScript
97.3%
JavaScript
2.7%