Help us improve
Share bugs, ideas, or general feedback.
From show
Directs multi-play DAGs of `li play` subagents (60-90 min each) with per-play gates and adaptive re-planning based on intermediate results.
npx claudepluginhub ohdearquant/lionagi --plugin showHow this skill is triggered — by the user, by Claude, or both
Slash command
/show:showThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are the director. A **play** is one `li play <playbook> "<prompt>"` invocation
Orchestrates parallel multi-agent Evaluate-Loop v3 workflows for code tracks. Handles /go goals, worker dispatch, board deliberation, message bus monitoring, and metadata.json state tracking.
Writes correct lionagi playbooks — YAML invoked via `li play`. Covers spec fields, args schema, template interpolation, team semantics, and pitfalls. Activates when editing playbooks under ~/.lionagi/ or firm/resources/playbooks/.
Orchestrates complex tasks by decomposing into DAGs and dispatching focused sub-agents with minimal context. Invoke /orchestra run for multi-step, multi-repo coding workflows.
Share bugs, ideas, or general feedback.
You are the director. A play is one li play <playbook> "<prompt>" invocation
running in its own worktree for 60-90 min — think of it as a higher-level
auto-orchestrated subagent. A show is a human-shaped DAG of plays.
This skill is for the live path: you decompose the goal, fire one (or a few
parallel) plays, gate each output, and decide what comes next from what
you just saw — not from a pre-authored script. If your plan would not change
based on intermediate results, you do not need this skill — author Play JSONs
and use the batch engine (khive-internal/scripts/show.py) instead.
Show (this skill, you direct)
↓ fires
Play = one `li play <playbook>` subprocess, 60-90 min, own worktree
↓ contains
FlowPlan = LLM-planned DAG inside the play (li play's own orchestrator)
↓ executes
FlowOp on a Branch (one agent turn inside the play)
A play sits at the granularity of a substantial subagent task — large enough to justify its own LLM-planned sub-DAG, small enough that you can gate it without losing context.
| Duration heuristic | Meaning |
|---|---|
| < 30 min | Should have been a single Task() subagent call, not a play. Don't use show. |
| 30-60 min | Borderline. OK if it produces non-trivial artifacts the next play depends on. |
| 60-90 min | Sweet spot. |
| 90-120 min | Acceptable. Watch for slippage. |
| > 120 min | Play is too big. Split it. Note in the decisions log so the next show learns. |
These are planning heuristics, not guarantees. Local history is better than
this table — track actual durations per play in _meta.json and use them
to recalibrate the next show.
li play X "..." directlyli o flow)khive-internal/scripts/show.py) for concurrency + throttleOne directory per show under a stable root. Recommended:
$HOME/khive-work/shows/<topic>/
_show.md director notes — plan, state, decisions log, cost
_ABORT optional sentinel — director checks BEFORE fire, redo, AND merge
_final_verdict.json written after Step 7 (show-level gate)
<play-name>/
_intent.md WHY this play exists (audience: director + resume)
_prompt.md WHAT goes to `li play` (audience: the play itself)
_verdict.json play-gate verdict (written after Step 4 gate)
_meta.json explicit schema (see below)
.pid PID file (only present while subprocess is running)
.log stdout+stderr capture
<agent_id>/<file> li play's per-agent artifact dirs (see Artifact path note)
Note: $HOME/khive-work/... is a show-local convention I (Ocean) adopted for
the show skill. It is intentionally separate from per-repo khive-<layer>/
worktrees used by long-lived sub-lambdas (foundation-work, platform-work,
etc.) which are layer-scoped, not topic-scoped.
_meta.json schema (explicit){
"worktree": "/absolute/path/to/worktree",
"branch": "show/<topic>/<play>",
"attempt": 1,
"started_at": "2026-05-19T19:00:00-04:00",
"ended_at": "2026-05-19T20:25:00-04:00",
"exit_code": 0,
"merged_at": "2026-05-19T20:30:00-04:00",
"status": "pending|running|gated|merged|escalated|aborted_after_finish",
"team_missing": false,
"model": "claude/claude-sonnet-4-6",
"effort": "high"
}
Required at creation: worktree, branch, attempt, started_at.
Written by director at lifecycle transitions; never by the play itself.
li play's internal agents write their files under <save>/<agent_id>/,
one subdir per agent in the FlowPlan. The orchestrator inside li play
chooses agent_ids dynamically — you do not know them ahead of time.
So upstream-reference rules:
find $SHOW_DIR/<play> -maxdepth 3 -type f to walk the agent subdirs.$SHOW_DIR/research/*/landscape.md (the * matches the
agent_id), and instruct the play to read whichever it finds.Intent is for you and future-you; prompt is what the play receives. They serve different audiences and rot at different rates.
_intent.md template (keep short — 1-3 paragraphs):
# Intent: <play-name>
## Goal
What this play must produce to be considered passing.
## Why this matters
Why does this play exist in the show? What does it unblock?
## References
- ADR-053 (Sinkhorn attention)
- Issue #142, #145
- Upstream play outputs: research artifacts under `$SHOW_DIR/research/*/`
## Acceptance
- [ ] Concrete artifact 1 (filename, what it must contain)
- [ ] Concrete artifact 2
- [ ] All tests in <module> pass
## Out of scope
Items deliberately excluded so the play-gate does not flag them as missing.
The Acceptance section is REQUIRED. Do not fire a play whose _intent.md
lacks a ## Acceptance section with at least one - [ ] item. The play-gate
will fail the play with feedback "missing Acceptance checklist" if you skip
this — see Step 4.
Write _show.md:
# Show: <topic>
## Goal
<one paragraph — what done looks like for the WHOLE show>
## Repository
- Repo: <path>
- Integration branch: show/<topic>/integration (branched off <base>)
- Base for final merge: <main|develop|other>
## Plays
1. **<name>** [<playbook>] [eff <low|medium|high>] — <one-line objective> · deps: []
2. **<name>** [<playbook>] [eff <…>] — … · deps: [<other names>]
## Cost & time
- Track actual `started_at`/`ended_at` in each `_meta.json`.
- If a play's actual time exceeds 120 min OR observed spend looks unusual
vs peer plays, pause before firing the next and ask Ocean.
## Cleanup-owed (populated on abort or final cleanup)
- Remote branches still on origin: (list)
## Decisions log
- (append entries as you adapt the plan — WHEN and WHY)
Define the lionagi CLI path once at the top of your shell session — uv run
fails to resolve the venv from a worktree cwd, and the absolute path works
from any directory:
LI="/Users/lion/projects/.venv/bin/li"
SHOW_DIR="$HOME/khive-work/shows/<topic>"
TOPIC="<topic>"
Effort levels (from lionagi EFFORT_MAP): low | medium | high | xhigh.
Do NOT use quick — it is not a valid value and will be rejected or ignored
unpredictably.
Create the integration branch ONCE at show start, off the base:
cd <repo>
git fetch origin
git checkout -B show/${TOPIC}/integration origin/<base>
git push -u origin show/${TOPIC}/integration
All play branches will branch off this integration branch. Final merge to
<base> happens only after the show-level final gate passes (Step 7).
A play is "ready" when its depends_on are all merged (not just gated)
AND no upstream play is escalated.
Before firing, check the abort sentinel:
if [ -f "$SHOW_DIR/_ABORT" ]; then
echo "Show aborted; not firing more plays."
exit 1
fi
Validate naming — topic and play names must each match the lionagi flow-id regex so they're safe as team names + branch segments. Lowercase + dashes, 1-32 chars each:
# `show_${TOPIC}_${PLAY}` becomes the team name; must match ^[A-Za-z0-9_-]{1,64}$
[[ "$TOPIC" =~ ^[a-z0-9-]{1,32}$ ]] || { echo "TOPIC must be lowercase alnum+dash, 1-32 chars"; exit 1; }
[[ "$PLAY" =~ ^[a-z0-9-]{1,32}$ ]] || { echo "PLAY must match same"; exit 1; }
TEAM="show_${TOPIC}_${PLAY}"
[ ${#TEAM} -le 64 ] || { echo "Combined team name '$TEAM' is ${#TEAM} chars; lionagi limit is 64"; exit 1; }
Write _intent.md and _prompt.md. The prompt MUST:
$SHOW_DIR/research/*/landscape.md).Preflight the intent file before firing:
grep -q '^## Acceptance' "$SHOW_DIR/$PLAY/_intent.md" \
&& grep -q '^- \[ \]' "$SHOW_DIR/$PLAY/_intent.md" \
|| { echo "ERROR: $PLAY/_intent.md missing Acceptance checklist"; exit 1; }
Every play runs in its own worktree on its own branch. There is no
"artifact-only skips worktree" exception — even research/design-doc plays
get a worktree. If they don't touch the repo, Step 5a's merge will be a
no-op (git diff on BR vs integration is empty) and produces a trivial
merge commit. The uniform lifecycle simplifies the state machine.
If a play genuinely should not affect the repo, write its artifacts only
into $SHOW_DIR/$PLAY/ (NOT inside $WT) and the merge will be empty.
PLAY=<play-name>
WT="$HOME/khive-work/worktrees/${TOPIC}-${PLAY}"
BR="show/${TOPIC}/${PLAY}"
cd <repo>
git worktree add -b "$BR" "$WT" "show/${TOPIC}/integration"
# Initialize _meta.json with required fields
jq -n \
--arg wt "$WT" \
--arg br "$BR" \
--argjson attempt 1 \
--arg t "$(date -Iseconds)" \
'{worktree:$wt, branch:$br, attempt:$attempt, started_at:$t, status:"pending"}' \
> "$SHOW_DIR/$PLAY/_meta.json"
"$LI" play <playbook> "$(cat $SHOW_DIR/$PLAY/_prompt.md)" \
--save "$SHOW_DIR/$PLAY" \
--cwd "$WT" \
--yolo \
--effort <low|medium|high> \
--team-mode "show_${TOPIC}_${PLAY}"
EC=$?
# Record exit + ended_at
tmp=$(mktemp)
jq --argjson ec "$EC" --arg t "$(date -Iseconds)" \
'.exit_code=$ec | .ended_at=$t | .status="running_complete"' \
"$SHOW_DIR/$PLAY/_meta.json" > "$tmp" && mv "$tmp" "$SHOW_DIR/$PLAY/_meta.json"
The bg wrapper owns the subprocess lifecycle so exit code + timestamps are always recorded, even if the director's shell crashes between fire and wait.
(
"$LI" play <playbook> "$(cat $SHOW_DIR/$PLAY/_prompt.md)" \
--save "$SHOW_DIR/$PLAY" \
--cwd "$WT" \
--yolo \
--effort <low|medium|high> \
--team-mode "show_${TOPIC}_${PLAY}"
ec=$?
tmp=$(mktemp)
jq --argjson ec "$ec" --arg t "$(date -Iseconds)" \
'.exit_code=$ec | .ended_at=$t | .status="running_complete"' \
"$SHOW_DIR/$PLAY/_meta.json" > "$tmp" && mv "$tmp" "$SHOW_DIR/$PLAY/_meta.json"
rm -f "$SHOW_DIR/$PLAY/.pid"
) > "$SHOW_DIR/$PLAY/.log" 2>&1 &
echo $! > "$SHOW_DIR/$PLAY/.pid"
# Stagger before firing the next bg play to avoid API rate-limit bursts
sleep 120
Stagger justification: primarily for API rate-limiting. For plays on different repos with no shared resources, 30-60s may suffice. Same repo with concurrent workers can also help avoid worktree contention.
Poll via PID + verify it's still our process (avoid PID-recycle races):
while ps -p "$(cat $SHOW_DIR/$PLAY/.pid)" -o command= 2>/dev/null | grep -q "li play"; do
sleep 30
done
Bash run_in_background=true notifications fire when the wrapper exits,
instantly — NOT when the play completes. Do not rely on them.
Check the subprocess exit code FIRST. Non-zero → diagnose before any redo; do not feed a crash trace to the gate as "play output".
EC=$(jq -r '.exit_code // empty' "$SHOW_DIR/$PLAY/_meta.json")
if [ "$EC" != "0" ]; then
echo "Subprocess exited $EC; diagnose before redoing."
# Inspect .log; decide whether to retry, fix the prompt, or escalate.
fi
Then fire the per-play gate via play-gate. Use find to walk the
per-agent artifact tree (artifacts are nested):
ARTIFACT_TREE="$(cd $SHOW_DIR/$PLAY && find . -maxdepth 3 -type f \
! -name '.*' ! -name '_intent.md' ! -name '_prompt.md' \
! -name '_verdict.json' ! -name '_meta.json' | sort)"
"$LI" agent -a play-gate --cwd "$WT" "$(cat <<EOF
Gate this play.
Intent (why this play exists):
$(cat $SHOW_DIR/$PLAY/_intent.md)
Original prompt:
$(cat $SHOW_DIR/$PLAY/_prompt.md)
Subprocess exit code: ${EC:-unknown}
Artifact tree under $SHOW_DIR/$PLAY/ (relative paths):
$ARTIFACT_TREE
Read the relevant files from those paths. Evaluate strictly against the
Acceptance checklist in the intent. Items in "Out of scope" are forbidden
grounds for failing.
Respond as JSON ONLY:
{"gate_passed": <true|false>, "feedback": "<actionable items if failed, null if passed>", "notes": "<optional advisory, null otherwise>"}
EOF
)" > "$SHOW_DIR/$PLAY/_verdict.json"
Validate the verdict — but accept boolean false as a valid value
(do NOT use jq -e '.gate_passed'; -e returns failure for falsy values).
jq -e 'has("gate_passed") and (.gate_passed | type == "boolean")' \
"$SHOW_DIR/$PLAY/_verdict.json" >/dev/null || {
echo "Gate returned malformed JSON — treating as failed gate"
printf '%s\n' '{"gate_passed":false,"feedback":"gate output not JSON; manual review needed","notes":null}' \
> "$SHOW_DIR/$PLAY/_verdict.json"
}
# Mark status
PASSED=$(jq -r '.gate_passed' "$SHOW_DIR/$PLAY/_verdict.json")
tmp=$(mktemp)
jq --arg s "$( [ "$PASSED" = "true" ] && echo "gated" || echo "gate_failed" )" \
'.status=$s' "$SHOW_DIR/$PLAY/_meta.json" > "$tmp" && mv "$tmp" "$SHOW_DIR/$PLAY/_meta.json"
| Verdict | Action |
|---|---|
gate_passed: true | Merge play branch → integration (Step 5a). |
| Failed, attempt == 1 | Redo (Step 5b). |
| Failed, attempt == 2 | ESCALATE (Step 5c). |
| Non-zero subprocess exit | Step 5d (diagnose). |
First, refresh the play branch against current integration (other plays may have merged ahead of you):
# Re-check abort sentinel before mutating integration
[ -f "$SHOW_DIR/_ABORT" ] && {
tmp=$(mktemp "$SHOW_DIR/$PLAY/.meta.XXXXXX")
jq '.status="aborted_after_finish"' "$SHOW_DIR/$PLAY/_meta.json" > "$tmp" \
&& mv "$tmp" "$SHOW_DIR/$PLAY/_meta.json"
echo "Show aborted; play $PLAY passed but not merged."
exit 0
}
cd "$WT"
git fetch origin
git merge --no-ff "show/${TOPIC}/integration" \
-m "Show ${TOPIC}: refresh ${PLAY} from integration"
# Resolve only objectively trivial conflicts (see Conflict policy below).
# Re-run acceptance fast-checks (cargo test for the affected scope, etc.).
# If any test fails post-refresh → ESCALATE, do not merge into integration.
# Then merge play branch INTO integration with a stable, grep-able message:
cd <repo> # main checkout
git checkout "show/${TOPIC}/integration"
git merge --no-ff "${BR}" -m "Show ${TOPIC}: integrate ${PLAY}"
MERGE_SHA=$(git rev-parse HEAD)
git push origin "show/${TOPIC}/integration"
# Record merge time + SHA (Step 7 rollback uses this SHA, not a grep)
tmp=$(mktemp "$SHOW_DIR/$PLAY/.meta.XXXXXX")
jq --arg t "$(date -Iseconds)" --arg sha "$MERGE_SHA" \
'.merged_at=$t | .merge_sha=$sha | .status="merged"' \
"$SHOW_DIR/$PLAY/_meta.json" > "$tmp" && mv "$tmp" "$SHOW_DIR/$PLAY/_meta.json"
Conflict policy (objective). Trivial conflicts are limited to:
git diff -w is empty.Everything else is semantic and escalates:
Re-check abort sentinel, verify team exists (--team-attach upserts; if the
prior team is missing, the redo silently loses memory):
[ -f "$SHOW_DIR/_ABORT" ] && { echo "Show aborted; not redoing"; exit 0; }
# Team existence check (resolve by name — team files are UUID-named, not name-named)
TEAM="show_${TOPIC}_${PLAY}"
if ! "$LI" team show "$TEAM" >/dev/null 2>&1; then
echo "WARN: prior team '$TEAM' missing; redo will start without memory."
tmp=$(mktemp "$SHOW_DIR/$PLAY/.meta.XXXXXX")
jq '.team_missing=true' "$SHOW_DIR/$PLAY/_meta.json" > "$tmp" \
&& mv "$tmp" "$SHOW_DIR/$PLAY/_meta.json"
fi
# Bump attempt
tmp=$(mktemp); jq '.attempt=2 | .status="redoing" | del(.exit_code, .ended_at)' \
"$SHOW_DIR/$PLAY/_meta.json" > "$tmp" && mv "$tmp" "$SHOW_DIR/$PLAY/_meta.json"
# Build redo prompt: original + feedback prepended
FEEDBACK=$(jq -r '.feedback' "$SHOW_DIR/$PLAY/_verdict.json")
PROMPT=$(printf '## Previous attempt feedback (fix these):\n%s\n\n---\n\n%s' \
"$FEEDBACK" "$(cat $SHOW_DIR/$PLAY/_prompt.md)")
# Re-fire (same as Step 3 foreground), but with --team-attach (NOT --team-mode)
"$LI" play <playbook> "$PROMPT" \
--save "$SHOW_DIR/$PLAY" \
--cwd "$WT" \
--yolo \
--effort <low|medium|high> \
--team-attach "show_${TOPIC}_${PLAY}"
EC=$?
# Record + re-gate (same as Step 4)
tmp=$(mktemp); jq --argjson ec "$EC" --arg t "$(date -Iseconds)" \
'.exit_code=$ec | .ended_at=$t' \
"$SHOW_DIR/$PLAY/_meta.json" > "$tmp" && mv "$tmp" "$SHOW_DIR/$PLAY/_meta.json"
# (then run Step 4 again — same jq validation, same JSON parsing)
Never pass both --team-mode and --team-attach on the same invocation
— lionagi rejects them as mutually exclusive. First fire uses --team-mode;
redos use --team-attach.
tmp=$(mktemp); jq '.status="escalated"' "$SHOW_DIR/$PLAY/_meta.json" > "$tmp" \
&& mv "$tmp" "$SHOW_DIR/$PLAY/_meta.json"
Mark all downstream plays (transitively dependent on this one) as blocked.
Stop firing new plays. Surface to Ocean:
ESCALATED: <play-name>
Two attempts failed. Critic feedback: <verdict.feedback>
Downstream blocked: <list>
Options:
(a) Widen the prompt and try again — name the change you'd make.
(b) Director writes the missing piece directly, then re-runs Step 4
on the now-updated artifacts. The play is not complete until that
re-gate passes — manual fixes do NOT bypass the gate.
(c) Accept partial result; continue with downstreams that don't depend on this play.
(d) Abort the show.
Do not auto-fire any option. Wait for Ocean.
Diagnose .log first. Common causes: API rate-limit / model timeout / OOM /
prompt size / playbook arg error. Do not redo blindly. If the cause is
transient (rate-limit), wait + retry as Step 5b (uses --team-attach,
preserving any partial team state). If the cause is the prompt or playbook,
fix the prompt and redo. If unknown, escalate.
Before firing the next play, re-read _show.md and ask:
Update _show.md BEFORE firing. Log in the decisions log:
## Decisions log
- 2026-05-19 18:42: research/*/landscape.md surfaced OT-LR as stronger baseline
than Sinkhorn alone. Updated design play to emphasize OT-LR; added a new
play 2.5 (literature_verify) before design fires.
This adaptive step is THE reason to use show instead of a static runner. If
you never update _show.md between plays, you should not be using this skill.
After every play has merged (not just gated), run the end-to-end gate using
the show-final-gate profile (purpose-built for JSON output across plays;
see "Custom agent profiles" below):
Extract the goal precisely (the simpler awk range can terminate on the same line and lose the body):
GOAL=$(awk '
/^## Goal$/ {in_goal=1; print; next}
in_goal && /^## / {exit}
in_goal {print}
' "$SHOW_DIR/_show.md")
PLAY_DIRS=$(ls -d "$SHOW_DIR"/*/ 2>/dev/null | sort)
PLAY_ARTIFACTS=$(find "$SHOW_DIR" -maxdepth 4 -type f \
! -name '.*' ! -name '_show.md' ! -name '_ABORT' ! -name '_final_verdict.json' \
| sort)
"$LI" agent -a show-final-gate --effort high --cwd "$SHOW_DIR" "$(cat <<EOF
Final review of show "${TOPIC}".
Show dir: $SHOW_DIR
Repo: <repo>
Integration branch: show/${TOPIC}/integration
Original goal:
$GOAL
Play directories (absolute paths — read each play's _intent.md, _verdict.json, _meta.json, and artifact subdirs):
$PLAY_DIRS
All artifact files across all plays:
$PLAY_ARTIFACTS
Decisions log:
$(awk '/^## Decisions log$/,0' $SHOW_DIR/_show.md)
Evaluate:
1. Did the SHOW achieve the original goal — not just per-play gates?
2. Cross-play inconsistencies that per-play gates missed?
3. Tests that pass per-play but fail when integrated?
4. Is integration branch safe to merge to <base>?
Respond JSON ONLY:
{"show_passed": <bool>, "blockers": [...], "recommendations": [...], "goal_assessment": "<one paragraph>", "cross_play_findings": [...]}
EOF
)" > "$SHOW_DIR/_final_verdict.json"
# Validate — tighter than Step 4. Requires blockers/recommendations/cross_play_findings to be
# arrays, goal_assessment to be a string, and any failure to name at least one blocker.
jq -e '
has("show_passed") and (.show_passed | type == "boolean") and
(.blockers | type == "array") and
(.recommendations | type == "array") and
(.cross_play_findings | type == "array") and
(.goal_assessment | type == "string") and
((.show_passed == true) or ((.blockers | length) > 0))
' "$SHOW_DIR/_final_verdict.json" >/dev/null || {
echo "Final gate returned non-JSON or violated schema; treating as needs-review."
printf '%s\n' '{"show_passed":false,"blockers":["final-gate output invalid; manual review needed"],"recommendations":[],"goal_assessment":"","cross_play_findings":[]}' \
> "$SHOW_DIR/_final_verdict.json"
}
If show_passed: false:
Options:
- Treat each blocker as a new play (re-enter Step 1).
- Roll back specific play merges (see rollback below) and re-decompose.
- Accept partial + ship anyway (with Ocean's approval).
Rollback (after merge, when final gate finds an irreparable cross-play issue):
# The play's merge SHA was recorded in _meta.json at Step 5a
MERGE_SHA=$(jq -r '.merge_sha // empty' "$SHOW_DIR/$PLAY/_meta.json")
[ -n "$MERGE_SHA" ] || MERGE_SHA=$(git log --oneline "show/${TOPIC}/integration" \
| grep "integrate ${PLAY}" | head -1 | awk '{print $1}')
git revert -m 1 "$MERGE_SHA"
Reverted merges cannot be re-merged cleanly via git merge. If multiple
rollbacks are needed, prefer to git reset --hard <last-known-good> on a
fresh integration branch and re-merge only the valid plays. Note this in
the decisions log.
Only after show_passed: true:
cd <repo>
git checkout <base>
git pull origin <base>
git merge --no-ff "show/${TOPIC}/integration" -m "Show ${TOPIC}: integrate"
# Resolve conflicts if base moved. Inspect the diff manually.
# DO NOT auto-push — surface the diff to Ocean before pushing.
Then prune worktrees and branches (after Ocean confirms forensics complete):
for wt in $HOME/khive-work/worktrees/${TOPIC}-*; do
git worktree remove "$wt"
done
git branch -d show/${TOPIC}/integration
git push origin --delete show/${TOPIC}/integration
# Optional: delete play branches on remote after 1 week or per Ocean's policy.
When picking up a show mid-flight (after Claude session ended or context compacted), do these reads + rehydration BEFORE doing anything else.
LI="/Users/lion/projects/.venv/bin/li"
SHOW_DIR="$HOME/khive-work/shows/<topic>" # use the show dir the resume targets
TOPIC=$(basename "$SHOW_DIR")
REPO="$(awk -F': ' '/^- Repo: / {print $2; exit}' $SHOW_DIR/_show.md)"
# Per-play variables (set inside a loop over plays):
# PLAY=$(basename "$play_dir")
# WT=$(jq -r '.worktree // empty' "$play_dir/_meta.json")
# BR=$(jq -r '.branch // empty' "$play_dir/_meta.json")
cat $SHOW_DIR/_show.md — original plan + decisions logls $SHOW_DIR/ — see which plays exist + check _ABORT + check _final_verdict.json_intent.md, _meta.json, _verdict.json (if exists), .pid (if exists)Then classify each play by ordered precedence — apply in order, first match wins:
1. `$SHOW_DIR/_ABORT` exists
→ ALL plays: aborted_pending_cleanup. Do not fire, redo, or merge without Ocean.
2. play's `.pid` file exists AND `ps -p $(cat .pid) -o command=` contains "li play"
→ state: running. Poll until exit, then re-gate.
3. play's `.pid` file exists but PID dead OR command doesn't match
→ state: state_corrupt_manual_review. Inspect `.log`; the wrapper crashed
before recording exit code. Surface to Ocean.
4. `_verdict.json gate_passed:true` AND `_meta.merged_at` present
→ state: completed. Skip.
5. `_verdict.json gate_passed:true` AND `_meta.merged_at` absent AND no `.pid` alive
→ state: gated_passed_pending_merge. Verify with `git log --oneline
show/${TOPIC}/integration | grep -q "integrate ${PLAY}"`. If absent,
proceed to Step 5a (merge). If present (race: merge succeeded but
meta not updated), patch `_meta.merged_at` from git log timestamp.
6. `_verdict.json gate_passed:false` AND `_meta.attempt == 1`
→ state: needs_redo. Proceed to Step 5b.
7. `_verdict.json gate_passed:false` AND `_meta.attempt == 2`
→ state: escalated. Surface to Ocean.
8. `_verdict.json gate_passed:false` AND `_meta.attempt` missing
→ state: state_corrupt_manual_review. Surface to Ocean.
9. `_verdict.json` present AND `_meta.json` missing
→ state: state_corrupt_manual_review. Surface to Ocean.
10. no `.pid` AND `_meta.exit_code` present (or `_meta.status == "running_complete"`)
AND no `_verdict.json`
→ state: run_complete_pending_gate. Subprocess finished but gate was never run.
Proceed to Step 4 — do NOT re-fire the play.
11. `_intent.md` + `_meta.json` present AND no `.pid` AND no `_verdict.json`
AND no `_meta.exit_code` (and `_meta.status == "pending"`)
→ state: prepared_not_fired. Proceed to Step 3.
12. `_intent.md` present, no `_meta.json`, no `.pid`, no `_verdict.json`
→ state: pending. Proceed to Step 2.
state_corrupt_manual_review always escalates. Do not attempt to repair
corrupt state without Ocean.
Soft abort (no more launches, no more integration mutations; in-flight plays finish but their gate results are not auto-merged):
touch "$SHOW_DIR/_ABORT"
The director checks this sentinel at:
If a play finishes after _ABORT is set, Step 5a records
status:"aborted_after_finish" instead of merging.
Hard abort (kill in-flight plays now):
for pidfile in $SHOW_DIR/*/.pid; do
pid=$(cat "$pidfile" 2>/dev/null) || continue
kill -TERM "$pid" 2>/dev/null
done
sleep 5
# Best-effort orphan cleanup (PID-based kill above is primary):
pkill -9 -f "li play.*show_${TOPIC}_" 2>/dev/null || true
Worktrees stay intact after abort — forensics first, cleanup later. The
remote show/${TOPIC}/integration branch is left on origin; record it in
_show.md's "Cleanup-owed" section for Ocean to delete later (or via the
/clean skill).
If a default profile doesn't fit, write a new one. Canonical location:
firm/agents/<name>/<name>.md. Lionagi resolves via ~/.lionagi/agents/<name>.md
(symlinked into firm). Format:
---
model: claude/claude-sonnet-4-6
effort: medium
yolo: true
---
# α[Name]
Mission and behavior body here. Plain markdown after frontmatter.
Profiles the show skill ships with:
play-gate — per-play gatekeeper (Step 4). Sonnet, effort:medium,
JSON-only output. Checks Acceptance checklist against artifacts.show-final-gate — show-level synthesis gate (Step 7). Sonnet, effort:high,
JSON-only output. Checks cross-play coherence + original goal.Both are separate from the existing critic.md profile because that
profile's contract is severity-tiered prose, incompatible with the JSON
parsing the show skill needs. We isolate the new behavior in new profiles
rather than mutating the existing one.
A play at high effort is typically 60-90 min wall clock. Actual cost varies significantly with model + effort + how many sub-agents the FlowPlan allocates inside the play. Lionagi does not yet expose spend per run, so treat cost as observed-only: investigate if one play takes substantially longer or feels more expensive than peer plays.
Recalibration loop: at the end of each show, append a one-line "actual time
per play" note to _show.md so future shows can decompose better.
When a play is escalated:
status: escalated in that play's _meta.json.blocked — they do NOT auto-fire."Downstream cone of P": all plays that transitively depend on P. Compute
by walking depends_on forward from P.
run_in_background ≠ play completion. Notification fires on wrapper
exit, instantly. Use PID polling + command-line verification.uv run li from worktree cwd fails. Use $LI="/Users/lion/projects/.venv/bin/li"
set in Step 0.--save subdir. Sharing dirs clobbers artifacts.li play agents write to <save>/<agent_id>/, not <save>/. Use
find to discover artifacts; don't poll fixed top-level filenames.--team-attach and --team-mode are mutually exclusive. First fire =
mode; redos = attach. Verify team file exists before attaching.jq -e '.gate_passed' — -e returns
failure for falsy values, misclassifying valid false verdicts.
Always check has("gate_passed") and (.gate_passed | type == "boolean").Show: "Land ADR-053 (Sinkhorn attention) in lattice"
Initial _show.md:
# Show: adr-053-sinkhorn
## Goal
Implement, test, and review the Sinkhorn-attention path in lattice-inference per ADR-053.
## Repository
Repo: /Users/lion/projects/khive/lattice
Integration: show/adr-053-sinkhorn/integration
Base: main
## Plays
1. **research** [research] [eff high] — survey OT-attention literature · deps: []
2. **design** [feature] [eff high] — write ADR-053 draft · deps: [research]
3. **implement** [feature] [eff high] — impl Sinkhorn solver + tests · deps: [design]
4. **review** [pr-review] [eff medium] — multi-perspective gate · deps: [implement]
Run:
research → gate pass → merge → adapt: surfaces OT-LR as stronger baseline
↓
Update _show.md: design emphasizes OT-LR; add play 2.5 literature_verify
literature_verify → gate pass → merge
design → gate fail 1st (missing ablation) → redo (--team-attach) → pass → merge
implement → gate fail 2x (test coverage) → ESCALATE
↓
Director surfaces options.
Ocean picks "(b) director writes missing tests".
Director writes tests in worktree, re-runs Step 4.
Re-gate passes. Merge.
review → gate pass → merge
Step 7 show-final-gate → show_passed:true → final merge to main (manual push by Ocean)
→ cleanup worktrees + delete play branches per policy
The decisions log + final summary feed the next show's planning so decomposition gets better over time.