agent-dice
June 21, 2026 ยท View on GitHub
Probabilistic dice triggers for AI agent hooks โ one host-agnostic engine with adapters for Claude Code and Pi.
Formerly cc-dice. The
cc-dicecommand andCC_DICE_*env vars still work as aliases, so existing setups keep running.
Register dice slots, and agent-dice rolls them on every agent stop event. Accumulators escalate probability with conversation depth; single/fixed slots give flat odds.
Why
Claude Code sessions are long-running, stateful, and unpredictable, which reminds me of a video game. Very much like a tabletop RPG campaign. In D&D, dice are the core mechanic that makes emergent behavior possible. A Natural 20 happens because probability demands it and it changes the course of the game.
agent-dice is more of a nod to the mechanics than trying to become a sophisticated plugin. In fact, you can create a stop hook yourself with a flat 5% chance to mimic a d20 roll. However, agent-dice gives you what a one-liner can't: accumulating dice pools across turns, shared rolls across multiple slots, per-session cooldowns, and state that persists across turns.
This creates moments that feel organic rather than scheduled. A prompt to invoke a skill that fires at turn 42 because the dice finally landed without any intervention. Your agents are part of the campaign.
Install
curl -fsSL https://raw.githubusercontent.com/pro-vi/agent-dice/main/install.sh | bash
Or clone locally:
git clone https://github.com/pro-vi/agent-dice.git
cd agent-dice && ./install.sh
./install.sh check # verify
./install.sh uninstall # remove
Quick Start
# Register an accumulator slot (escalating probability)
agent-dice register refactor \
--die 20 --target 20 --type accumulator \
--message "Cast /refactor and review your current work."
# Register a flat-chance slot
agent-dice register second-opinion \
--die 20 --target 2 --type single \
--message "Get a second opinion (e.g. from codex)."
# Check status
agent-dice status refactor
# Manual roll (dry run, no state change)
agent-dice roll second-opinion
That's it. The installed stop hook rolls all slots automatically on every Claude Code stop event.
How Hooks Work
The installer registers a Stop hook in ~/.claude/settings.json. On each stop:
- Hook receives
{ session_id, transcript_path }on stdin checkAllSlots()groups slots by die size, rolls one base die per group- Each slot checks its target against shared/bonus rolls
- On trigger:
๐ฒ Nat {best}! {message}shown to Claude via stderr (exit2) - No triggers: exit
0(roll results visible to user only)
A SessionStart hook clears state for slots with clearOnSessionStart (default).
Slot Configuration
agent-dice register <name> [options]
| Option | Default | Description |
|---|---|---|
--die <n> | 20 | Die size (d20, d6, etc.) |
--target <n> | 20 | Target number |
--target-mode <mode> | exact | exact, gte, or lte |
--type <type> | accumulator | accumulator, fixed, or single |
--accumulation-rate <n> | 7 | Turns per +1 die (accumulator only) |
--max-dice <n> | 100 | Dice cap (accumulator only) |
--fixed-count <n> | 1 | Dice count (fixed only) |
--cooldown <mode> | per-session | per-session or none |
--no-clear-on-start | Don't clear state on session start | |
--no-reset-on-trigger | Don't reset accumulator on trigger | |
--no-flavor | Don't prepend dice emoji + roll lingo | |
--message <msg> | Message shown to Claude on trigger |
Message Placeholders
{rolls}, {best}, {diceCount}, {slotName} are replaced at trigger time.
By default, trigger output is prefixed with ๐ฒ Nat {best}! โ disable with --no-flavor.
Dice Types
Accumulator: +1 die per N turns since last trigger. Escalating pressure.
Turns 0-6: 0 dice (0%) Turns 14-20: 2d20 (9.8%)
Turns 7-13: 1d20 (5%) Turns 21-27: 3d20 (14.3%)
Single: Always 1 die. Flat chance every stop event.
Fixed: Always N dice. Constant probability.
Shared Roll Pools
Slots sharing a die size observe the same base roll:
- Two single d20 slots claiming different faces are mutually exclusive
- An accumulator d20 slot gets the shared base roll + independent bonus dice
- Different die sizes (d20 vs d6) roll independently
This means registering reflection (target 20) and second-opinion (target 1) on a d20 guarantees at most one triggers per base roll.
CLI Reference
agent-dice register <name> [options] Register a dice slot
agent-dice unregister <name> Remove a slot
agent-dice list List all slots
agent-dice status <name> Show dice status
agent-dice roll <name> Dry-run roll
agent-dice reset <name> Reset accumulator
agent-dice clear <name> Clear state
Storage
~/.claude/dice/
slots.json Slot registry
state/
{slotName}-{sessionId}.json Per-slot per-session state
triggered-{slotName}-{sessionId} Cooldown markers
Library API
Hooks and scripts can import via the installed symlink:
const { registerSlot, checkAllSlots } =
await import(`${process.env.HOME}/.claude/dice/cc-dice.ts`);
Or from the source tree directly:
import { registerSlot, checkAllSlots } from "./src/index";
Environment Variables
| Variable | Purpose |
|---|---|
AGENT_DICE_BASE | Override base directory (default: ~/.claude/dice/, or ~/.pi/agent/dice/ under Pi) |
AGENT_DICE_SESSION_ID | Override session ID |
DEBUG=1 | Verbose logging to stderr |
CC_DICE_BASE / CC_DICE_SESSION_ID remain supported as back-compat aliases (read when the AGENT_DICE_* form isn't set; the session-start hook sets both).
Troubleshooting
./install.sh check shows broken symlinks: The source directory was moved or deleted. Re-run ./install.sh to re-link.
Hook not firing: Verify with ./install.sh check that hooks are registered in settings.json. Use agent-dice roll <name> to test a dry run.
State not clearing between sessions: Ensure the SessionStart hook is registered. Use agent-dice clear <name> to reset a specific slot manually.
Architecture
agent-dice is a thin host facade over a reusable, host-agnostic dice core.
Scheduling (dice counts, shared rolls, cooldowns, sentinel calibration) lives in
src/core/; everything host-specific (transcripts/sessions, file storage, hook or
event output) lives in src/adapters/. The Claude Code public API in src/index.ts
is unchanged. The same engine now drives two hosts via the DiceHost contract:
Claude Code (default) and Pi (src/adapters/pi/, see below). A Codex adapter is
researched but not yet built.
Use with Pi
agent-dice also ships as a Pi extension โ the same core engine behind a Pi adapter.
pi install npm:agent-dice
# or from source:
pi install git:github.com/pro-vi/agent-dice
Configure by talking to the agent (recommended). The extension registers
register_dice / list_dice / remove_dice tools, so you just describe intent โ
no flags to type:
"nudge me to refactor as the session gets long, and about 5% of turns remind me to get a second opinion"
The agent calls register_dice and the slots are created.
Or drive it yourself with the /dice command (mirrors the CLI):
/dice register refactor --die 20 --target 20 --message "Cast /refactor and review."
/dice list | status <name> | roll <name> | reset <name> | clear <name>
The extension rolls on each agent_end (Pi's analog of Claude's Stop) and injects a
nudge when a slot triggers. State lives under ~/.pi/agent/dice/ (or AGENT_DICE_BASE).
Depth & delivery: Pi measures depth the same way as Claude โ the count of user
messages in the session (sessionManager.getEntries(), the analog of Claude's
transcript exchanges) โ so accumulationRate defaults carry over unchanged. Trigger
nudges are best-effort (at-most-once): cooldown/reset commit at roll time, but a nudge
can be dropped if the session ends before Pi's next turn.
Contributing
git clone --recurse-submodules https://github.com/pro-vi/agent-dice.git
cd agent-dice
bun install
bun run test # BATS suite + conformance probes (bare `bun test` won't run BATS)
Test submodules (bats-core, bats-support, bats-assert) are required. If you cloned without --recurse-submodules:
git submodule update --init --recursive
See docs/architecture.md for internals.
License
MIT