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-dice command and CC_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

Requires bun, git, and jq.

./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:

  1. Hook receives { session_id, transcript_path } on stdin
  2. checkAllSlots() groups slots by die size, rolls one base die per group
  3. Each slot checks its target against shared/bonus rolls
  4. On trigger: ๐ŸŽฒ Nat {best}! {message} shown to Claude via stderr (exit 2)
  5. 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]
OptionDefaultDescription
--die <n>20Die size (d20, d6, etc.)
--target <n>20Target number
--target-mode <mode>exactexact, gte, or lte
--type <type>accumulatoraccumulator, fixed, or single
--accumulation-rate <n>7Turns per +1 die (accumulator only)
--max-dice <n>100Dice cap (accumulator only)
--fixed-count <n>1Dice count (fixed only)
--cooldown <mode>per-sessionper-session or none
--no-clear-on-startDon't clear state on session start
--no-reset-on-triggerDon't reset accumulator on trigger
--no-flavorDon'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

VariablePurpose
AGENT_DICE_BASEOverride base directory (default: ~/.claude/dice/, or ~/.pi/agent/dice/ under Pi)
AGENT_DICE_SESSION_IDOverride session ID
DEBUG=1Verbose 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