Play is the implicit pre-harness controller for reusable outcomes. It searches authorized Play
indexes first, runs an adequate Play in Use mode, or asks before entering Explore mode. The
existing rote and rote-* skills remain callable specialists that can hand off through Play.
You do not need to remember organization/name slugs or lower-level rote commands. Describe the outcome in ordinary language:
$play find a Play that retrieves recent emails
$play run the PostHog daily active users report
$play create a reusable weekly customer report
$play whats new
$play birth weekly customer report
$play list my organizations and shared Plays
Handle this normally without Play
Play keeps four decisions separate so each prompt is small and honest:
| You intend to… | Play does… | You choose… |
|---|---|---|
| Find something reusable | Search local and authorized organization indexes | Inspect one result or stop |
| Run a known or vaguely named Play | Resolve the name, inspect it read-only, and show setup/effects | Pull and run, or not now |
| Solve a one-off task | Search first; if no adequate Play exists, offer Explore | Explore with rote, or continue normally |
| Preserve successful exploration | Verify it before preparing a candidate | Private, Public, or Skip |
| See what’s new | Pull an inbox grouped by organization and compare it with the remembered SHA | Run, search, create, or finish |
| Revisit how a Play was born | Open the owner-private, redacted birth certificate | Choose an unambiguous name, reference, or birth SHA |
Search selection is never execution approval. Before every run, Play shows what the exact version does, its parameters, adapters and credentials, what this machine must install or repair, declared operations and writes, and any unknown effect semantics. Only the next structured choice can authorize the exact inspected version and displayed parameters.
Play is driven by one declarative machine, references/controller/machine.yaml
(play.machine/v1). The controller re-reads it on every activation, executes exactly one declared
prompt or entry action per state, and accepts only events declared by
actions.yaml and prompts.yaml.
It never jumps states from conversational intuition. Initial state: qualify. Terminals:
receipt, completed, exited, blocked. Any action failure emits action_blocked and lands in
blocked (or, for a failed run, the repair_offer gate).
stateDiagram-v2
direction TB
[*] --> qualify
%% ── Qualify routes each request to one trajectory ──
qualify --> search : outcome / search request
qualify --> use_inspect : exact play request
qualify --> awareness_collect : whats new
qualify --> creator_search : create a Play
qualify --> management_list : list orgs / plays
qualify --> management_offer : ambiguous list request
qualify --> birth_show : birth lookup
qualify --> exited : conversation / excluded
%% ── Search and adequacy ──
search --> search_present : search-only request
search --> classify : outcome request
search_present --> search_offer
search_offer --> use_inspect : result selected
search_offer --> completed : dismissed
classify --> use_inspect : full match
classify --> explore_offer : partial / uncertain / no match
%% ── Use (existing Play) ──
use_inspect --> use_offer : inspected
use_inspect --> completed : not runnable
use_inspect --> search : reference unresolved
use_offer --> use_run : approved
use_offer --> completed : declined
use_run --> use_verify : run ready
use_run --> repair_offer : drifted / failed
use_verify --> use_receipt : outcome verified
use_verify --> repair_offer : not verified
use_receipt --> receipt
repair_offer --> explore_prepare : repair approved
repair_offer --> exited : continue normally
%% ── Explore (consent, route, delegated execution) ──
explore_offer --> explore_prepare : explore approved
explore_offer --> exited : continue normally
explore_prepare --> explore_route
explore_route --> explore_handoff : route within policy
explore_route --> modality_offer : widening required
modality_offer --> explore_route : widening approved
modality_offer --> blocked : declined
explore_handoff --> explore_execute : typed packet ready
explore_execute --> explore_receipt : typed specialist receipt
explore_receipt --> explore_verify : outcome ready
explore_receipt --> explore_route : route exhausted, budget left
explore_receipt --> effect_offer : rote confirmation required
explore_receipt --> auth_repair_offer : auth repair required
effect_offer --> explore_handoff : guarded call approved
effect_offer --> blocked : declined
auth_repair_offer --> auth_repair_handoff : approved
auth_repair_offer --> blocked : declined
auth_repair_handoff --> auth_repair_execute
auth_repair_execute --> auth_repair_receipt
auth_repair_receipt --> explore_handoff : validated repair
auth_repair_receipt --> blocked : failed / invalid
explore_verify --> crystallize : outcome verified
explore_verify --> explore_route : not verified, budget left
%% ── Save lifecycle (crystallize → publish → birth → index) ──
crystallize --> save_offer : candidate ready
crystallize --> completed : not reusable
save_offer --> author_release : Private or Public
save_offer --> completed : Skip
author_release --> birth_capture : flow released
birth_capture --> private_org : private
birth_capture --> public_owner : public
private_org --> private_publish
public_owner --> public_publish : owner resolved
public_owner --> public_owner_offer : owner ambiguous
public_owner_offer --> public_publish : owner selected
public_owner_offer --> blocked : declined
private_publish --> birth_bind : published private
public_publish --> birth_bind : published public
birth_bind --> index
index --> saved_inspect
saved_inspect --> completed : readback matches
%% ── Awareness, creator, management, birth ──
awareness_collect --> awareness_present : new items
awareness_collect --> completed : unchanged
awareness_present --> awareness_offer
awareness_offer --> use_inspect : play selected
awareness_offer --> awareness_need : search
awareness_offer --> creator_need : create
awareness_offer --> completed : done
awareness_need --> search
creator_need --> creator_search
creator_search --> creator_classify
creator_classify --> creator_offer : related Play exists
creator_classify --> explore_prepare : no match
creator_offer --> use_inspect : use existing
creator_offer --> explore_prepare : adapt / create distinct
management_offer --> management_list
management_list --> management_present
management_present --> completed
birth_show --> completed
receipt --> [*]
completed --> [*]
exited --> [*]
blocked --> [*]
State ownership is explicit: play owns prompts, evaluators, and verification; rote-specialist
states (explore_execute, crystallize, author_release, publication, management_list) are
delegated through typed play.handoff/v1 packets and validated play.handoff-receipt/v1 receipts;
flow-runtime owns use_run and saved_inspect via first-class rote play run/inspect.
Guards (for example search_is_complete, route_within_policy, exploration_budget_remaining,
exact_published_version_is_indexed) are declared in actions.yaml, and
tests/controller/test_machine_conformance.py fails when the machine, actions, prompts, or the
thinking-orbs presentation mapping drift.
Play is packaged as one self-contained plugin under plugins/play. The package includes the skill,
controller references, Python runtime, harness activation tools, and the justfile recipes that
configure and verify the Play-first experience. scripts/bin/package-plugin --check prevents those
installed files from drifting from this repository's source of truth.
Rote is a prerequisite. Install and complete its guided setup first:
# Codex
codex plugin marketplace add modiqo/rote-skills
codex plugin add rote-onboard@rote-skills
# Claude Code
claude plugin marketplace add modiqo/rote-skills
claude plugin install rote-onboard@rote-skillsRestart the harness and invoke $rote-setup in Codex or /rote-setup in Claude Code. Play checks
that the rote executable is present, an identity is authenticated, and rote play is available;
it stops with the applicable setup commands when any precondition is missing. It never installs or
authenticates Rote without permission.
Install Play from its public marketplace after Rote setup:
codex plugin marketplace add modiqo/play
codex plugin add play@play-skills
claude plugin marketplace add modiqo/play
claude plugin install play@play-skillsThis checkout is also a valid local marketplace:
# Run from this repository root.
codex plugin marketplace add .
codex plugin add play@play-skills
claude plugin marketplace add .
claude plugin install play@play-skillsThe public marketplace source is modiqo/play, so . can be replaced with that GitHub
owner/repository from outside this checkout. Claude's manifest declares the rote plugin as a
dependency from the separately trusted rote-skills marketplace; every harness still uses the same
runtime preflight because plugin metadata alone cannot prove CLI installation or login state.
Kimi has no plugin marketplace. It discovers skills from AGENTS.md-standard roots — the shared
~/.agents/skills directory and per-harness ~/.<harness>/skills roots. Install Play for Kimi
from this checkout:
just plan
just installinstall discovers every skills root containing rote skills — including ~/.agents/skills —
links this Play skill into each, and applies the activation metadata in
agents/openai.yaml (allow_implicit_invocation: true) so Play stays
implicitly invocable and the rote specialists remain model-invocable for chained handoffs. Play's
structured prompts map to Kimi's askquestion control
(scripts/bin/play-question <prompt> --harness kimi), and just harness kimi /
just smoke kimi start and smoke-test the harness like Codex and Claude Code.
Restart the harness after plugin installation. On first use, Play runs the bundled preflight. To
make Play the preferred implicit entrypoint while keeping installed rote specialists
model-invocable for chained handoffs, preview
and apply the bundled reversible activation profile from the installed skill directory:
just plan
just install
just verify-profileIn marketplace mode this profile does not create a second Play link. It snapshots and updates the
activation metadata of discovered rote skills so the harness can follow chained handoffs.
Uninstall restores those exact snapshots and fails closed if a managed file was subsequently
changed.
After a new Play release is pushed, refresh the marketplace snapshot and reinstall/update the plugin. Published changes must carry a new plugin version; a push that keeps the same version is not a reliable cache invalidation mechanism.
For Codex:
codex plugin marketplace upgrade play-skills
codex plugin add play@play-skillsFor Claude Code:
claude plugin marketplace update play-skills
claude plugin update play@play-skillsRestart the harness and start a new conversation after updating so it loads the refreshed skill. If you enabled the implicit Play-first profile, run the following from the newly installed Play skill directory to converge its reversible activation metadata after the plugin refresh:
just install
just verify-profileKimi and other AGENTS.md-standard roots (~/.agents/skills, ~/.<harness>/skills) have no plugin
cache to upgrade; they always follow the source-checkout path below. If you use a cloned source
checkout instead of the GitHub marketplace — or need to refresh those AGENTS.md roots — update from
the repository root with:
git pull --ff-only
just package
just updateThen restart the harness and begin a new conversation. just package refreshes the self-contained
marketplace payload; just update safely reapplies and verifies the source-linked Play-first
profile.
Preview every harness root and canonical rote skill that will change:
just planActivate the Play-first profile and verify it:
just install
just verify-profileAfter editing the source skill, confirm that every source-linked installation is still valid:
just updateThe links make source edits live immediately; a running harness must still be restarted to reload the revised skill.
install discovers every installed harness skill root containing rote or rote-*, links this
Play skill into each root, and makes every Rote skill model-invocable so specialist handoffs can
continue without another user command. It snapshots the original Rote activation files so the
change is reversible. Restart running harnesses after enabling the profile.
It is also the convergence command after rote harness setup, a plugin refresh, or a newly added
harness. If Rote replaced managed skill files, just install preserves those refreshed files as the
new uninstall baseline and reapplies only Play's activation metadata. It adds new roots/skills and
retires removed ones without restoring stale backups. A changed or conflicting Play link still
fails closed.
Inspect the active profile at any time:
just status
just status-rootsStart a supported interactive harness after verifying the profile:
just harness codex
just harness claude
just harness kimiStart a compact Codex session without changing global Codex configuration:
just harness-quietThis launch sets model_verbosity="low", model_reasoning_summary="none", and
hide_agent_reasoning=true as per-session overrides. It reduces model narration and reasoning
events; the Codex UI may still render tool calls that were actually made.
Run a read-only smoke test that must reach Play's Explore-or-continue consent gate:
just smoke codex
just smoke claude
just smoke kimiRun every locally installed supported smoke-test harness with:
just smoke-allSmoke tests start new harness processes and may consume model credits.
Find by outcome across local and authorized remote indexes:
$play find a Play that retrieves recent emails
$play search live status for AI services
$play run the PostHog DAU report
For a vague run request, Play searches and offers recognizable names. For an exact reference, it
skips search but never skips inspection or approval. A registry-only result is labeled as available
in an authorized organization and expected to need a local pull/install. The first-class run later
performs that convergence after approval; Play does not manually assemble pull and Flow commands.
List organization and registry inventories:
$play list orgs
$play list plays
$play list
Create, explore, save, and share without memorizing lifecycle commands:
$play create a reusable weekly customer report
Explore this with rote and make it reusable if it works
Handle this normally without Play
Play always searches before creating. If an adequate Play exists it offers Inspect existing, Adapt existing, or Create distinct. Otherwise explicit create intent enters Explore directly. Successful exploration is verified before Play asks:
- Private — release and publish to an authorized private organization;
- Public — release and publish under a selected public owner;
- Skip — keep the result without publishing or indexing a Play.
Explore execution is fail-closed around Rote ownership. CALL routes only through
rote-using-adapters, SHELL through rote-shell, DRIVE through rote-browse, and combined work
through rote-workspace. Play first confirms that the exact skill is callable in the current
harness, then validates its typed receipt before accepting the result. If the specialist is absent,
Play blocks; it never substitutes a directly exposed MCP, app, shell, or browser tool.
CALL routes also converge on an authenticated Rote adapter. Play reuses an installed adapter or asks Rote to detect OpenAPI, GraphQL, or MCP and create the appropriate adapter, then completes Rote's auth cycle before execution. The receipt records adapter, type, creation, and auth provenance, so a raw MCP call cannot masquerade as a delegated result.
DRIVE routes carry a current-version crystallization limit that Play discloses before
exploration begins. Typed browser steps express navigation, waits, clicks, typing, and the
canonical extract slices only; they cannot carry a raw page snapshot or arbitrary DOM/table
content, and front-end accessibility trees are volatile across sites and releases. A browser
outcome whose required facts exceed the canonical slices can crystallize only as a legacy stepless
body, which rote play run rejects (play_run_eligible: false). Play therefore warns at the
Explore offer (or the DRIVE route milestone), repeats the limit at the save offer, and treats
play_run_eligible: false as a publication gate requiring explicit user approval. See the
DRIVE crystallization limit guidance and the
RCA that motivated it.
After release, Play captures a private birth certificate from the exploration evidence. After Private or Public publication, it binds that certificate to the minted exact reference, then indexes and inspects the canonical version before calling the save successful. Organization membership, invitations, and sharing use the organization/list surface rather than hidden local state.
Open or verify how one of your Plays was born:
$play birth weekly customer report
$play show how modiqo/weekly-customer-report was born
scripts/bin/play-birth show modiqo/weekly-customer-report@1.0.0
scripts/bin/play-birth list --json
scripts/bin/play-birth verify weekly-customer-report --json
scripts/bin/play-birth capture --workspace weekly-report-build --flow weekly-customer-report --json
scripts/bin/play-birth bind <birth-sha> --reference modiqo/weekly-customer-report@1.0.0 --json
Birth certificates live under ~/.play/births, independently of .rote. They are readable only by
the local OS user, content-addressed, and captured once per released Flow fingerprint. They preserve
safe counts, timings, dependency edges, modalities, token savings, artifact hashes, and the minted
URI’s registry-supplied publication author provenance while
excluding raw commands, parameters, queries, responses, credentials, and workspace paths. They are
not uploaded to the registry and do not follow a Play to another machine. See
references/publish/birth.md for capture, binding, privacy, and
lookup semantics. Normal users need only $play birth …; capture and bind are controller-owned
lifecycle commands shown here for diagnostics and integration testing.
Open the externally read-only Play inbox:
$play whats new
$play whats new this week
$play digest # compact command alias
scripts/bin/play-digest --remember --days 1 --json
scripts/bin/play-digest --since 2026-08-03T00:00:00Z --json
scripts/bin/play-digest --checkpoint host-checkpoint.json --json
scripts/bin/play-digest --org modiqo --days 7 --json
“What’s new” is intentionally framed like an inbox. It groups new and revised Plays by organization and shows each Play’s title, publication author when provenance supplies one, short description, visibility, timestamp, and canonical reference. It then shows the top 10 public Plays in authorized organizations ranked by lifetime downloads. Registry flow info does not currently expose run counts, so the UI labels downloads explicitly and never calls them runs or trending activity.
Selecting a card enters read-only inspection before execution approval. Publication authors are
display metadata; Play does not equate an author string with the current signed-in identity. Ranking
scope and missing global, run, or personal metrics are explicit rather than inferred. The emitted
checkpoint token can be persisted by an authorized host for gap-free daily delivery; the command
does not write host state unless --remember is explicit.
On normal $play whats new requests, Play uses remembered mode. It stores only a stable awareness SHA,
UTC checkpoint, and authorized-scope contract in ~/.rote/play/digest-state.json. If the current
snapshot has the same SHA, the next response is simply “Nothing new since your last Play check.”
The moving time window is excluded from the SHA, and no inbox contents or credentials are stored.
Recurring delivery is optional and must be explicitly requested. Its host-neutral two-phase contract remains available for an authorized scheduler:
scripts/bin/play-scheduler-probe
scripts/bin/play-delivery prepare --target-key daily-self --channel harness --days 1
scripts/bin/play-delivery release --envelope envelope.json --ack delivered-ack.jsonThe host scheduler owns recurrence, destination delivery, and storage. prepare emits an immutable
envelope with a deterministic delivery ID; release emits the next checkpoint only for a matching
successful acknowledgment and never persists it. Failed sends therefore leave the prior checkpoint
unchanged. Play never installs or fabricates a scheduler as part of an on-demand digest request.
Search normalizes punctuation and repeated terms, runs both sources concurrently, deduplicates aliases and versions by canonical Play reference, and shows a URI, local availability, and the next read-only inspection command for every registry-addressable result.
The organization view shows active member, private Play, public Play, and total counts. The Play
view groups private and public Plays under each authorized organization. An ambiguous $play list
request presents both views as structured choices supported by the active harness.
For diagnostics or integrations, the same reusable building blocks are available directly:
scripts/bin/play-search recent emails --json
scripts/bin/play-inspect warsaw-rust/posthog-dau-report@0.0.3 --json
scripts/bin/play-inventory --json
scripts/bin/play-handoff prepare --stdin --json
scripts/bin/play-handoff verify --stdin --json
scripts/bin/play-birth show weekly-customer-report --json
scripts/bin/play-birth verify weekly-customer-report --json
scripts/bin/play-question approve_play_run --harness codex
scripts/bin/play-question approve_play_run --harness claude
scripts/bin/play-question approve_play_run --harness kimiThe question command maps the same prompt and event contract to Codex request_user_input, Claude
and Kimi askquestion, or a numbered Markdown fallback. play-inspect normalizes the complete
rote play inspect <reference> --json result into a stable disclosure. After approval, the
controller performs exactly one rote play run <exact-reference> <approved-parameters> --yes.
It uses rote flow or rote registry flow only where rote play has no equivalent capability and
never decomposes a failed Play operation into a pull-plus-Flow-run fallback.
After a new Play is released, Play captures its owner-private birth object. After publication, it binds the object to the registry content hash, indexes the Play, reads the canonical registry entry back with JSON inspection, verifies its owner/version/visibility, and only then reports success.
React-capable hosts can use the adapter in ui/thinking-orbs to render
thinking-orbs from the authoritative Play machine state:
just ui-install
just ui-checkimport { PlayActivity } from '@modiqo/play-thinking-orbs';
<PlayActivity playState="creator_search" />The mapping uses all nine animations for distinct trajectories: listening for declared prompts, searching for discovery, solving for classification and verification, connecting for existing-Play inspection/execution, weaving for multimodal exploration, shaping for crystallization, composing for release/publication, working for result assembly, and breathing for paused terminal states. Every machine state has exactly one accessible status label, and tests fail if the machine and mapping drift.
The installed skill also teaches the agent to use the same presentation at meaningful milestones:
scripts/bin/play-presentation creator_search
# ◌ Peeking through the Play shelves…
scripts/bin/play-presentation use_run --json
# play.presentation/v1 payload for a capable host rendererWhen a compatible MCP Apps/custom-UI host exposes a callable renderer, PlayActivity displays the
animated orb and message. Installing the skills-only plugin does not create that renderer. Codex CLI
and Claude Code text transcripts use the exact static glyph and message without claiming it is
animated. During the blocking rote play run command, Rote retains ownership of its own progress
display.
This is an optional host adapter, not a claim that a skill can replace native Codex, Claude Code,
Cursor, or Kimi activity chrome. Hosts without a custom React surface continue to receive Play's
milestone-only text updates. The adapter depends on thinking-orbs 0.2.0 from Jakub Antalik under
the MIT license; no upstream source is copied into this repository.
Remove Play from every managed harness root and restore the exact original rote activation files:
just uninstall
just statusRestart running harnesses after disabling the profile. Uninstall fails closed if a managed Play link was replaced or a rote activation file changed after installation; it will not overwrite the newer content silently.
just package
just package-check
just ui-check
just testThe tests exercise the declarative Play machine and the complete activation lifecycle in temporary harness roots, including installation, verification, idempotency, rollback, and conflict handling.
The foundation is Python-only. Commands under scripts/bin/ and harness entrypoints under
scripts/harness/ are thin executables; reusable command, private-store, birth-certificate,
registry, search, inventory, digest, elicitation, typed specialist handoff, and machine-validation logic lives in
scripts/lib/play/. References and tests are
grouped by controller, awareness, Explore, publication, integration, and harness use case.
For isolated testing, override the discovered roots or reversible state location:
PLAY_HARNESS_ROOTS=/path/one:/path/two just install
PLAY_PROFILE_STATE=/tmp/play-profile.json just install