Observify

User Tests · wolf-orchestra
← all user tests
USER TEST PLAN

wolf-orchestra

Derived from: no git repo present at ~/Projects/wolf-orchestra (confirmed via `git

0/56 checked · 0 pass · 0 fail

Flows

check each step and final check
F1

Fresh-project bootstrap (the Quickstart copy-in)

Proves the README's stated install path actually produces a working project skeleton.

Persona / precondition: a new, empty directory outside this repo; none of wolf-orchestra's files present yet.

0/6

Run git init my-test-project && cd my-test-project. Expect: a normal git init message
ending Initialized empty Git repository in .../my-test-project/.git/.

Run cp -r ~/Projects/wolf-orchestra/{.claude,.claude-plugin,skills,scripts,CLAUDE.md} .
exactly as the README's Quickstart shows it. Expect: the copy succeeds with no error, and
ls -A now shows .claude, .claude-plugin, skills, scripts, CLAUDE.md — verified
this exact command copies cleanly on the reference machine.

Run test -f .claude/settings.json && test -x scripts/worktree.sh && echo OK. Expect:
OK — the hook config and an executable script both survived the copy.

Open CLAUDE.md in the new project. Expect: it opens with # wolf-orchestra — Conductor
Constitution
and lists the routing table (Escalation ceiling / Conductor / Implementer /
Recon / Codex worker / … / Verdict oracle) — ten rows, matching this repo's own CLAUDE.md.

Start claude inside the new project (manual — needs an interactive terminal). Expect: the
session starts as the conductor and, per Hard rule 6 in CLAUDE.md, is expected to end its
first unit of work with a red/yellow/green /recap line. NOT independently exercised in
this pass — confirm manually in a live session.

Final check: diff -rq ~/Projects/wolf-orchestra/scripts my-test-project/scripts reports
no differences — the copied scripts are byte-identical to the source, so nothing about the
copy step silently mutated them.

F2

Worktree lifecycle: one-vendor-per-worktree enforcement

Proves Hard rule 1 ("one vendor per worktree") is a real, live-tested guard, not just prose.

Persona / precondition: a scratch git repo (git init, one commit) with ORCHESTRA_ROOT exported to point at it, so nothing touches the real wolf-orchestra checkout.

0/6

Run ~/Projects/wolf-orchestra/scripts/worktree.sh new TEST-1. Expect: stderr shows
[orchestra] worktree ready: <path> and stdout's last line is the bare worktree path
<ORCHESTRA_ROOT>/.orchestra/worktrees/TEST-1; git worktree list now shows two entries
(main checkout + the new one on branch orchestra/TEST-1) — verified live, exit 0.

Run the identical command again: scripts/worktree.sh new TEST-1. Expect: it refuses
rather than silently reusing or duplicating — exact stderr [orchestra] FATAL: worktree
exists for TEST-1 (one vendor per worktree)
, exit 1 — verified live.

Run scripts/worktree.sh clean TEST-1. Expect: stderr shows [orchestra] cleaned TEST-1,
exit 0, and git worktree list is back down to just the main checkout — verified live.

Run scripts/worktree.sh clean NEVER-EXISTED (a task that was never created). Expect: it
does not error — [orchestra] cleaned NEVER-EXISTED, exit 0 (the script's || true guards
on both the worktree remove and branch -D calls) — verified live.

Inspect .orchestra/.gitignore in the scratch repo. Expect: it was auto-created containing
exactly *, so worktrees and reports never show up in git status of a consumer project —
verified live.

Final check: git status --short in the scratch repo shows nothing untracked from any of
the above — the self-ignoring .orchestra/.gitignore from step 5 is doing its job.

F3

Vendor CLI flags are still accurate (Article III re-verification)

Proves the "flags verified against installed CLI" comments in the run-.sh wrappers are not stale — each wrapper's exact invocation is checked against the CLI actually installed today.*

Persona / precondition: codex, agy, and grok installed and on PATH.

0/6

Run codex exec --help and check for -m, --model, -s, --sandbox <SANDBOX_MODE> with
workspace-write listed as a possible value, and -o, --output-last-message <FILE>.
Expect: all three are present exactly as scripts/run-codex.sh uses them
(codex exec --model "$model" --sandbox workspace-write -o "$last" ...) — verified live
against codex-cli 0.146.0.

Run agy --help and check for -p/--print, --model, and --print-timeout with a
stated default. Expect: all three present, and the default is 5m0s — matching
scripts/run-gemini.sh's own comment ("--print-timeout defaults to only 5m, so raise it")
and its ${AGY_PRINT_TIMEOUT:-30m} override — verified live against agy 1.1.9.

Run grok --help and check for -p, --single <PROMPT>, --json-schema <SCHEMA> (its help
text says it "Implies --output-format json"), and -m, --model <MODEL>. Expect: all three
present exactly as scripts/run-grok.sh uses them — verified live against grok 0.2.117.

Compare the README's Quickstart dependency line ("codex ... · gemini · grok (x.ai/cli) ·
ollama") against what scripts/run-gemini.sh actually invokes. Expect (KNOWN DEFECT,
confirmed): the script calls require agy ... and runs the agy binary — never gemini
yet neither the README's deps line nor its Config section names agy anywhere (CLAUDE.md
and AGENTS.md DO say "codex/grok/agy/ollama" in Article I, so the two docs disagree with
each other on the vendor's actual command name). A user who installs the real Google
gemini CLI following the README literally will find it never gets invoked, and agy is
not on PATH. Confirmed: command -v gemini → not found, command -v agy → found, on this
machine. This currently fails; correct behavior is the README naming the binary it actually
shells out to.

Grep the README's Config section for AGY_PRINT_TIMEOUT and GROK_FLAGS. Expect (KNOWN
DEFECT, confirmed): neither appears, even though scripts/run-gemini.sh reads
AGY_PRINT_TIMEOUT and scripts/run-grok.sh reads GROK_FLAGS as live overrides. This
currently fails; correct behavior is both env vars listed alongside CODEX_MODEL etc.

Final check: none of the three vendor wrappers' documented flags have silently changed
meaning since the "Jul 2026" comment date — re-running steps 1-3 a month later (2026-08-07)
still matches, so Article III's guard is currently satisfied for the flags themselves (only
the naming/docs gaps in steps 4-5 are open).

F4

Async run polling contract (start / wait / status)

Proves the reason this harness exists at all: no single Bash call ever blocks past the harness timeout, because long vendor runs are detached and polled in bounded chunks.

[sensitive content omitted]

0/5

Run scripts/run-async.sh status NOPE-1 for a task that was never started. Expect: prints
exactly NONE, exit 0 — verified live.

Manually create .orchestra/reports/SIM-1.async/ with t0 (a timestamp) and exit
containing 0 (simulating a vendor run that already finished successfully — the same
files run-async.sh start's detached _exec branch would have written). Run
scripts/run-async.sh status SIM-1. Expect: DONE, exit 0. Then run
scripts/run-async.sh wait SIM-1. Expect: stderr logs [orchestra] SIM-1 finished exit=0
(log: ...)
, stdout prints DONE, exit 0 — verified live.

Repeat step 2 with exit containing 3 (simulating a failed vendor run) under task
SIM-2. Expect: status prints FAILED (exit 3); wait prints bare FAILED (no exit
code suffix) and exits 3 — this asymmetry between status's and wait's FAILED text is
real script behavior, not a typo; both were verified live.

elapses with no exit file, it kills the process tree (pkill -P then kill),
logs a TIMEOUT line, and prints FAILED with exit 3 — read from source, not forced live
(forcing a real 3600s timeout is impractical for this pass).

Final check: every exit code run-async.sh can produce (0 = DONE, 3 = FAILED, 10 =
RUNNING/call-again) is accounted for by the two subagent courier specs (codex-worker.md,
grok-swarm.md) that loop on it — both explicitly say "RUNNING (exit 10) means the vendor is
still working — call wait again; do not treat it as an error."

F5

Report validation gate

Proves Hard rule 3 ("reports must validate against report.schema.json") actually rejects a malformed report rather than waving it through.

Persona / precondition: scratch repo with a copy of skills/report-protocol/references/report.schema.json under the same relative path (so scripts/lib/common.sh's SCHEMA variable resolves).

0/4

Write a report JSON with all nine required fields (task_id, agent, model, status,
worktree, files_touched, commands_run, evidence, uncertainties) and
status: "done". Source scripts/lib/common.sh and call validate_report on it. Expect:
exit 0 (valid) — verified live.

Write a second report missing the agent field entirely. Call validate_report on it.
Expect: exit 1 (invalid) — the schema's required list is enforced, not decorative —
verified live.

Write a fake vendor transcript: some free-text prose, then a fenced `json block
containing a valid report object. Call extract_json on the transcript file. Expect: it
prints the fenced JSON object exactly (parses clean via python3 -m json.tool) — this is
the exact mechanism normalize-report.sh's deterministic fast-path depends on before it
ever calls the (paid) Nemotron fallback — verified live.

Final check: report-protocol/SKILL.md's claim "Grok emits it natively via
--json-schema" matches scripts/run-grok.sh, which pipes grok's own --json-schema-
constrained stdout straight to validate_report with no Nemotron step in between — read from
source, consistent.

F6

Verdict-oracle dependency guard

Proves the notebooklm-oracle path fails loudly and exactly when its dependency is missing, rather than silently downgrading a verdict.

Persona / precondition: a machine where notebooklm (the notebooklm-py pip CLI) is NOT installed — true on the reference machine at snapshot time.

0/4

Confirm the precondition: run command -v notebooklm. Expect: not found (this machine has
codex/agy/grok/ollama but never had notebooklm-py installed) — verified live.

Run scripts/ask-notebooklm.sh some-notebook "test claim". Expect: it does not hang or
produce a partial/garbage verdict — exact stderr [orchestra] FATAL: missing dependency:
notebooklm (pip install notebooklm-py && notebooklm login)
, exit 1 — verified live.

Re-read skills/report-protocol/SKILL.md's conductor obligation #4 ("A model cannot
self-finalize 'validated' — the citation is the evidence"). Expect: consistent with step 2
— since the script cannot even start without the dependency, there is no code path where a
claim reaches "validated" without a real NotebookLM citation.

Final check: once pip install notebooklm-py && notebooklm login has actually been run
(not done in this pass — would install software on the host per Article IV consent rules),
re-running step 2 should instead reach the real notebooklm chat call. NOT independently
verified in this pass.

F7

Conductor model-pin doctrine consistency

Proves the workspace's live settings match what CLAUDE.md/AGENTS.md tell every session to believe about who the conductor is.

Persona / precondition: read access to .claude/settings.local.json and this repo's own CLAUDE.md.

0/4

Read .claude/settings.local.json. Expect to find a "model" key. Confirmed present:
"model": "claude-fable-5".

Read CLAUDE.md's routing table. Expect: Conductor | Claude Opus 4.8 | this session: plan,
route, judge, integrate
is a DIFFERENT row from Escalation ceiling | Claude Fable 5 |
wrong-is-expensive, long-horizon core components
— and skills/conductor-routing/
references/routing.json
likewise dispatches the escalation-ceiling tier only to
subagent:fable-core (a worktree-isolated subagent), never to main-session. Confirmed
present as described.

Compare steps 1 and 2 (KNOWN DEFECT, confirmed). Expect: the interactive session's model
pin should match the tier the docs call "Conductor" (claude-opus-4-8 per routing.json's
own tier id), since Fable 5 is documented and dispatched exclusively as the escalation-
ceiling subagent, not the main session. Actual: the live pin is claude-fable-5 — the
escalation-ceiling model — with no corresponding doc update. .claude/
settings.local.json.bak-model-pin
(the pre-change backup, still present in the repo)
confirms this was a deliberate, dated edit that never propagated to CLAUDE.md/AGENTS.md/
routing.json. This currently fails.

Final check: whichever way this is resolved (repin the session to Opus, or update the
three doc surfaces to declare Fable 5 the new conductor tier), CLAUDE.md, AGENTS.md, and
routing.json must all say the same thing after the fix — they are three independent copies of
the same claim today.

F8

Worked-example routing coverage

Proves the one worked example (ClipMark) actually routes through tiers that exist, so a new user copying its pattern doesn't dispatch to a tier the routing table doesn't define.

Persona / precondition: examples/clipmark/TICKETS.md and skills/conductor-routing/references/routing.json both present.

0/4

List every tier id in routing.json's tiers array. Expect exactly ten: escalation-
ceiling, conductor, implementer, recon, codex-worker, codex-bulk, big-context-audit,
cheap-swarm, local-glue, verdict-oracle
— verified live via python3 -m json.load.

For each of the 15 tickets (CLIP-001 … CLIP-015) in TICKETS.md's table, check that its
"Tier → Worker" column names one of the ten tiers from step 1. Expect: 13 of 15 do
(escalation-ceiling, conductor, implementer ×2, codex-worker ×2, codex-bulk, big-context-
audit, cheap-swarm ×2, local-glue ×2, verdict-oracle) — verified live, all match.

Look at the remaining two: CLIP-014 ("second-opinion: Sol via codex, cross-checked by
conductor") and CLIP-015 ("one-shot Pro-tier escalation"). Expect: these are deliberately
descriptive one-off escalations rather than routing-table tier names — consistent with
conductor-routing's escalation ladder being a default path, not the only path, for a
named "nastiest bug budget" ticket. Not a defect; noted so a tester doesn't mistake it for
one.

Final check: every tier actually named in step 2 has a real subagent or script backing it
(fable-core.md, sonnet-impl.md, codex-worker.md + run-codex.sh, gemini-auditor.md +
run-gemini.sh, grok-swarm.md + run-grok.sh, nemotron-local.md + run-nemotron.sh,
notebooklm-oracle.md + ask-notebooklm.sh) — confirmed all seven files exist.

F9

Marketplace plugin manifest shape

Proves the plugin half of the install path (/plugin marketplace add <this-repo>) has a well-formed manifest before a human ever points Claude Code at it.

Persona / precondition: .claude-plugin/plugin.json present.

0/4

Run python3 -m json.tool .claude-plugin/plugin.json. Expect: parses with no error —
verified live.

Inspect the parsed object. Expect fields: name: "wolf-orchestra", a non-empty
description, version: "1.1.0", author.name: "[redacted]", license: "MIT" — all present
as read from the file.

Confirm plugin.json does not itself list commands, agents, or scripts paths.
Expect: it doesn't — consistent with the README's claim that only skills/ auto-
discovers at the plugin root, and agents/commands/scripts "ride the project-copy path"
instead. Read from source; the actual /plugin marketplace add install behavior itself
is NOT independently exercised in this pass (needs a live Claude Code marketplace call) —
confirm manually.

Final check: after a manual /plugin marketplace add + install, skills/conductor-
routing
, skills/handoff-packets, and skills/report-protocol should each appear as
discovered skills — confirm manually.

F10

Kanban board reflects reality

Proves Observify's board for this project is telling the truth about a kit that has no git history and has never run a live session.

Persona / precondition: Observify reachable at http://127.0.0.1:8787.

0/5

GET http://127.0.0.1:8787/api/kanban?project=wolf-orchestra. Expect: "reachable": true
and a projects entry with "id": "wolf-orchestra" — verified live.

Check that project's column_counts. Expect: "backlog": 3 and every other column
(in_progress, running, in_development, testing, done, deployed) at 0
matching the fact that no work has been marked started on this kit — verified live.

Check git_last_touch_ts and freshness for this project. Expect: git_last_touch_ts:
null
and "freshness": "unknown" — a direct, correct consequence of this project having
no .git directory (confirmed in this document's provenance line), not a board bug.

Read the three backlog card texts. Expect one of them to be the literal baseline card
Repo baseline — wolf-orchestra (README.md) and one to include NEXT: run the ClipMark
pilot end-to-end
— verified live, both present.

Final check: none of this GET request created, modified, or deleted anything on the
board — confirmed by re-running step 1 and getting byte-identical column_counts.

F11

Illustrative docs open cleanly

Proves the two files in docs/ — the only visual explanation of the pipeline a new reader gets before their first /orchestrate — are not broken artifacts.

Persona / precondition: a browser and, ideally, a Mermaid-aware viewer (VS Code's Mermaid preview, or pasting into an Artifact).

0/3

Open docs/session-view.html directly in a browser (file:// path is fine — it's fully
self-contained, no external requests). Expect: a dark terminal-styled mock-up titled
"wolf-orchestra — session view" renders with no visible broken layout. Structurally
verified (all HTML tags balanced via a parse check); visual rendering itself NOT
independently confirmed in this pass — confirm manually.

Open docs/workflow.mermaid in a Mermaid-aware viewer. Expect: a flowchart renders with
four subgraphs (CONDUCTOR, DISPATCH, SPECIALISTS, EXT) and no syntax error. Structurally
verified (4 subgraph / 4 matching end — balanced); actual rendering NOT independently
confirmed in this pass (no local Mermaid renderer available) — confirm manually.

Final check: the flowchart's escalation node (ESC{"escalate 1 rung ..."}) and its two
inbound edges (RV -->|blocked / failed| ESC, VF -->|contradicted| ESC) both point at the
same node id — confirmed by reading the source text.

F12

This file is discoverable by Observify's /testit surface

Proves the two-consumer promise in the Founder's request: one authoring pass lights up both the board-book chapter and Observify's own /testit page — verified against the SHIPPED parser, not a re-implementation.

Persona / precondition: this exact file at ~/Projects/wolf-orchestra/USER_TESTING.md.

0/5

Load observify.py's actual _TESTIT_FLOW_RE, _TESTIT_STEP_RE, _TESTIT_FINAL_RE,
_TESTIT_RECORD_RE, _TESTIT_DERIVED_RE, and _testit_parse_text into a scratch
namespace (sliced verbatim from /Users/wolf/Projects/observify/observify.py, not
retyped) and run this file's text through _testit_parse_text. Expect: it returns a
non-empty derived_from string and a flows list of length 12. Verified live — see
session evidence below.

Check each flow's item_count is greater than zero (every flow has at least one
parseable numbered step or Final check). Expect: true for all 12 flows — verified live.

Sum every flow's len(items). Expect this to equal the total item count Observify's own
_testit_parse_text reports in its return value's item_count field — verified live
(see the exact number reported alongside this plan).

Place this file at ~/Projects/wolf-orchestra/USER_TESTING.md (already true — this is
where it was written) and confirm _testit_discover_plans()'s directory-name filter
re.fullmatch(r"[a-z0-9-]+", entry.name) accepts wolf-orchestra as a slug. Expect:
match succeeds (all lowercase, digits, and hyphens) — verified live.

Final check: open Observify's /testit index page in a browser and confirm a
wolf-orchestra card now appears alongside agentify's — confirm manually (this pass
verified the parser accepts the file; it did not drive the live HTTP page).

Not covered and why

[sensitive content omitted]
across Codex, Grok, and (if configured) Gemini/agy for a full pipeline run; F1-F9 verify
every stage of that pipeline's contract in isolation instead, without spending money.

  • NotebookLM verdict oracle end-to-endnotebooklm-py is not installed on the

reference machine (F6 verifies the dependency guard instead of the real call).

  • Live /plugin marketplace add install — needs an interactive Claude Code session

driving its own marketplace UI; F9 verifies the manifest shape it would consume.

  • Fable-core / sonnet-impl subagents actually producing a component — these are Claude

Code subagents invoked from within a live session, not standalone scripts; their contract
(worktree isolation, report-protocol output) is read from source and cross-checked in F5
and F8, not executed here.

  • The SessionStart hook's live injection (kit-remember-hook.sh) — running it exercises

the much larger think-like-fable/tools/remember.py canon-read machinery, which is out of
scope for this project's own USER_TESTING pass; confirmed only that the hook file exists,
is executable, and is wired correctly in .claude/settings.json.

Results

FlowVerdictFailures → tickets
F1 fresh-project bootstrap
F2 worktree lifecycle
F3 vendor CLI flags
F4 async run polling
F5 report validation gate
F6 verdict-oracle dependency guard
F7 conductor model-pin consistency
F8 worked-example routing coverage
F9 marketplace plugin manifest
F10 kanban board reflects reality
F11 illustrative docs open cleanly
F12 /testit self-discovery
updated just nownext 3m 00s