Skip to content

Commit a50478f

Browse files
committed
fix(recruit): silent programmatic mode, optional cache
Programmatic callers (like /discuss) no longer surface per-expert prompts to the user. Adds 'interactive' parameter (default false) to the recruiting protocol: fresh-install users running /discuss now see one intake Q-round + one roster confirmation instead of N prompts asking about individual experts. Cache behavior: silent on all failure modes. Unwritable data root, missing directory, or WITS_CACHE=off never block or surface to the user — personas are still returned, just not persisted. Adds WITS_CACHE=off escape hatch for users who want fully stateless runs. discuss: pinned recruit callsites in Step 4 (domain coverage), Step 5 (facilitator panel review), and the mid-discussion recruit_expert handler to interactive=false. The Step 6 roster-confirmation prompt is now marked as the single user-facing approval surface for expert selection.
1 parent b3b44b7 commit a50478f

4 files changed

Lines changed: 43 additions & 18 deletions

File tree

references/data-root.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ Skills in this plugin that persist data (recruit, discuss) use a shared data roo
44

55
## Resolution Order
66

7-
1. **Environment variable**: `WITS_DATA_DIR` — if set, use this path
8-
2. **XDG default**: `~/.local/share/wits/`
9-
3. **Ephemeral fallback**: `/tmp/wits-$USER/` — used when home directory is not writable
7+
1. **Disable caching entirely**: if `WITS_CACHE=off`, skip all reads/writes (skills run stateless — personas are generated fresh each time)
8+
2. **Environment variable**: `WITS_DATA_DIR` — if set, use this path
9+
3. **XDG default**: `~/.local/share/wits/`
10+
4. **Ephemeral fallback**: `/tmp/wits-$USER/` — used when home directory is not writable
1011

1112
## Directory Structure
1213

@@ -21,10 +22,12 @@ Skills in this plugin that persist data (recruit, discuss) use a shared data roo
2122

2223
When a skill needs to read or write persistent data:
2324

24-
1. Check if `WITS_DATA_DIR` is set: `echo $WITS_DATA_DIR`
25-
2. If not set, use `~/.local/share/wits/`
26-
3. Create subdirectories on first use (don't assume they exist)
27-
4. If directory creation fails (permissions), fall back to `/tmp/wits-$USER/` and warn the user
25+
1. Check if `WITS_CACHE=off` — if so, skip all persistence and run stateless
26+
2. Check if `WITS_DATA_DIR` is set: `echo $WITS_DATA_DIR`
27+
3. If not set, use `~/.local/share/wits/`
28+
4. Create subdirectories on first use (don't assume they exist)
29+
5. If directory creation fails (permissions), fall back to `/tmp/wits-$USER/` silently. Do not prompt or warn the user — caching is a silent optimization and skills must work the same whether the cache succeeds or not.
30+
6. If even `/tmp` fails, run stateless (reads return empty, writes are no-ops). Never block on cache failures.
2831

2932
## Notes
3033

skills/discuss/SKILL.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ If the intake agent returns questions, present them to the user. After answers (
6060

6161
2. **Domain coverage** — identify needed voices by domain:
6262
- From the intake agent's topic brief, extract domain areas
63-
- For each domain, call `/recruit` (search → evaluate → offer → reuse/create) to get a primary expert
63+
- For each domain, call `/recruit` with `interactive=false` (search → evaluate → decide silently → reuse-or-create) to get a primary expert. Recruit does not prompt the user during this phase — the roster-confirmation step below is the single user-facing approval surface for the whole recruiting pass.
6464
- Number based on `--size` flag or auto-heuristic (1-2 domains → 1-2 experts, 3-4 → 2-3, 5+ → 3-4+)
6565

6666
3. **Stance diversity** — explicit anti-homogeneity step [NEW]:
@@ -83,12 +83,13 @@ If the intake agent returns questions, present them to the user. After answers (
8383
5. **Facilitator panel review** [NEW]:
8484
- Before locking the roster, dispatch the facilitator ONCE with the proposed panel + topic brief.
8585
- Prompt: "Here's the proposed panel: [list]. Topic: [brief]. Critique it: what stance is missing? Who would disagree with the emerging frame for reasons none of these people would voice? Answer in 3-5 sentences. If the panel is adequate, say 'adequate' and why."
86-
- If facilitator flags a gap: recruit one more expert to fill it (call `/recruit create` with the gap description) before proceeding. Max one additional recruit from this step — if the facilitator keeps flagging gaps after that, proceed anyway and note the limitation in the final report.
86+
- If facilitator flags a gap: recruit one more expert to fill it by calling `/recruit create` with `interactive=false` and the gap description. Max one additional recruit from this step — if the facilitator keeps flagging gaps after that, proceed anyway and note the limitation in the final report. Still no user prompt at this stage.
8787

88-
6. Present the assembled roster to the user: "Your discussion team: [names + roles + stances + models]"
88+
6. **Present the assembled roster to the user — the single approval surface:** "Your discussion team: [names + roles + stances + models]"
8989
- Show the stance distribution explicitly
9090
- User can say "add <domain>" or "remove <name>" to customize
9191
- Once confirmed, write `team.json` to `tmp/discuss-<session-id>/`
92+
- This is the ONLY user-facing prompt for expert selection. All per-expert decisions (reuse vs create, name, persona details) happen silently inside `/recruit` during steps 2–5. A fresh-install user with no cached experts should see exactly one intake question round plus this one roster confirmation — not N prompts about individual experts.
9293

9394
### Step 5: Phase 2 — Discussion
9495

skills/discuss/references/orchestration.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,10 +423,11 @@ Invoked when the facilitator names a structural voice gap — usually in respons
423423

424424
**Steps:**
425425

426-
1. **Invoke `/recruit create`** with the `domain` field from the action. Pass the `rationale` as additional context so the recruit skill can evaluate reuse of existing experts. Recruit follows its normal search → evaluate → offer → reuse/create flow and returns:
426+
1. **Invoke `/recruit create`** with `interactive=false`, the `domain` field from the action, and the `rationale` as additional context. Recruit silently runs its search → evaluate → decide → reuse-or-create flow (no user prompt inside recruit) and returns:
427427
```
428428
{persona_name, persona_slug, stance, model_recommendation, persona_file_path}
429429
```
430+
The user prompt for mid-discussion panel changes happens at the discuss layer in the next step — recruit itself stays silent.
430431

431432
2. **Ask the user** using the same mechanism SKILL.md Step 4 uses for initial roster confirmation. Prompt:
432433
```

skills/recruit/SKILL.md

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ Create a new expert persona and add it to the registry.
4343

4444
Other skills call this protocol during their setup phases. Follow these steps in order.
4545

46+
**Default mode is non-interactive.** Programmatic callers (like `/discuss`) do NOT surface per-expert prompts to the user — the recruiting phase runs silently and returns the assembled personas. The calling skill is responsible for presenting the final roster to the user once, at its own approval surface. This is the contract: recruit is infrastructure, not a user-facing dialog.
47+
48+
Callers can request interactive mode by passing `interactive=true` (e.g. a debugging flow where the user wants to review each pick). Default: `false`.
49+
4650
### Step 1: Search
4751

4852
```
@@ -55,16 +59,26 @@ Scan for matching tags and domains. If the INDEX has no hits, also try:
5559
rg -i "<domain_keyword>" <data-root>/experts/*.md
5660
```
5761

62+
If the data root doesn't exist or isn't readable, treat as "no matches" and proceed to Step 5 (Create). Do not prompt.
63+
5864
### Step 2: Evaluate
5965

6066
For each candidate match, assess fit:
6167
- **Domain alignment**: Does the expert's domain cover what's needed? Partial overlap is OK if the core area matches.
6268
- **Research freshness**: If the expert's Research Context includes specific version-pinned or date-sensitive information, check if it's still accurate (>1 year → flag for refresh).
6369
- **Thinking style fit**: Does the persona's thinking style match the task? A "risk-averse systems thinker" is the right choice for a migration review but may be too conservative for a brainstorming session.
6470

65-
### Step 3: Offer
71+
Score each candidate on the three axes. A match is "strong" if domain alignment is clearly present AND research is fresh AND thinking style fits the task context.
72+
73+
### Step 3: Decide (silent in programmatic mode)
6674

67-
Present matches to the caller (or user, if user-invoked):
75+
**Programmatic mode (`interactive=false`, default):**
76+
- If at least one candidate is a **strong** match → reuse the best one silently (Step 4).
77+
- Otherwise → create a new expert silently (Step 5).
78+
- Never prompt the user between these steps.
79+
80+
**Interactive mode (`interactive=true`, user-invoked commands):**
81+
Present matches to the user:
6882

6983
> "Found cached expert **Dr. PostgreSQL** (database, postgresql, migration — last used 3 days ago). Reuse, or create a fresh expert for this domain?"
7084
@@ -75,22 +89,24 @@ If no match: skip to Step 5.
7589
When reusing a cached expert:
7690
1. Load the full file from `<data-root>/experts/<slug>.md`
7791
2. Extract the Persona Prompt and Research Context sections
78-
3. Update `last_used` to today's date in the file frontmatter
92+
3. Update `last_used` to today's date in the file frontmatter (best-effort — skip silently if the data root isn't writable)
7993
4. Add the calling skill to `consumers[]` if not already present
8094
5. Return the persona text to the caller
8195

82-
### Step 5: Create (when no suitable match exists)
96+
### Step 5: Create (when no suitable match exists or data root is empty/unavailable)
8397

8498
1. **Identify the domain**: Narrow and specific beats broad. "PostgreSQL migration specialist" beats "database expert."
8599
2. **Build persona** using `references/expert-template.md`:
86100
- Choose a name that creates a character (not "Expert #1")
87101
- Define thinking style, key frameworks, what they look for, blind spots
88102
- Write the Persona Prompt (100-300 words, second person: "You are...")
89103
3. **Deep research** (if triggered — see criteria below): dispatch a research subagent, save findings under `## Research Context`
90-
4. **Save**: write to `<data-root>/experts/<slug>.md`
91-
5. **Update INDEX.md**: add a row to the Domain Experts table with name, domain, tags, last used, created date
104+
4. **Save**: write to `<data-root>/experts/<slug>.md` if the data root is writable. If not writable, skip the write silently — the persona is still returned to the caller, it just isn't persisted for reuse.
105+
5. **Update INDEX.md**: add a row to the Domain Experts table with name, domain, tags, last used, created date (best-effort — skip silently on write failure)
92106
6. Return the persona text to the caller
93107

108+
Caching is an optimization. A failed or unavailable cache never blocks recruiting and never surfaces to the user in programmatic mode.
109+
94110
---
95111

96112
## Expert File Format
@@ -123,7 +139,10 @@ Expert data is stored under the **wits data root** (see `references/data-root.md
123139

124140
- Default: `~/.local/share/wits/experts/`
125141
- Override: set `WITS_DATA_DIR` environment variable
126-
- Fallback: `/tmp/wits-$USER/experts/` (ephemeral, with warning)
142+
- Disable caching: set `WITS_CACHE=off` to skip all reads/writes (personas are generated fresh each time)
143+
- Fallback: `/tmp/wits-$USER/experts/` (ephemeral, silent)
144+
145+
**Caching is a silent optimization.** If the data root is unwritable, missing, or caching is disabled, recruit proceeds without persisting — it never blocks, errors, or prompts the user about cache state. Reads return "no matches" on a missing/empty cache; writes are best-effort.
127146

128147
```
129148
<data-root>/experts/
@@ -165,6 +184,7 @@ When calling the protocol programmatically, pass:
165184
- The domain description (what expertise is needed)
166185
- The task context (what the expert will be doing — affects thinking style selection)
167186
- Whether deep research is acceptable (some callers are latency-sensitive)
187+
- `interactive` (default `false`) — when `false`, recruit makes reuse-vs-create decisions silently and never prompts the user. The calling skill handles user-facing approval at its own layer (e.g. `/discuss` shows the assembled panel once, after all recruitment is done). Set `true` only when the caller genuinely wants per-expert user review.
168188

169189
---
170190

0 commit comments

Comments
 (0)