From session-orchestrator
Runs multiple persona agents in parallel against a target file or output, consolidating verdicts via voting-quorum, hard-gate-threshold, or coordinator-summary modes. Use for domain-expert, buyer-persona, or compliance reviews.
How this skill is triggered — by the user, by Claude, or both
Slash command
/session-orchestrator:persona-panelinheritThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Persona Panel runs any number of catalog-defined personas in parallel against a single target
Persona Panel runs any number of catalog-defined personas in parallel against a single target (file, document, or output range). Each persona agent produces a structured verdict. The coordinator consolidates the verdicts into a final result using one of three configurable modes and persists a sidecar record for audit and trend-tracking.
The catalog lives in .claude/personas/*.md — per-repo, never plugin-central. This is
intentional: climate-research repos need physicists; SaaS repos need buyer personas;
compliance repos need auditors. Plugin-central catalogs block that diversity.
The plugin ships one ready-made preset alongside the general templates/personas/ catalog: a
3-lens PM/Designer/Engineer panel at skills/persona-panel/presets/ (pm-lens.md,
designer-lens.md, engineer-lens.md; tier domain-expert). Each lens audits one assumption
dimension — value (PM), usability (Designer), feasibility (Engineer) — as three parallel
domain-expert personas rather than one reviewer working all three serially.
Reach for it on feature/PRD/design reviews in a repo whose .claude/personas/ is empty or
missing and no domain-specific catalog exists yet. It is a starting point, not a substitute —
the per-repo catalog philosophy above still holds; copy and adapt into the project's own
catalog rather than referencing the plugin copy in place.
Install:
mkdir -p .claude/personas
cp "$(claude plugin dir session-orchestrator)/skills/persona-panel/presets/"*.md .claude/personas/
Read skills/_shared/bootstrap-gate.md and execute the gate check. If the gate is CLOSED,
invoke skills/bootstrap/SKILL.md and wait for completion before proceeding. If the gate is
OPEN, continue to Phase 1.
Load the per-repo persona catalog via loadCatalog() from
scripts/lib/persona-panel/catalog-loader.mjs.
Failure modes — all are hard stops:
(a) .claude/personas/ directory missing:
Error (exit 2): .claude/personas/ directory not found in this repo.
Create persona files there to use /persona-panel.
See templates/personas/ for starter templates (issue #458).
(b) .claude/personas/ present but empty (no .md files):
Error (exit 2): .claude/personas/ exists but contains no persona files (*.md).
Add at least one persona file to use /persona-panel.
See templates/personas/ for starter templates (issue #458).
(c) --personas <name> arg specified but name not found in catalog:
Error (exit 1): Persona "<name>" not found in .claude/personas/.
Available personas: <list of names from catalog>.
(d) Malformed YAML frontmatter in a catalog file:
Error (exit 1): Malformed YAML in .claude/personas/<filename>.md at line <N>: <error>.
Fix the frontmatter before running /persona-panel.
Model validation (H2 security guard): The catalog loader validates each persona's model:
field against MODEL_ID_RE + ALLOWED_MODEL_ALIASES from scripts/lib/agent-frontmatter.mjs
at load time. A persona with an invalid model string triggers failure mode (d) with an
informative message: "invalid model '' — must be a Claude model ID or alias
(inherit|sonnet|opus|haiku)".
output_contract structural pre-check (H3 security guard): After YAML parse and before
AJV compile, the loader inspects each persona's output_contract object for forbidden keys:
$ref, $defs, allOf, anyOf. Any occurrence triggers failure mode (d). This structural
pre-check runs BEFORE ajv.compile(). The AJV compile call wraps in a 2-second AbortSignal
timeout to guard against pathological schema inputs.
After successful load, emit a one-line status banner:
Catalog: [N] personas loaded from .claude/personas/. Tier breakdown: domain-expert [N], buyer-persona [N], compliance [N], custom [N].
If --personas <names> was passed, filter to the named subset. Report the active set.
Resolve the <target-path> argument against the project root.
git rev-parse --show-toplevel).validatePathInsideProject(absolutePath, projectRoot) from
scripts/lib/path-utils.mjs. This function performs a two-phase lexical + realpath guard.
--lines <start>-<end>), validate that start ≤ end and both are
positive integers.Store the resolved absolute path as $TARGET.
Dispatch one Agent per persona from the active catalog set.
Model selection per persona:
persona.model is a full Claude model ID (MODEL_ID_RE): use it as-is.persona.model is opus or unset AND persona.tier == 'domain-expert': override to
claude-opus-4-7 (empirically validated — Opus finds real problems Sonnet misses; see vault
learning [[persona-opus-finds-real-failing-cibadge]]).Agent dispatch contract:
Agent({
subagent_type: "general-purpose",
model: <resolved model ID>,
prompt: <buildPersonaPrompt(persona, $TARGET, targetContent, groundingMode)>,
tools: ["Read", "Grep", "Glob"]
})
Use buildPersonaPrompt(persona, target, targetContent, groundingMode) from
scripts/lib/persona-panel/persona-runner.mjs to compose the prompt. The runner wraps
evaluation_criteria entries in <persona-criteria>...</persona-criteria> delimiters (security
M1: persona body is treated as data, not free-form instructions; see persona-format.md for the
full rationale).
Grounding Mode (optional, --grounding <off|re-derive>, default off, #730 Epic H): when
re-derive, groundingMode='re-derive' is passed to buildPersonaPrompt, which inserts a
<grounding-instruction> block before <target-content> instructing the persona to
independently re-derive supporting sources via Read/Grep/Glob rather than trusting a "Sources"
section the target may already assert, and to report them as derived_sources in its JSON
output. See persona-format.md § "Grounding Mode (optional)" for the full contract. v1 is
advisory-only: diffGroundingSources() in consolidator.mjs never influences final_verdict.
Concurrency cap (security M2): Maximum 20 personas per panel run. If the active set exceeds 20, emit a warning and truncate to the first 20 alphabetically:
Warning: Persona set truncated to 20 (cap). Omitted: <names>.
run_in_background: false for all agents. Do not proceed to Phase 4 until ALL agents
complete.
Dispatch summary line (before dispatch):
Dispatching [N] persona agents in parallel. Target: <$TARGET>. Mode: <consolidation-mode>.
After all agents complete, run consolidation via scripts/lib/persona-panel/consolidator.mjs.
Three consolidation modes (set by --mode arg, default: voting-quorum):
voting-quorum (default)Deterministic M-of-N threshold. Default M = ceil(N / 2) + 1 (simple majority). Override with
--quorum <M>.
verdict == "pass"."pass"."fail"."fail" (ties go to FAIL).hard-gate-thresholdStrict M-of-N where default M == N (unanimity). Override with --threshold <M>.
"pass"."fail": final-verdict = "fail"."warn" and no failures: final-verdict = "warn".coordinator-summaryLLM aggregate via coordinator. The coordinator reads all persona outputs and produces a synthesized summary verdict.
WARN (required — emit to BOTH stderr AND sidecar consolidation.aggregator_warning):
Warning: coordinator-summary mode triggers an additional LLM call (the coordinator aggregation
step). This incurs extra token cost. Use voting-quorum or hard-gate-threshold for deterministic,
zero-extra-LLM-call consolidation.
For each persona output, parse the structured block (see persona-format.md Output Contract).
Validate that verdict ∈ {"pass", "fail", "warn"} — if a persona output lacks a valid verdict,
treat it as "fail" and record it in dissenting_personas with reason "missing-verdict".
Emit a consolidation summary:
Consolidation ([mode]): [pass-count] pass / [fail-count] fail / [warn-count] warn — Final: <verdict>
Dissenting: <names> (if any)
Write the sidecar record and emit the final report.
Run ID generation (H1 security guard):
const runId = randomUUID().slice(0, 8); // format: [a-z0-9-]{8}
Validate: runId MUST match /^[a-z0-9-]{1,64}$/. Reject and regenerate if it does not.
Timestamp format for filename: ^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}(\.\d+)?Z?$
(filename-safe ISO — colons replaced with hyphens).
Example: 2026-05-20T14-30-00Z-a1b2c3d4.json
Path: .orchestrator/persona-panel/<isoTs>-<runId>.json
Validate the sidecar target path with validatePathInsideProject(sidecarAbsPath, projectRoot)
(H1 path guard) before writing.
Schema validation (security M3 — validate BEFORE write):
Validate the sidecar object against agents/schemas/persona-panel-sidecar.schema.json using
validateAgentOutput() from scripts/lib/agent-output-schema.mjs (AJV 2020-12). If
validation fails: print the AJV errors to stderr and exit 1 — never write an invalid sidecar.
Write via writeJsonAtomic() from scripts/lib/io.mjs (atomic tmp-then-rename to prevent
partial writes).
Sidecar schema shape (matched by agents/schemas/persona-panel-sidecar.schema.json):
{
"run_id": "<string, 1-64 chars [a-z0-9-]>",
"target": "<absolute path to reviewed file>",
"personas_invoked": [
{
"name": "<persona name>",
"version": "<string>",
"model": "<full model ID used>",
"prompt_hash": "<sha256 of canonicalized prompt — see persona-format.md>",
"timestamp_start": "<ISO 8601>",
"timestamp_end": "<ISO 8601>",
"token_usage": {
"input": "<integer>",
"output": "<integer>",
"cache_read": "<integer>",
"cache_creation": "<integer>"
}
}
],
"outputs": [
{
"persona_name": "<string>",
"verdict": "<pass|fail|warn>",
"rationale": "<string, max 4096 chars>",
"recommendations": ["<string>"],
"derived_sources": [
{ "path": "<string, optional — only when groundingMode='re-derive'>", "supports_claim": "<string, optional>" }
]
}
],
"consolidation": {
"mode": "<voting-quorum|hard-gate-threshold|coordinator-summary>",
"final_verdict": "<pass|fail|warn>",
"pass_count": "<integer>",
"fail_count": "<integer>",
"warn_count": "<integer>",
"dissenting_personas": ["<name>"],
"audit_reason": "<string>",
"aggregator_warning": "<string | null>",
"grounding_diff": {
"unconfirmed_author_sources": ["<string, optional — only when authorSources was supplied>"],
"newly_derived": ["<string>"],
"personas_reporting": "<integer>"
}
}
}
Token-usage contract (H4): Each personas_invoked entry records token_usage from the
Anthropic API response: { input, output, cache_read, cache_creation }. Agents that do not
return usage data record all fields as 0.
Emit to stdout:
## Persona Panel Report
Target: <$TARGET>
Personas: <N> invoked | Mode: <mode>
Final verdict: <PASS|FAIL|WARN>
| Persona | Tier | Verdict | Rationale (excerpt) |
|---------|------|---------|---------------------|
| <name> | ... | pass | ... |
| <name> | ... | fail | ... |
Dissenting: <names or "none">
Sidecar: .orchestrator/persona-panel/<filename>.json
If final_verdict == "fail": exit with code 1 so CI and wave-executor hooks can gate on the
result. If final_verdict == "warn": exit 0 with a warning line on stderr. If
final_verdict == "pass": exit 0.
validatePathInsideProject for the target path OR the sidecar output path (H1).run_in_background: true for persona agents — lose coordination ability.model: fields from the catalog against MODEL_ID_RE + aliases (H2).output_contract structural pre-check before ajv.compile() (H3).aggregator_warning to BOTH stderr and sidecar when using
coordinator-summary mode."fail" — never silently skip or default to pass.--personas
restricts deliberately. The value is the N-dimensional view.voting-quorum — record them in the sidecar even when the
majority passes. They are the signal for trend-tracking (#459).ajv.compile() without the AbortSignal timeout — pathological schemas can block the
event loop indefinitely.commands/persona-panel.md — argument parsing and CLI contractagents/schemas/persona-panel-sidecar.schema.json — sidecar JSON Schema (Draft 2020-12)scripts/lib/persona-panel/catalog-loader.mjs — loadCatalog() implementationscripts/lib/persona-panel/persona-runner.mjs — buildPersonaPrompt() implementationscripts/lib/persona-panel/consolidator.mjs — consolidation logic (3 modes)skills/persona-panel/persona-format.md — persona file format specificationskills/wave-executor/wave-loop.md — Persona-Gate hook (Phase 5b/3b, added in #458)scripts/lib/path-utils.mjs — validatePathInsideProject()scripts/lib/io.mjs — writeJsonAtomic()scripts/lib/agent-frontmatter.mjs — MODEL_ID_RE, ALLOWED_MODEL_ALIASESnpx claudepluginhub kanevry/session-orchestrator --plugin session-orchestratorOrchestrates simulated reader panel reviews: spawns persona agents to read document chapters, capture reactions via mood journals, enforce review gates, and synthesize editorial recommendations.
Reviews and critiques prose planning documents (plans, specs, PRDs, requirements, design docs) using multi-persona analysis to produce structured findings.
Reviews requirements, plans, or specs through multi-persona analysis with role-specific lenses. Applies auto-fixes and routes findings for user decision.