role-x
bstack P17 — Lens-Routed Request Articulation. The typed routing layer above P5 parallel-agent dispatch. On every substantive user input, the first-touch agent reflexively selects role lens(es) from `
SKILL.mdskills/orchestration/role-x/SKILL.md
GitHubrole-x — bstack P17 — Lens-Routed Request Articulation
Primitive: P17 in the Broomva Stack. See broomva/workspace AGENTS.md §P17 for the full reflexive trigger rule and the design spec at docs/superpowers/specs/2026-05-13-role-x-primitive-design.md.
What this skill provides
- Lens registry:
roles/_meta.md(always-loaded base) +roles/<name>.mdper-domain lenses. Each lens has YAML frontmatter (signals, context_loaders, quality_bar, prompt_improvement_patterns, default_mode, mode_escalation, out_of_scope) and a prose body. - Selection algorithm (reasoning-enforced): score each lens against current signals (paths, prompt_keywords, branch_patterns, linear_labels); threshold ≥ 2 matches; resolve
extends:chain. - Mode selection (reasoning-enforced):
augment(silent context load — default),rewrite(surfaced prompt refinement),decompose(P5 parallel-agent plan, user-approved). Mode escalation per the lens'smode_escalationfield. - CLI helpers (
scripts/role-x.py):role-x list— list all available lenses with status + extends + default_moderole-x validate <path>— validate lens YAML frontmatter against schemarole-x index— regenerateroles/_index.mddiscovery filerole-x intake(v0.2.0) —UserPromptSubmithook entry pointrole-x suggest(v0.4.0) — analyze events.jsonl; surface fire-rate + drift + emergent clustersrole-x init <name>(v0.4.0) — scaffold astatus: candidatelens from CLI flagsrole-x coverage(v0.4.1) — brief registry-health summary; silent when healthy (SessionStart hook entry point)role-x reflexes route(v0.7.0) — the reflex router offline: one prompt, or--evalsover every skill's trigger-eval set
- Hooks (
scripts/*-hook.sh):role-x-intake-hook.sh(v0.2.0) —UserPromptSubmitwrapperrole-x-coverage-hook.sh(v0.4.1) —SessionStartwrapper with 24h cooldown
- Reference docs (
references/):lens-schema.md— YAML frontmatter field referenceselection-algorithm.md— scoring algorithm in detailmode-selection.md— augment/rewrite/decompose decision treefeedback-loop.md— Nous-pattern telemetry + P13 consolidation (M2+)
When to invoke
Intake reflex (every prompt)
Always — at the start of every session, before responding to substantive user input. P17 is a reflexive primitive. The skill exists to make the lens registry and CLI helpers discoverable; the behavior is enforced by reasoning + the UserPromptSubmit hook.
Carve-outs (no role-x intake needed): single-line typo fixes, pure read questions ("what does this function do?"), conversation continuation without new substantive request.
The intake block's "Task-relevant knowledge" list holds at most 5 catalog entities. ROLE_X_TASK_ENTITY_TOP_N=<n> (0–50) in the hook's environment changes that cap; a bad value means 5. It only shortens or lengthens the same ranked list, and it exists for the context-ablation evals' compression arm (scripts/skill_evals/ctx_ablation/).
Reflex output (ROLE_X_OUTPUT, v0.7.0, default off)
ROLE_X_OUTPUT in the hook's environment chooses what the intake hook injects. Unset, empty or unknown is legacy, today's block, so nothing changes until it is set. ROLE_X_MODE is read as an alias when ROLE_X_OUTPUT is unset.
| Value | Injects | Logs |
|---|---|---|
legacy |
the lens block (above) | the intake row |
reflex |
at most 3 lines under [bstack reflexes], ≤ 600 characters, or nothing |
a reflex row |
shadow |
the lens block | the intake row and the reflex row it would have injected |
qbar |
the lens block cut to header, lenses, mode and quality bar | the intake row, render: qbar |
The reflex router (scripts/reflex_router.py) reads references/reflexes.yaml: each entry is trigger clauses → one line that names the command → the source that states the rule. Per clause it works cheapest first, and each stage only narrows:
- The prompt: the catalog's regexes, the phrases copied from each skill's description, and named routes such as
change_work. Pure regex, no I/O. - State: for a clause with a prompt side, only once that matched; a state-only clause (a push the reflog shows, staged changes on
main) reads it on every prompt. Sources: onegit status --porcelain=v2 --branch, one reflog tail, and ctx-core'sboard.jsoncache (never its log), each read at most once. A failing source costs only its own predicate. ROLE_X_JEV: the seam for a typed classifier;offin v1.
State still ranks first: state+prompt fires, then state, then prompt. The p9 rule is pinned (spec I1): it fires on change work, on push or "open a PR" wording, after a fresh push, or when the whole prompt is a go-ahead ("ok", "yes please") with unshipped work; whenever it fires it takes the first slot, and neither the repeat cap nor a narrower drops it.
No persona lines, no entity list. Lines state facts ("after a push, the stack runs p9 watch <pr> --background"), never orders. A line goes out at most twice per session for the same fact (a small per-session file under ~/.config/broomva/role/reflex-sessions/; shadow counts too). Any error prints nothing and logs its class; a state source that fails costs only its own predicate.
Only status: routed entries are injected. An entry routes when its prompt side clears the sealed held-out routing cases (evals/reflex-routing-heldout-v2.json: 10 should-route and 5 near-miss prompts per id, 40 and 20 for change_work, written blind and hashed before any scoring or tuning): recall ≥ 0.60 and false fire ≤ 0.20 (change_work, which the p9 pin rests on: ≥ 0.80 on ≥ 40), which a test enforces. The v1 cases (3 and 2 per id) are kept and no longer gate. Entries below that bar are listed, with the measurement in m3:; judgment-call triggers are judgment. Both stay in the catalog so the gap is visible. role-x reflexes shadow --since <install time> reads the spec's A1 gate off ROLE_X_OUTPUT=shadow rows: router_ms p99, error rows, and p9 on the turns that ran git push or gh pr create. role-x reflexes route --prompt "…" shows what a prompt would get, --heldout scores the sealed cases, and --evals scores each skill's own evals/prompts.json (in-sample, since the phrases come from the same descriptions). Design of record: broomva/workspace/docs/specs/2026-09-30-reflex-router-and-ontology-ranked-context.html §5 (BRO-2674). Eval: scripts/skill_evals/ctx_ablation/RESULTS-reflex.md (sonnet v1, then the pre-flip steps and opus).
Meta-progression discipline (v0.4.1+)
The intake reflex routes prompts in real-time. The meta-progression discipline ensures the registry itself grows from real telemetry:
| When | Action | Cadence |
|---|---|---|
| SessionStart in a workspace with the role-x coverage hook wired | role-x coverage --since 7d fires automatically; surfaces fire-rate + config hints when registry health drops |
≤1 nudge per 24h |
| Per substantive prompt | If intake routes to _meta only AND prompt is domain-rich (≥8 words, ≥4 distinct meaningful tokens) |
Agent sees a 1-line role-x init <slug> suggestion appended to the intake context |
When the agent observes a recurring _meta-only pattern within a session (e.g. 3+ unrouted prompts about the same domain) |
Propose role-x init <name> to the user as the rule-of-three trigger |
At the agent's discretion, surfaced as a one-line note |
| Weekly (or after collecting ~50+ events) | Run role-x suggest --since 7d for the full report — fire-rate, per-lens drift, emergent keyword clusters (requires sanitized capture on) |
Manual, with telemetry signal from the SessionStart nudge |
After ≥3 positive-outcome uses of a status: candidate lens |
Author promotes the lens to status: active (P16 rule-of-three) |
Manual, candidate ledger tracks instances |
When NOT to invoke meta-actions
- Single-prompt sessions where the intake nudge is purely informational — don't pause work to author lenses mid-flow
- Edits to existing active lenses unless
role-x tune <lens>(v0.5.0+) surfaces concrete drift signals - New lenses without ≥3 distinct-session evidence in events.jsonl (avoids cargo-cult lens proliferation)
When NOT to invoke
- One-shot read questions answered from context alone
- Conversation-only exchanges (no work to execute)
- Brainstorming / design discussion (use
superpowers:brainstorminginstead; role-x kicks in once implementation begins)
Composition with other primitives
| Primitive | How role-x composes |
|---|---|
| P5 parallel agents | role-x decompose mode produces the dispatch plan; each sub-agent runs role-x at its scope (typed edges in the reasoning graph) |
| P13 dream cycle | lens consolidation via role-x-replay.py (M4 — planned, not yet shipped) — gather → replay → prune → consolidate → index |
| P14 dep-chain | the lens's quality_bar IS the domain-specific P14 enumeration template |
| P15 state snapshot | feeds the selection algorithm's signals |
| P16 bstack engine | new lenses get promoted via per-lens rule-of-three (≥3 positive-outcome uses → status: active) |
/autonomous |
seeds roles/_meta.md content; remains invocable for the full reflex pipeline |
persona-* skills |
referenceable from lenses via context_loaders.skills:; not replaced |
How to use
- Start of session — agent reads
roles/_meta.md+ allroles/*.md(cached in working context). At UserPromptSubmit for substantive work, agent reasons through:- Snapshot signals (P15): current branch, touched files (
git diff --name-only), prompt content, Linear ticket if any - Score each lens against signals (≥2 matches → applies)
- Resolve
extends:chain; merge context_loaders + quality_bar - Choose mode (augment / rewrite / decompose) per the lens's
default_mode+mode_escalationrules - Surface mode + selected lenses to user unless mode is augment
- Proceed with the user's request, applying the lens's quality_bar as the P14 dep-chain trace
- Snapshot signals (P15): current branch, touched files (
- Adding a new lens — author
roles/<name>.mdfollowing the schema inreferences/lens-schema.md; runrole-x validate roles/<name>.md; runrole-x indexto regenerateroles/_index.md; commit on a worktree branch; PR. - Lens consolidation (M4 — planned, not yet shipped) —
python3 scripts/role-x-replay.py <lens-name>runs the P13 dream cycle against~/.config/broomva/role/events.jsonl.
Cardinal invariant
No
act as Xpersona rewrites. Lenses load substantive context (files, conventions, checklists, optional suggestions). They do not insert persona declarations into the model's working context. The 2026 research (PRISM USC, Zheng et al. arXiv 2311.10054, Anthropic best-practices) is clear: persona declarations don't add expertise and frequently hurt accuracy for code/factual tasks (MMLU drops 71.6% → 66.3% with long expert personas).
Persona federation (F3′ — the confined 2nd root)
context_loaders.entities normally resolve workspace-relative (Phase 2). Federation lets persona facets (research/entities/persona/<slug>.md, type: persona) live in a per-user store — ~/.config/broomva/persona/ — so the same identity rides every turn from any workspace, without weakening workspace confinement. It is OFF by default and enabled only via trusted user config:
// ~/.config/broomva/role/config.json (trusted user-level — a workspace can never set this)
{
"persona_federation": {
"root": "~/.config/broomva/persona", // fixed under ~/.config/broomva; canonicalized
"workspaces": { // the workspace→persona binding (an allowlist)
"/abs/path/to/workspace": ["railway-deploy-default", "auth-better-auth"]
}
}
}
Security contract (design §3 S0-result — enforced in role-x.py): fd-based no-follow read (single O_NOFOLLOW open relative to a root dir-fd; fstat + read from the same fd — TOCTOU-safe, never reopened by path) · workspace→persona binding (_meta slugs are lookup keys constrained to the workspace's allowed set, not free paths) · ancestry + ownership (no symlink/foreign-owner/group-or-world-writable dir in the chain) · hardlink reject (st_nlink == 1) · persona-type-only. A workspace that isn't allowlisted surfaces nothing (a hostile clone can't load your identity); a listed facet missing from the store warns loudly — it is never silently dropped. Reading persona from the store is marked · store in the intake block.
Files
roles/_meta.md— always-loaded base lens (the workspace's implicit "bstack-aware autonomous senior engineer" contract made addressable). Lives in the consuming workspace, not in this skill repo.roles/<name>.md— per-domain lenses. Live in the consuming workspace.roles/<name>.eval.yaml— resolver-eval fixture (should_fire/should_not_fireintents) asserting the lens's trigger actually routes. Run viarole-x.py eval; gate in CI. The skillify "resolver eval" step — a trigger that says "phrase X selects lens Y" is only trustworthy once a test proves it. Live in the consuming workspace alongside the lens.roles/_index.md— auto-generated discovery index.scripts/role-x.py— CLI helpers (validate,list,index,intake,coverage,suggest,init,eval,reflexes route,reflexes shadow).scripts/reflex_router.py— the reflex router (ROLE_X_OUTPUT=reflex), loaded byrole-x.pyby file path.references/reflexes.yaml— the reflex catalog: P1–P20 reflexes, memory conventions, and one line per skill withevals/prompts.json, each citing its source.references/*.md— schema + algorithm reference docs.~/.config/broomva/role/events.jsonl— telemetry log (M2).~/.config/broomva/role/status.json— per-lens stats cache (M2).~/.config/broomva/role/consolidation-runs/— dream-cycle snapshots (M4).~/.config/broomva/role/config.json— trusted user config: observability opt-in (v0.4.0) +persona_federation(F3′, OFF by default; see above).~/.config/broomva/persona/— the per-user persona store (F3′ confined 2nd root; its own git repo for cross-machine sync). Populated by the S2 migration.
Related
- Design spec:
broomva/workspace/docs/superpowers/specs/2026-05-13-role-x-primitive-design.md - Implementation plan:
broomva/workspace/docs/superpowers/plans/2026-05-13-role-x-primitive-implementation.md - Pattern entity:
broomva/workspace/research/entities/pattern/role-x.md - Reflexive trigger rule:
broomva/workspace/AGENTS.md§P17 - bstack engine ledger:
broomva/workspace/research/entities/pattern/bstack-engine.md(P17 in Promoted Patterns) - Seed meta-role source:
broomva/autonomous—/autonomousskill embeds the universal role contract