From bigspin
You're the orchestrator. The user invoked `/persona` (installed plugin) or
How this skill is triggered — by the user, by Claude, or both
Slash command
/bigspin:personaThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You're the orchestrator. The user invoked `/persona` (installed plugin) or
analysis/archetypes.pyanalysis/cards_preview.pyanalysis/compute_metrics.pyanalysis/interpret.mdanalysis/render/__init__.pyanalysis/render/cli.pyanalysis/render/hero_md.pyanalysis/render/html.pyanalysis/render/markdown.pyanalysis/render/templates/_logo_mark.svganalysis/render/templates/_logo_primary.svganalysis/render/templates/assets/fonts/PPNeueMachinaInktrap-Regular.woff2analysis/render/templates/assets/illustrations/Multi-Mode Journeyman.pnganalysis/render/templates/assets/illustrations/Pair Programmer.pnganalysis/render/templates/assets/illustrations/Prompt Minimalist.pnganalysis/render/templates/assets/illustrations/Quick-Turn Sprinter.pnganalysis/render/templates/assets/illustrations/Runtime Mechanic.pnganalysis/render/templates/assets/illustrations/Showrunner.pnganalysis/render/templates/assets/illustrations/Spec-First Architect.pnganalysis/render/templates/report_wrapped.html.j2You're the orchestrator. The user invoked /persona (installed plugin) or
asked you to follow this skill from a local clone, and wants you to run the
analysis. Walk through the steps below, keeping the user in the loop at the
decision points. Everything stays local — no API key required, and no data
leaves the machine.
All per-run files are written under $OUT_DIR
(~/.claude/bigspin/persona-<timestamp>/), established by the shared run
contract in Setup below — never in the repo or the current directory.
The full voice + content rules live in $PERSONA/analysis/interpret.md. Read
that after you have $OUT_DIR/metrics.json + $OUT_DIR/findings.md and
before you write $OUT_DIR/report_content.json. Don't skip it.
All script paths are under $PERSONA; all I/O paths are under $OUT_DIR.
preprocessing/sessions_to_csv.py → sessions.csv (1 row/session)
preprocessing/enrich.py → sessions_enriched.csv (+ deterministic signal cols)
[STRUCTURED TRACK — for positioned numbers]
tagging/tag_sessions.py → tag_prompt.md, transcripts/, transcripts/_manifest.json
[you orchestrate subagents] → annotations/<session_id>.json
tagging/tag_sessions.py --assemble → tagged_sessions.csv
analysis/compute_metrics.py → metrics.json
[OPEN TRACK — for rich behavioral observation]
[you orchestrate one open subagent] → findings.md
[AUTHORING — synthesis between both tracks]
[you author] → report_content.json
analysis/render_report.py → persona-report.html, persona-report.md,
persona-hero.md, persona-hero-card.txt,
persona-hero-card.plain.txt
The output is three artifacts sharing copy verbatim: an HTML report, a
CLI hero card, and a markdown report. All three render from a single
report_content.json.
The pipeline has three layers now, not two:
enrich.py) — ~14 structural columns from
message structure. Iteration counts, tool diversity, tests_attempted.
Free, instant, fully reproducible.The structured track gives the report its archetype label, fingerprint gauge, and within-cohort bars. The open track gives the report its recognition lines, sensitivity framing, and experiments. Neither alone produces a credible report — the synthesis does.
You are running this for the user, on their data, on their machine. No data leaves this directory. (An older API-tagging path exists as a fallback — see "Legacy: API tagging" at the bottom — but the default flow is fully local.)
The corpus baselines were tagged with Claude Opus 4.7. For within-cohort positioning to be calibrated, your subagents must run on the same model. Claude Code subagents inherit the parent session's model — so this happens automatically when the user invoked you from an Opus 4.7 session.
Before starting Step 4, verify which model you're on. If the user is on a different model (e.g., Sonnet 4.6, Haiku 4.5):
interpret.md's edge-case section so the report can hedge precision
claims.The model dependency exists because Sonnet and Opus read the taxonomy's
categorical rubrics differently (especially interaction_style —
Sonnet defaults to mixed, Opus to augmentative). Empirically:
holding model constant, API and subagent paths produce ~5pp drift on
average. Switching models produces ~11pp drift. Model-pinning matters
more than delivery-pinning.
Resolve the plugin root. Prefer $BIGSPIN_PLUGIN_ROOT; if unset, walk up
from this file (two levels: skills/persona/SKILL.md → plugin root). Path
shorthands used below:
$PERSONA = $BIGSPIN_PLUGIN_ROOT/skills/persona (read-only code,
fixtures, baselines, templates).$SCRIPTS = $BIGSPIN_PLUGIN_ROOT/scripts (shared run contract + helpers).Start the run with the shared entrypoint:
eval "$(bash "$SCRIPTS/new_run.sh" persona)"
new_run.sh is the single source of truth for the run contract: it
bootstraps the shared venv, creates the output directory, and exports PY
(venv interpreter), RUN_ID (persona-<timestamp>), OUT_DIR
(~/.claude/bigspin/$RUN_ID, already created), plus BIGSPIN_PLUGIN_ROOT
and PYTHONPATH. Every file this skill writes — intermediate CSVs, the
user's exported transcripts, annotations, and the final report — goes under
$OUT_DIR, never in the repo or cwd. If it exits non-zero, surface stderr
and stop (exit 2 = no uv/python3, exit 3 = venv/pip failure).
Setup already bootstrapped the venv ($PY). Confirm the renderer works
before touching the user's data:
"$PY" "$PERSONA/tests/smoke_test.py"
Free, fast, < 1 second. Confirms the renderer can produce all five artifacts from the schema + the Showrunner exemplar + the Multi-Mode Journeyman edge case. If it fails, stop and surface the error to the user.
Confirm the user wants to proceed. Show them what you're about to do and ask if they want to override defaults:
~/.claude/projects
for the structured track. (Down from 50 in the old API path —
subagents read sequentially within a batch, so smaller batches keep
wall-clock time and token usage reasonable. 20 is enough for a stable
archetype label; fewer sessions add statistical noise to the
within-cohort bars, not bias.)project values from _manifest.json so
the open pass still spans contexts; if the recent sample clusters in one
project, that breadth is inherently limited and that's fine.~/.claude/projects (override with
CLAUDE_SESSIONS_ROOT or a path arg)."$PY" "$PERSONA/preprocessing/sessions_to_csv.py" \
--out "$OUT_DIR/sessions.csv" --min-messages 5
Or with a custom sessions root:
"$PY" "$PERSONA/preprocessing/sessions_to_csv.py" /path/to/sessions \
--out "$OUT_DIR/sessions.csv" --min-messages 5
Surface the script's summary back to the user — parent count, subagent count, date range, p95 message size. If p95 is over ~500 KB, mention that and ask if they want to filter (rare, usually fine).
"$PY" "$PERSONA/preprocessing/enrich.py" "$OUT_DIR/sessions.csv" \
--out "$OUT_DIR/sessions_enriched.csv"
Surface the per-script summary — it lists the 14 column names added.
This step replaces the old API call with Claude Code subagents. It has
five sub-steps; the helpers all live in tagging/tag_sessions.py.
"$PY" "$PERSONA/tagging/tag_sessions.py" --export-prompt "$OUT_DIR/tag_prompt.md"
"$PY" "$PERSONA/tagging/tag_sessions.py" "$OUT_DIR/sessions_enriched.csv" \
--export-transcripts "$OUT_DIR/transcripts/" \
--limit 20
The first line writes the ~20 KB system prompt (built from
$PERSONA/tagging/taxonomy.json) to $OUT_DIR/tag_prompt.md. The second
writes one <session_id>.txt per selected session into
$OUT_DIR/transcripts/, plus a _manifest.json that the assembler reads
later.
Everything up to here was free — local Python, no model tokens. Tagging is the first step that spends tokens, so stop here and get the user's explicit consent before going further.
Run the estimate (still free, no tokens spent):
"$PY" "$PERSONA/tagging/tag_sessions.py" --run-estimate "$OUT_DIR/transcripts/"
It prints the structured sample size (call it N) and an estimated
whole-run token band (call it X–Y million). Show the user this message,
filling in N and X–Y from the command output verbatim:
/personabuilds a personalized practice report by having Claude Code subagents read your recent session transcripts locally. Nothing leaves your machine. I've run an estimate and this run will analyze your last N sessions. Estimated token usage: ~X–Y million tokens for this run (it runs in your Claude Code session). Reply "yes, I understand this will use ~X–Y tokens" to proceed, or "no" to cancel.
Then STOP and wait for the user's reply. Do not spawn any tagging subagent (4c) or run the open behavioral pass (Step 6) until the user replies with an explicit affirmative that acknowledges the token usage (e.g. "yes, I understand…"). A bare "ok", silence, or any reply that doesn't acknowledge the cost is not consent — re-ask or stop. If the user declines, stop here: the files written so far are local scratch and cost nothing.
Spawn N subagents in parallel — typically 4 subagents × 5 transcripts
each for a 20-session run. Use the Explore subagent type or
general-purpose; they need read access to $OUT_DIR/transcripts/ and
write access to $OUT_DIR/annotations/ (create it first:
mkdir -p "$OUT_DIR/annotations/"). (compute_metrics.py assumes no fixed
session count, so other splits are fine.)
Each subagent's prompt:
Your job is to tag Claude Code session transcripts against a fixed
taxonomy. Read the system prompt at `$OUT_DIR/tag_prompt.md` end to end —
it defines the output schema, the categorical fields, and per-signal
rubrics with strength anchors.
For each transcript file in this batch:
<list of absolute paths to $OUT_DIR/transcripts/<session_id>.txt>
Read the FULL transcript, then write the JSON annotation to
`$OUT_DIR/annotations/<session_id>.json`. The JSON must conform to the
schema described in tag_prompt.md (the OUTPUT FORMAT section). No
markdown fencing, no commentary — just the JSON object.
Precision over recall: tag a signal only if you see clear evidence.
Every fired signal must include `evidence`. Anti-pattern signals are
presence-only. Reality-contact signals require `trigger` and
`surface_type`.
Return when all transcripts in the batch have annotation files written.
The subagents work in parallel. Each session takes ~30–90 seconds of subagent time depending on transcript length.
"$PY" "$PERSONA/tagging/tag_sessions.py" \
--assemble "$OUT_DIR/annotations/" \
--manifest "$OUT_DIR/transcripts/_manifest.json" \
--out "$OUT_DIR/tagged_sessions.csv"
This emits the canonical tagged_sessions.csv — same shape
compute_metrics.py already consumes. Reports ok / missing / invalid
counts. If the missing count is > 10% of the batch, re-spawn a subagent
to fill the gaps before continuing.
A spot-check is worth doing: read 2–3 random files from
$OUT_DIR/annotations/ and confirm they conform to the schema (all
required top-level fields present, signals have evidence strings,
reality-contact signals have trigger + surface_type). The renderer
won't catch malformed annotations until much later.
"$PY" "$PERSONA/analysis/compute_metrics.py" "$OUT_DIR/tagged_sessions.csv" \
--raw "$OUT_DIR/sessions.csv" \
--out "$OUT_DIR/metrics.json"
Pure stdlib, fast. Same as before. Output is the JSON file the renderer
consumes for numbers via *_ref paths.
This is the new track. Spawn one general-purpose subagent (or the
persona-tagger agent in open mode). Hand it ~12 of the cleaned
transcript files already exported in Step 4a — pick a cross-project
subset by maximizing distinct project values in
$OUT_DIR/transcripts/_manifest.json. Reusing those stripped transcripts
(rather than re-reading raw ~/.claude/projects/*.jsonl) is the main
token saving: the open pass produces qualitative findings.md that is
never positioned against the baseline, so reading the cleaned export
can't move any calibrated number. The subagent's prompt:
You're characterizing a user's Claude Code practice — what's distinctive
about how they work, not what's generic-software-engineer-y. You have
~12 cleaned session transcripts to read, listed below (tool-output
payloads are stripped; the conversation is intact). Read enough of each
to form a behavioral picture; you don't need to read every line.
Sessions:
<list of absolute paths to $OUT_DIR/transcripts/<session_id>.txt>
(a cross-project subset of the exported transcripts)
Return findings as markdown to `$OUT_DIR/findings.md` with these sections:
## Character
One sentence describing how this user collaborates with Claude. Vivid,
specific, second-person.
## Three distinctive patterns
Three behavioral patterns that are this user's, not generic. Each:
- One-line claim
- 2–3 specific session anchors with session_id and brief paraphrase
(NOT verbatim quotes — paraphrase patterns)
- Why it's distinctive (vs. what most users do)
## Sensitivity
Where this user's practice is fragile — what they'd lose if a session
went sideways. Identify the *mechanism*, not just the missing tool-use.
## Two experiments
Two concrete moves to try. Each:
- One-sentence move name (imperative)
- Why this user specifically — anchored to a pattern from above
- Specific action in their next session
Be sharp, not flattering. The user wants recognition, not reassurance.
No verbatim transcript quotes — paraphrase. No percentages — those
belong on the structured side.
The output $OUT_DIR/findings.md is what carries the rich behavioral
observation into Step 7. This is the part the structured tagger can't do
because the schema is a ceiling on what it can find.
Now read $PERSONA/analysis/interpret.md end to end. It has the voice rules,
structural contract, Y-vocabulary table for picking the title verb, and
the new section on synthesizing across structured + open tracks.
Then author $OUT_DIR/report_content.json:
metrics.json via *_ref paths. (Same as before.)findings.md. The open pass authored these specifically; lift
them with light editing for voice.The schema is at $PERSONA/analysis/report_content.schema.json. The renderer
validates and exits non-zero on failure.
You write strings. The renderer fetches numbers via *_ref paths.
Don't round-trip arithmetic through your head.
Pre-flight checklist (also in interpret.md):
kind: "high" + one kind: "contrast".findings.md, not invented.findings.md's "Two experiments" section,
cross-referenced with the structured track's borrow_from. Each
attributed to a named archetype."$PY" "$PERSONA/analysis/render_report.py" \
--content "$OUT_DIR/report_content.json" \
--metrics "$OUT_DIR/metrics.json" \
--out "$OUT_DIR" \
--slug persona
Validates the content JSON, resolves all *_ref paths, writes (all under
$OUT_DIR):
persona-report.html — full reportpersona-report.md — markdown versionpersona-hero.md — chat-paste summary (the inline deliverable)persona-hero-card.txt — CLI hero card with ANSIpersona-hero-card.plain.txt — same, no ANSIIf validation fails or a *_ref doesn't resolve, the renderer prints a
diff and exits non-zero. Fix the content JSON and re-run.
Open the report in the browser:
bash "$SCRIPTS/open_report.sh" "$OUT_DIR/persona-report.html"
Then paste the contents of $OUT_DIR/persona-hero.md inline in the chat.
That is the deliverable — a tight markdown summary the user can read in
place without opening anything. Then a single CTA:
Open
$OUT_DIR/persona-report.htmlfor the full read.
Stop there. No file inventory, no cat'ing the ANSI hero card, no
"want me to dive into…" follow-up question. The user can ask if they
want more.
If the user later asks "is this private?" — yes, fully. With the
subagent path, no data leaves the machine; all output stays under
$OUT_DIR. They can verify by reading $PERSONA/tagging/tag_sessions.py
and $PERSONA/analysis/compute_metrics.py.
ls ~/.claude/projects.interpret.md to flag this in the
report.findings.md reads as generic, the
subagent likely under-sampled or skimmed. Re-spawn over the same ~12
transcripts and emphasize "specific session anchors required"; if the
subset clustered in one project, widen it to more distinct project
values from _manifest.json.compute_metrics.py runs anyway; the renderer
drops cohort comparison bars; interpret.md's edge-case section
describes how to author content JSON when comparisons are
unavailable.The API-based tagger still works:
"$PY" "$PERSONA/tagging/tag_sessions.py" "$OUT_DIR/sessions_enriched.csv" \
--out "$OUT_DIR/tagged_sessions.csv" \
--limit 50 \
--model claude-opus-4-7 \
--dry-run # cost estimate first
"$PY" "$PERSONA/tagging/tag_sessions.py" "$OUT_DIR/sessions_enriched.csv" \
--out "$OUT_DIR/tagged_sessions.csv" \
--limit 50 \
--model claude-opus-4-7 \
--yes
Pin the model to match the corpus baseline (Opus 4.7). The default
in tag_sessions.py is Sonnet 4.6 for historical reasons — explicitly
pass --model claude-opus-4-7 for calibrated output.
Cost: ~$25–30 for 50 sessions on Opus 4.7 (~5× Sonnet). Requires
ANTHROPIC_API_KEY. Slightly faster than the subagent path (1–3 min)
and has a small failure mode of its own — Opus occasionally responds in
text instead of via the enforced tool call (~1 in 50 sessions in our
testing). The subagent path is the default because it's free, local,
runs without a key, and avoids the tool-enforcement edge case.
findings.md is the source.baselines/ are the
source of truth.findings.md.
Paraphrase patterns; verbatim feels like surveillance.Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub bigspinai/toolkit --plugin bigspin