diff --git a/CHANGES.md b/CHANGES.md index 92906368..f475c630 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,17 @@ This port applies the Cursor → Claude Code substitutions in skill bodies. Earlier drafts left them flagged; this revision resolves them. A later pass added a Codex build that shares the same skills; see [Codex port](#codex-port) below. +## pstack-flex (unreleased) — gateway lanes and optional families + +Fork of open-pstack v1.4.1. Additive changes, all in port-owned files: + +- Runner: new gateway providers `deepseek` and `minimax` (`runner/flex-providers.ts`). Each spawns the stock `claude` binary with the exact claude argv, plus injected environment: the lab's Anthropic-compatible endpoint, `ANTHROPIC_AUTH_TOKEN` from `DEEPSEEK_API_KEY`/`MINIMAX_API_KEY`, model pins, and an isolated `CLAUDE_CONFIG_DIR` (`~/.pstack-flex/`). Inherited `ANTHROPIC_*` values are deleted before injection so a parent's credentials or endpoint never bleed into a gateway child. +- OAuth-leak guard: a gateway lane refuses to start (in-process, `unauthenticated` receipt, exit 77) when its API key variable is missing or when its config dir carries a claude.ai OAuth credentials file, so a claude.ai login can never be pointed at a third-party endpoint. +- Gateway preflight is `claude --version`; the one-shot invocation is the real auth test. Gateway receipts force `costUsd` to null (the CLI prices at Anthropic rates) and match served models case-insensitively, falling back to `modelEvidence: "pinned-argv"` like Codex. +- `provider-dispatch.md`: new additive "Flex model matrix" section, extended route table, gateway preflight semantics, and the panel-diversity rule (arena runners and interrogate reviewers span at least two providers unless the operator explicitly confirms otherwise). The stock model matrix is byte-unchanged. +- `setup-pstack`: role assignments are selected first, and only assigned families get effort questions and probes; there is no requirement to assign every matrix family (mirrors upstream PR #73 / issue #72). The first-run sheet, its stock quad, and the fail-closed write rules are unchanged. +- Tests: the model-matrix contract gains a flex-matrix section check cross-validated against the runner's gateway specs; runner, commands, parse-output, and CLI tests cover env injection, the guard, cost nulling, and case-insensitive verification. Nothing in the suite performs network I/O. + ## 1.4.1 syncs to Cursor pstack 0.15.1 Open Pstack 1.4.1 tracks Cursor pstack 0.15.1 at `f8abeddd1862dc73704e3d719dd73df0d51b8c71`. Poteto-mode now requires each claim to include its evidence or a measured, inferred, or guess label in the same sentence. Agents also run any check they can run themselves instead of handing that check to the user. No playbook, model, runtime, or dependency changed. diff --git a/NOTICE.md b/NOTICE.md index 60e30343..a1be00dc 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -4,7 +4,7 @@ This plugin is a port of upstream MIT-licensed work. All upstream copyright noti ## pstack-flex provenance -This repository, **pstack-flex** (Martin Patino), is a fork of [ericlitman/open-pstack](https://github.com/ericlitman/open-pstack) at v1.4.1 (`de67e6b40511814171e5e4c8ad7af3b79f07c9ee`), which ports [Lauren Tan's pstack](https://github.com/cursor/plugins/tree/main/pstack) (Cursor) to Claude Code and Codex. Provenance chain: pstack-flex <- ericlitman/open-pstack <- cursor/plugins/pstack. All licenses remain MIT; every upstream license and notice file is preserved. The flex gateway additions and their tests are (c) 2026 Martin Patino, MIT, and are inventoried in [UPSTREAM-FLEX.md](UPSTREAM-FLEX.md). +This repository, **pstack-flex** (Martin Patino), is a fork of [ericlitman/open-pstack](https://github.com/ericlitman/open-pstack) at v1.4.1 (`de67e6b40511814171e5e4c8ad7af3b79f07c9ee`), which ports [Lauren Tan's pstack](https://github.com/cursor/plugins/tree/main/pstack) (Cursor) to Claude Code and Codex. Provenance chain: pstack-flex <- ericlitman/open-pstack <- cursor/plugins/pstack. All licenses remain MIT; every upstream license and notice file is preserved. The flex gateway providers, optional families, docs, and tests are (c) 2026 Martin Patino, MIT, and are inventoried in [UPSTREAM-FLEX.md](UPSTREAM-FLEX.md). ## Upstream sources diff --git a/README.md b/README.md index d6ef60e8..7a2acbda 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,11 @@ Open Pstack is an unofficial community project that makes pstack work in Claude ## This fork: pstack-flex -**pstack-flex** is a fork of [ericlitman/open-pstack](https://github.com/ericlitman/open-pstack) at v1.4.1. This change adds `deepseek` and `minimax` providers to the external runner. Each provider runs the stock `claude` binary against its Anthropic-compatible endpoint with its own API key. The runner uses an isolated `CLAUDE_CONFIG_DIR`, rejects OAuth credentials found there, and removes inherited Anthropic headers and provider-selection flags before starting the child. Gateway receipts keep token usage but set `costUsd` to null because Claude Code's cost estimate uses Anthropic prices. +**pstack-flex** is a fork of [ericlitman/open-pstack](https://github.com/ericlitman/open-pstack) at v1.4.1. Setup can assign only the model families you have. The `deepseek:*` and `minimax:*` routes run the stock `claude` binary against each lab's Anthropic-compatible endpoint with that lab's API key. The runner isolates Claude configuration, strips inherited provider routing and Anthropic headers, and rejects OAuth credentials found in the gateway config directory. Gateway receipts keep token usage but set `costUsd` to null because Claude Code's cost estimate uses Anthropic prices. -The stock setup and model matrix remain in place. A follow-up change will add gateway routes to setup and document the lane configurations. The fork's provenance and sync process are recorded in [UPSTREAM-FLEX.md](UPSTREAM-FLEX.md). Anthropic does not support pointing Claude Code at non-Anthropic endpoints; use synthetic data for gateway testing and keep API keys in your local environment. +The [lane guide](docs/LANES.md) covers setup, costs, and safety notes. The fork's provenance and sync process are recorded in [UPSTREAM-FLEX.md](UPSTREAM-FLEX.md). Anthropic does not support pointing Claude Code at non-Anthropic endpoints; use synthetic data for gateway testing and keep API keys in your local environment. + +New here? **[docs/USAGE.md](docs/USAGE.md)** is the walkthrough: diagrams of how work flows through the lanes, three setup configurations (full frontier, hybrid saver, zero-subscription), copy-paste examples for the daily skills, and troubleshooting. ## What pstack does @@ -45,7 +47,7 @@ You need a current Claude Code or Codex installation. For the full four-model re Run these commands inside Claude Code: ```text -/plugin marketplace add ericlitman/open-pstack +/plugin marketplace add thisguymartin/pstack-flex /plugin install pstack@open-pstack /reload-plugins ``` @@ -55,7 +57,7 @@ Run these commands inside Claude Code: Run these commands in your shell: ```shell -codex plugin marketplace add ericlitman/open-pstack --ref main +codex plugin marketplace add thisguymartin/pstack-flex --ref main codex plugin add pstack@open-pstack ``` diff --git a/UPSTREAM-FLEX.md b/UPSTREAM-FLEX.md index 52c8e9b6..f0617423 100644 --- a/UPSTREAM-FLEX.md +++ b/UPSTREAM-FLEX.md @@ -22,11 +22,11 @@ All flex changes are additive and live in port-owned files so upstream merges st - `plugins/pstack/skills/poteto-mode/scripts/runner/flex-providers.ts` and `flex-providers.test.ts` (new) - Gateway-provider hooks in `runner/{types,commands,run,parse-output,cli}.ts` and their tests -- This file, the README fork section, and the NOTICE/LICENSE additions +- The "Flex model matrix" section and route-table columns in `references/provider-dispatch.md` +- The assignment-first restructure of `skills/setup-pstack/SKILL.md` +- `docs/LANES.md`, this file, the README fork section, and the NOTICE/LICENSE/CHANGES additions -The follow-up routing and documentation changes will add the flex model matrix, assignment-first setup, and `docs/LANES.md`. - -The stock model matrix, the first-run sheet, every upstream skill body, and the static quad invariants are byte-unchanged. +The stock model matrix, the first-run sheet, every upstream skill body, and the static quad invariants are byte-unchanged. Flex rows and setup instructions are additive. ## Merge procedure diff --git a/docs/LANES.md b/docs/LANES.md new file mode 100644 index 00000000..82566f59 --- /dev/null +++ b/docs/LANES.md @@ -0,0 +1,117 @@ +# Lanes: models, providers, and cost control + +pstack-flex's reason to exist: you choose which models run and what they cost. This document covers the lane concepts, the gateway environment reference, prices, the zero-subscription walkthrough, and the safety rules. + +Prices and endpoints below were verified 2026-09-25 and drift. Re-verify against each provider's own docs before relying on a number. + +## Lane kinds + +| Kind | Lanes | Auth | Billing | Route | +| --- | --- | --- | --- | --- | +| Subscription | `claude:fable`, `claude:opus`, `codex:gpt-5.6-sol`, `grok:grok-4.6` | each CLI's own login | that CLI's plan | native or external per the route table | +| Gateway (flex) | `deepseek:deepseek-flash`, `minimax:MiniMax-M3` | API key in the environment | pay per token on the lab's key | always the external runner | + +A gateway lane is the stock `claude` binary env-pointed at the lab's Anthropic-compatible endpoint. There is no custom agent loop and no separate harness: the same runner that spawns Codex and Grok lanes spawns gateway lanes with injected environment. Both labs document this Claude Code setup themselves (DeepSeek: `deepseek-ai/awesome-deepseek-agent`, `docs/claude_code.md`; MiniMax: platform.minimax.io, Claude Code guide). + +## Gateway environment reference + +Set by you: + +| Variable | Required | Meaning | +| --- | --- | --- | +| `DEEPSEEK_API_KEY` / `MINIMAX_API_KEY` | yes, per lane | the lab's API key; the lane refuses to start without it | +| `DEEPSEEK_BASE_URL` / `MINIMAX_BASE_URL` | no | endpoint override; defaults are in the flex model matrix | +| `PSTACK_FLEX_DEEPSEEK_CONFIG_DIR` / `PSTACK_FLEX_MINIMAX_CONFIG_DIR` | no | config-dir override; default `~/.pstack-flex/` | +| `DEEPSEEK_MAX_CONTEXT_TOKENS` / `MINIMAX_MAX_CONTEXT_TOKENS` | no | context-cap override for the claude CLI | + +Injected by the runner at spawn time (never written to disk, never in receipts): `ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN`, the model pins (`ANTHROPIC_MODEL`, the opus/sonnet/haiku alias defaults, `CLAUDE_CODE_SUBAGENT_MODEL`), `CLAUDE_CODE_ATTRIBUTION_HEADER=0`, `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1`, and `CLAUDE_CONFIG_DIR`. The runner first removes inherited `ANTHROPIC_*` values and Claude Code cloud-provider flags from the parent session. + +## Storing keys + +Keys reach a lane through the environment only; the runner never writes them to disk, receipts, or sheets. So key hygiene is entirely about how your shell gets them. Do not put raw keys in dotfiles or committed `.env` files. + +Recommended: your OS keychain, loaded on demand. + +- **macOS** (built in, encrypted at rest, unlocks with login): + + ```zsh + # once per key — prompts for the value, nothing lands in shell history + security add-generic-password -a "$USER" -s pstack-deepseek -w + security add-generic-password -a "$USER" -s pstack-minimax -w + + # in .zshrc: a function, not an export — keys enter env only when called + pstack-keys() { + export DEEPSEEK_API_KEY=$(security find-generic-password -a "$USER" -s pstack-deepseek -w) + export MINIMAX_API_KEY=$(security find-generic-password -a "$USER" -s pstack-minimax -w) + } + ``` + +- **Linux**: `pass` (GPG-encrypted, git-syncable) or `secret-tool` (libsecret) with the same load-on-demand function shape. +- **1Password CLI**: `op run --env-file=.env.tpl -- claude` injects the keys at process start with biometric unlock and exports nothing into the shell permanently. +- **direnv**: fine for per-project scoping (gateway lanes are per-project opt-in anyway), but a raw `.envrc` is plaintext — have it call the keychain instead of holding the key. + +Honest threat model: encryption at rest protects against dotfile repos, backups, and file theft. Once a key is in process env, any process running as your user can read it — the same exposure your CLI OAuth credential files already have. Keychain storage plus two ops controls is the right amount: **set spend caps on the DeepSeek and MiniMax dashboards** (the real blast-radius limiter) and rotate keys if a machine is ever compromised. + +## Prices (verified 2026-09-25 — re-check before budgeting) + +| Lane | Price per million tokens | Notes | +| --- | --- | --- | +| DeepSeek V4.1-Flash (`deepseek-flash`) | $0.30 in / $1.20 out peak; $0.15 / $0.60 off-peak; cache hits near-free | Off-peak windows: 01:00-04:00 and 06:00-10:00 UTC on weekdays. The discount is automatic on DeepSeek's side; pstack-flex surfaces the window but never delays your work to hit it. MIT open weights. | +| DeepSeek V4-Pro | $1.32 / $3.96 peak; half off-peak | Stronger model for hard lanes; assign it per role if wanted. | +| MiniMax M3 (`MiniMax-M3`) | $0.30 / $1.20 at up to 512K input; higher above | 1M context. Custom community model license (irrelevant for API use). | +| Claude / Codex / Grok subscription lanes | plan-dependent | Billed by each provider's plan, not per token here. | + +Gateway receipts always report `costUsd: null`: the claude CLI computes `total_cost_usd` at Anthropic list prices, which would be fiction for third-party traffic. Token usage in receipts is real — multiply it by the table above. + +## Zero-subscription walkthrough + +Goal: run poteto-mode and its panels with no Claude, ChatGPT, or Grok plan — only two API keys. The Claude Code binary is a free download; a subscription is only needed to reach Anthropic's servers. + +1. Install the claude CLI, Bun, and this plugin as usual. Do not run `claude login` anywhere in this setup. +2. Export `DEEPSEEK_API_KEY` and `MINIMAX_API_KEY`. +3. Make the parent session itself a DeepSeek session — same mechanism as a lane, applied to your interactive shell: + + ```shell + export ANTHROPIC_BASE_URL="" + export ANTHROPIC_AUTH_TOKEN="$DEEPSEEK_API_KEY" + export ANTHROPIC_MODEL="deepseek-flash" + export CLAUDE_CODE_SUBAGENT_MODEL="deepseek-flash" + export CLAUDE_CONFIG_DIR="$HOME/.pstack-flex/parent-deepseek" + claude + ``` + + Native `claude:*` lanes spawned by this parent inherit the endpoint, so the fable/opus role slots ride DeepSeek too. +4. Run `/setup-pstack`. Assign roles across the `deepseek` and `minimax` families (a `budget-duo` style panel), skip the unassigned stock families, and let the probes confirm both endpoints. +5. Panels keep real diversity: DeepSeek and MiniMax are two distinct providers, which satisfies the two-provider panel rule without any override. + +Quality note: this trades peak capability for cost control. The hardest-task role on a frontier subscription lane is a config choice you can add later without touching anything else. + +## Safety and policy + +- **Unsupported, not prohibited.** Anthropic's docs state that routing Claude Code to non-Claude models through gateways is not supported. No terms clause or enforcement against pointing the unmodified binary at a third-party endpoint was found (2026-09-25), but a CLI update can break compatibility without notice. Pin the claude CLI version on machines that depend on gateway lanes and bump it deliberately. +- **Never a claude.ai login on a gateway path.** Do not run `claude login` or `claude setup-token` inside any `~/.pstack-flex/` config dir. The runner enforces this: a gateway lane refuses to start when its config dir carries an OAuth credentials file. Caveat: on macOS the CLI may store credentials in the Keychain where the file check cannot see them — the rule above is the real defense; the check is a backstop. +- **Privacy: gateway lanes are opt-in per project.** Do not send client or customer code to third-party providers by default. Keep sensitive repositories on subscription lanes, and enable gateway lanes deliberately, per project. +- **No runner fallback.** A failed gateway lane is a named dropout receipt. A reported model mismatch fails the lane. If the endpoint reports no model, the receipt says `modelVerified: false` and `modelEvidence: "pinned-argv"`; this cannot prove which model the gateway served. Confirm supported model slugs during the live probe. + +## Optional lanes + +- **OpenRouter (off by default).** OpenRouter has no Anthropic-format endpoint, so a lane needs a local translator that serves `/v1/messages` — musistudio/claude-code-router or a version-pinned LiteLLM — with `ANTHROPIC_BASE_URL` pointed at it. That is one extra long-running local process, which is why it is documented rather than shipped. Expect roughly a 5.5% credit fee on top of provider list prices. If you build it, model it as another gateway provider in `flex-providers.ts`. +- **Local via Ollama (planned).** Ollama serves an Anthropic-compatible API since v0.14, so a `local` gateway provider pointed at it is the natural next lane: full compute control, zero per-token cost, your hardware. Not wired in yet. + +## Adding a gateway provider + +Any lab that serves an Anthropic-compatible `/v1/messages` endpoint can become a gateway lane. The runner, parser, and preflight branch on `isGatewayProvider`, so no `switch` needs a new case. + +1. Add the provider name to `GATEWAY_PROVIDERS` in `plugins/pstack/skills/poteto-mode/scripts/runner/types.ts`. +2. Add its row to `GATEWAY_SPECS` in `runner/flex-providers.ts`: API key variable, base URL default, override variables, and context-window default. Typecheck fails until this row exists. +3. Add its row to the "Flex model matrix" in `plugins/pstack/skills/poteto-mode/references/provider-dispatch.md`. `model-matrix.test.ts` fails until the key variable and base URL match the spec. +4. Add its probe row to the table in `plugins/pstack/skills/setup-pstack/SKILL.md`, its variables to the gateway environment reference above, and its prices to the price table. +5. Run the live validation checklist below for the new lane before merging. + +## Live validation checklist (post-merge, real keys, never in CI) + +- V1: one DeepSeek probe through the runner (`--provider deepseek --model deepseek-flash --effort high`, read-only). Expect a `complete` receipt with `costUsd: null`; record the `reportedModel` string and confirm the base-URL default against DeepSeek's current guide; confirm `--effort` is accepted end-to-end. +- V2: same for MiniMax (`MiniMax-M3`); record the served-model casing. +- V3: run `claude auth status --json` inside a fresh flex config dir with `ANTHROPIC_AUTH_TOKEN` set and record the output here. On macOS, confirm whether `claude login` under an explicit `CLAUDE_CONFIG_DIR` writes `.credentials.json` or the Keychain. +- V4: the zero-subscription walkthrough above, end to end, on a machine with no stored provider logins. +- V5: OAuth guard live: `claude login` inside a scratch flex config dir, run a lane, confirm the refusal receipt, then delete that login. diff --git a/docs/USAGE.md b/docs/USAGE.md new file mode 100644 index 00000000..7ce4e2d4 --- /dev/null +++ b/docs/USAGE.md @@ -0,0 +1,233 @@ +# Using pstack-flex + +The walkthrough: what this plugin is, how work flows through it, how to set it up on the models you actually have, and copy-paste examples for the skills you will use daily. Lane mechanics and pricing live in [LANES.md](LANES.md); the fork's delta over upstream is in [UPSTREAM-FLEX.md](../UPSTREAM-FLEX.md). + +## What this is + +pstack is a plugin of engineering skills, playbooks, and small local tools for coding agents — not a model, not a service. You hand `poteto-mode` a task; it matches the task to a playbook, works the steps, and leaves evidence (diffs, runs, receipts) you can inspect instead of asking for trust. Its sharpest edge is multi-model adversarial review: several different model families challenge important work, because the adversarial signal comes from model diversity, not assigned personas. + +pstack-flex adds one thing on top: **you choose the models and the compute**. Any subset of families works, and two open labs — DeepSeek and MiniMax — are first-class lanes on plain API keys, down to a zero-subscription setup. + +If you also use my [thisguyskills](https://github.com/thisguymartin/skills) collection: that repo decides **what** to build (shaping, spec, Linear, handoff) and its handoff ends with "Use `pstack:poteto-mode`" — which is exactly where this repo picks up. + +## The big picture + +```mermaid +flowchart TD + T([Your task]) --> P["/pstack:poteto-mode"] + P --> PB[Playbook match
feature, bug-fix, refactoring, perf, ...] + PB --> S[Skills fire per step
how, tdd, interrogate, arena, ...] + S --> F{Lane fan-out} + F --> N1["claude:fable / claude:opus
native Agent (Claude sub)"] + F --> N2["codex:gpt-5.6-sol
native or codex CLI (ChatGPT sub)"] + F --> N3["grok:grok-4.6
grok CLI (Grok sub)"] + F --> G1["deepseek:deepseek-flash
runner + env -> DeepSeek API (key)"] + F --> G2["minimax:MiniMax-M3
runner + env -> MiniMax API (key)"] + N1 --> R[Outputs + receipts] + N2 --> R + N3 --> R + G1 --> R + G2 --> R + R --> V[Verification: run it, judge it,
cross-model consensus] + V --> PR([Review-ready PR]) +``` + +Every lane is a real agent process with tools and file access. The parent harness (your Claude Code or Codex session) resolves the route once; children never pick their own models. + +## Install this fork + +The marketplace keeps upstream's name (`open-pstack`), so only the source changes. + +Claude Code: + +```text +/plugin marketplace add thisguymartin/pstack-flex +/plugin install pstack@open-pstack +/reload-plugins +``` + +Codex: + +```shell +codex plugin marketplace add thisguymartin/pstack-flex --ref main +codex plugin add pstack@open-pstack +``` + +Plus [Bun](https://bun.sh) for the lane runner, and `multi_agent = true` under `[features]` in `~/.codex/config.toml` if Codex is your parent. Sign in only to the CLIs whose subscriptions you actually have — missing families are fine now. + +## Keys for the gateway lanes + +DeepSeek and MiniMax have no login flow here; their lanes read an API key from your environment at spawn time. The runner never writes keys to disk or receipts, so the only question is how the env gets populated. Don't paste keys into `.zshrc` — store them encrypted and load on demand. macOS Keychain, built in and free: + +```zsh +# once: store each key (prompts for the value, nothing in shell history) +security add-generic-password -a "$USER" -s pstack-deepseek -w +security add-generic-password -a "$USER" -s pstack-minimax -w + +# in .zshrc: a function, not an export — keys enter env only when you call it +pstack-keys() { + export DEEPSEEK_API_KEY=$(security find-generic-password -a "$USER" -s pstack-deepseek -w) + export MINIMAX_API_KEY=$(security find-generic-password -a "$USER" -s pstack-minimax -w) +} +``` + +Daily flow: `pstack-keys -> claude -> /pstack:poteto-mode`. Alternatives, the threat model, and the spend-cap advice are in [LANES.md](LANES.md#storing-keys). Set spend caps on both provider dashboards; that is the real blast-radius control. + +## First-time setup: /setup-pstack + +```text +/pstack:setup-pstack +``` + +(Codex: `Use pstack:setup-pstack to configure pstack.`) + +Setup is assignment-first: pick which roles run on which families, answer one effort question per **assigned** family, and only assigned families get probed. Unassigned families are skipped, not errors. Every probe is a real one-turn run — a failed probe writes nothing. Three configurations that make sense: + +**A. Full frontier** (Claude + ChatGPT + Grok subs) — accept the defaults; behaves exactly like stock upstream: + +```text +arena runners: claude:fable@max, codex:gpt-5.6-sol@max, grok:grok-4.6@xhigh, claude:opus@xhigh +``` + +**B. Hybrid saver** (Claude sub + two API keys) — frontier judgment, cheap volume: + +```text +feature, refactoring: deepseek:deepseek-flash@high +bug-fix: deepseek:deepseek-flash@high +judgment and prose: claude:fable@max +hardest tasks: claude:fable@max +swarm workers: deepseek:deepseek-flash@high +arena runners: claude:fable@max, deepseek:deepseek-flash@high, minimax:MiniMax-M3@high +interrogate reviewers: claude:fable@max, deepseek:deepseek-flash@high, minimax:MiniMax-M3@high +``` + +**C. Zero-subscription budget duo** (nothing but two keys) — start your parent session env-pointed at DeepSeek (walkthrough in [LANES.md](LANES.md#zero-subscription-walkthrough)), then assign everything across the two flex families: + +```text +arena runners: deepseek:deepseek-flash@high, minimax:MiniMax-M3@high +interrogate reviewers: deepseek:deepseek-flash@high, minimax:MiniMax-M3@high +``` + +Two labs are two distinct families, so panels keep real diversity without any override. A single-provider panel needs your explicit confirmation — by design. + +## Daily driving: the skills, with examples + +**poteto-mode** — the default entry point for any real task. It stays sticky across turns and pairs well with long autonomous sessions. + +```text +/pstack:poteto-mode + +Take ENG-142: saved reports lose their date-range filter after rename. +Repro is in the issue. Fix it, prove it in the running app, and prep the PR. +``` + +**interrogate** — multi-model review of a decision, design, or diff. Reviewers come from different families; the parent sorts their findings. + +```text +/pstack:interrogate + +Review this migration plan in docs/plans/report-store.md. Attack the +premise, the rollout order, and anything that loses data on rollback. +``` + +```mermaid +flowchart LR + Q[Decision or diff] --> A[Reviewer A
family 1] + Q --> B[Reviewer B
family 2] + Q --> C[Reviewer C
family 3] + A --> S[Parent synthesizes] + B --> S + C --> S + S --> O["consensus (2+ models) -> act on
lone findings -> consider
disagreements -> resolve explicitly"] +``` + +**arena** — N parallel attempts at the same task, an independent cross-judge, then graft the best parts onto a base. + +```text +/pstack:arena + +Implement the rate limiter from the spec in docs/spec.md. Run the +configured arena panel and keep the winner's tests regardless of base. +``` + +```mermaid +flowchart LR + T[Task] --> C1[Candidate 1] + T --> C2[Candidate 2] + T --> C3[Candidate 3] + C1 --> J[Cross-judge
different provider] + C2 --> J + C3 --> J + J --> G[Pick base + graft
best pieces] +``` + +**swarm** — same-shaped work fanned across N workers, one combined report. Good for sweeps: "apply this codemod across packages," "audit every endpoint for X." + +```text +/pstack:swarm + +Audit every handler under src/api/ for missing input validation. +One worker per file group, combined findings ranked by severity. +``` + +**architect** — competing designs from different families, scored by a judge on yet another family, before any code. + +```text +/pstack:architect + +Design the offline sync layer: local-first edits, conflict policy, +and migration from the current always-online store. +``` + +Worth knowing by name: `how` (explain how something works before touching it), `why` (root-cause an incident with your MCP context), `tdd`, `unslop` (de-slop prose and code), `fix-ci`, `babysit` (drive a PR to green). The 23 `principle-*` leaves are loaded by poteto-mode as needed — you rarely invoke them directly. + +## What actually happens on a gateway lane + +No new harness. The same runner that launches Codex and Grok lanes spawns the stock `claude` binary with swapped environment: + +```mermaid +sequenceDiagram + participant P as Parent session + participant R as pstack-runner + participant C as claude -p (subprocess) + participant D as DeepSeek / MiniMax API + P->>R: lane: deepseek:deepseek-flash@high + R->>R: guard: DEEPSEEK_API_KEY set?
config dir free of OAuth creds? + Note over R: refusal = unauthenticated receipt,
no subprocess ever spawned + R->>C: spawn with ANTHROPIC_BASE_URL,
ANTHROPIC_AUTH_TOKEN, isolated CLAUDE_CONFIG_DIR + C->>D: every model request in the agent loop + D-->>C: completions + C-->>R: JSON result + R-->>P: output file + receipt +``` + +The guard order matters: key check and OAuth check happen in-process **before** anything runs, so a claude.ai login can never be pointed at a third-party endpoint. Inherited `ANTHROPIC_*` values from your parent session are stripped before injection. + +## Reading receipts + +Every external lane writes a JSON receipt next to its output. The fields that matter: + +| Field | Meaning | +| --- | --- | +| `status` | `complete`, or a named dropout (`unauthenticated`, `unavailable-cli`, `timed-out`, ...) | +| `modelVerified` + `modelEvidence` | `provider-report` = the endpoint echoed the requested model (case-insensitive for gateways). `pinned-argv` = it didn't, but the argv pinned it — normal for Codex and sometimes gateways | +| `usage` | real token counts — trust these | +| `costUsd` | real for claude/grok subscription lanes; **always `null` on gateway lanes** (the CLI would price at Anthropic rates). Multiply `usage` by the [LANES.md](LANES.md) table instead | + +## Cost playbook + +- High-volume code-writing roles (`feature`, `bug-fix`, `swarm workers`) -> `deepseek:deepseek-flash` — cheapest tokens, near-free cache hits, and half price in the off-peak window. +- Long-context research and big-repo reading -> `minimax:MiniMax-M3` — 1M context. +- `judgment and prose` and `hardest tasks` -> your best frontier lane if you have one; this is the last role to economize. +- Panels: one frontier + two flex lanes gets you three-family diversity at a fraction of three subscriptions. + +## Troubleshooting + +| Symptom | Meaning | Fix | +| --- | --- | --- | +| Receipt `unauthenticated`, exit 77, "KEY is not set" | lane env missing | run your `pstack-keys` function (or export the key) in the shell that starts the parent | +| Receipt `unauthenticated`, "OAuth credentials found" | a claude.ai login sits in the lane's config dir | that's the leak guard working; remove the login from `~/.pstack-flex/` — never `claude login` there | +| Receipt `unauthenticated` after the model ran | the endpoint rejected the key (401) | check the key and the base URL against the provider's current guide | +| Exit 69 `unavailable-cli` | the `claude` binary isn't on PATH for the runner | install it or fix PATH | +| A panel ran with fewer lanes than configured | a lane dropped out with a named receipt | read that receipt; pstack proceeds N-1 and never silently substitutes a model | +| Everything gateway broke after a claude CLI update | Anthropic doesn't support third-party endpoints; compatibility can shift | pin the CLI version on machines that depend on gateway lanes; see [LANES.md](LANES.md#safety-and-policy) | diff --git a/plugins/pstack/skills/poteto-mode/references/codex-tools.md b/plugins/pstack/skills/poteto-mode/references/codex-tools.md index b967458d..1d8621c3 100644 --- a/plugins/pstack/skills/poteto-mode/references/codex-tools.md +++ b/plugins/pstack/skills/poteto-mode/references/codex-tools.md @@ -42,7 +42,7 @@ poteto-mode's Subagents section sets Claude-specific defaults (`subagent_type: " ## Models and providers -Do not replace every configured entry with a Codex model. `/setup-pstack` writes portable descriptors such as `claude:fable@max`, `codex:gpt-5.6-sol@max`, and `grok:grok-4.6@xhigh`. In a Codex parent, only `codex:*` is native. Route Claude and Grok descriptors through the external launcher exactly as `provider-dispatch.md` specifies. The current default panel intentionally keeps four-provider frontier diversity and contains no older GPT or Claude substitute. +Do not replace every configured entry with a Codex model. `/setup-pstack` writes portable descriptors such as `claude:fable@max`, `codex:gpt-5.6-sol@max`, and `grok:grok-4.6@xhigh`. In a Codex parent, only `codex:*` is native. Route Claude and Grok descriptors through the external launcher exactly as `provider-dispatch.md` specifies. The current default panel intentionally keeps four-provider frontier diversity and contains no older GPT or Claude substitute. pstack-flex gateway descriptors (`deepseek:*`, `minimax:*`) also always route through the external launcher in a Codex parent; they are never `spawn_agent` lanes. ## Claude built-in skills pstack references diff --git a/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md b/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md index 74ab90da..f30e88c8 100644 --- a/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md +++ b/plugins/pstack/skills/poteto-mode/references/provider-dispatch.md @@ -19,6 +19,21 @@ The allowed effort universe is exactly `low`, `medium`, `high`, `xhigh`, `max`. `fable` and `opus` are Claude Code's rolling aliases. Claude resolves each alias to the latest available family revision. A runner receipt keeps the requested alias in `model` and the concrete provider-reported revision in `reportedModel`; verification accepts only a numeric `claude-fable-*` or `claude-opus-*` revision from the matching family. +## Flex model matrix + +pstack-flex addition. The stock matrix above is upstream-owned and unchanged; these lanes are additive. A flex lane runs the stock `claude` binary env-pointed at the provider's Anthropic-compatible endpoint, with the provider's own API key and an isolated `CLAUDE_CONFIG_DIR`, so it uses no Anthropic account, no claude.ai login, and no subscription. + +| Family | Provider | Model | Default effort | Selectable efforts | API key variable | Base URL default | +|---|---|---|---|---|---|---| +| deepseek | deepseek | deepseek-flash | high | low medium high xhigh max | DEEPSEEK_API_KEY | https://api.deepseek.com/anthropic | +| minimax | minimax | MiniMax-M3 | high | low medium high xhigh max | MINIMAX_API_KEY | https://api.minimax.io/anthropic | + +Flex lanes have no Claude-native agent stem and always take the external runner in both parents. The base URL is a documented default; override it with `DEEPSEEK_BASE_URL` or `MINIMAX_BASE_URL`, and confirm it against the provider's current Claude Code guide during setup's live probe. The config dir defaults to `~/.pstack-flex/` (override: `PSTACK_FLEX__CONFIG_DIR`). Secrets stay in the environment: nothing in the sheet, the receipts, or this repository carries a key. + +Gateway receipt semantics differ from stock claude lanes in two documented ways. `costUsd` is always `null`: the claude CLI prices `total_cost_usd` at Anthropic rates, which would be fiction for third-party traffic; real prices live in [LANES.md](../../../../../docs/LANES.md), and token usage in the receipt stays accurate. Model verification accepts a case-insensitive matching provider report. A mismatched report fails the lane. When the endpoint reports no model, the receipt uses `modelEvidence: "pinned-argv"` and `modelVerified: false`. + +Panel diversity rule (pstack-flex): `arena runners` and `interrogate reviewers` must span at least two distinct providers. DeepSeek plus MiniMax satisfies it. A single-provider panel is written only after the operator explicitly confirms the reduced diversity during setup, and the setup report records that confirmation. The adversarial signal comes from model diversity, so treat the override as an exception, not a configuration style. + ## Read-time normalization Normalize configured descriptors before matching them to the matrix or choosing a route. If a provider-qualified Claude model starts with `claude-fable-` or `claude-opus-` and its remaining revision contains only digits and hyphens, replace that model component in memory with `fable` or `opus`. Preserve provider, effort, role, and lane order. Use only the normalized descriptor for native dispatch or runner argv. Never pass the versioned predecessor to Claude. @@ -31,10 +46,12 @@ This read-time rule makes an older installed sheet use the latest family revisio The top-level harness resolves the route once. A child receives an assigned provider, model, effort, access mode, prompt, working directory, and output path. A child never detects the harness, chooses a provider, or launches another model. Environment markers may corroborate the top-level harness before fan-out, but nested processes inherit parent markers and must not use them for routing. -| Parent | `claude:*` | `codex:*` | `grok:*` | -|---|---|---|---| -| Claude Code | native `Agent` | external runner | external runner | -| Codex | external runner | native `spawn_agent` | external runner | +| Parent | `claude:*` | `codex:*` | `grok:*` | `deepseek:*` | `minimax:*` | +|---|---|---|---|---|---| +| Claude Code | native `Agent` | external runner | external runner | external runner | external runner | +| Codex | external runner | native `spawn_agent` | external runner | external runner | external runner | + +Flex gateway descriptors are never native, even under a Claude Code parent: the gateway lane must run in its own process with injected endpoint, token, and isolated config dir, which the parent's native `Agent` primitive cannot provide. `inherit-parent` and `auto` remain aliases. They use the parent's current model and effort through its native subagent primitive. In a panel they still consume one lane, but they reduce provider diversity; say so in the synthesis record. @@ -54,7 +71,7 @@ The launcher lives at `skills/poteto-mode/scripts/runner/pstack-runner` under th ```text pstack-runner \ --parent \ - --provider \ + --provider \ --model \ --effort \ --mode \ @@ -67,6 +84,8 @@ pstack-runner \ Pass arguments as an argv array or quote every path. Never interpolate prompt text into a shell command. The launcher preflights the assigned CLI and authentication, invokes the model exactly once, disables recursive agents and ambient skill dispatch where the CLI supports it, restricts the built-in tool surface, and records the exact provider/model/effort flags. External lanes do not receive the parent's MCP surface. Keep MCP-dependent Why and Reflect roles on `inherit-parent` or `auto`. The launcher never falls back. +Gateway lanes (`deepseek`, `minimax`) run three checks before the model executes, all fail-closed. First, in-process: the lane's API key variable must be set, and the lane's isolated `CLAUDE_CONFIG_DIR` must be free of OAuth credentials — a `.credentials.json` carrying a claude.ai login, or one that cannot be parsed, refuses the lane with an `unauthenticated` receipt before any subprocess runs, so a claude.ai credential can never be sent to a third-party endpoint. Second, the spawned preflight is `claude --version`, which proves the binary executes; `claude auth status` is deliberately not used because its behavior under token auth is undocumented. Third, the one-shot invocation is the real authentication and model test; an endpoint authentication error classifies as `unauthenticated` like any other lane. + Grok authentication preflight has one bounded retry. If the first `grok models` result would be classified as unauthenticated, the runner waits five seconds and tries the same preflight once more. A second failure is terminal. The delay and second attempt share the runner's absolute deadline and cancellation latch, and the receipt keeps evidence from both attempts. Model execution is never retried. The parent tool sandbox still governs whether a subscribed child CLI can reach its credentials and network. Run setup's live probe from the actual parent profile. A blocked external CLI is a loud dropout, not a reason to elevate permissions or substitute a model silently. @@ -90,7 +109,7 @@ Success requires all of these: 1. Exit status `0`. 2. Receipt status `complete`. -3. Either `modelVerified: true` with `modelEvidence: "provider-report"`, or a Codex receipt with `reportedModel: null`, `modelVerified: false`, and `modelEvidence: "pinned-argv"`. For Claude's `fable` and `opus` aliases, the concrete provider report must belong to the requested family. Codex 0.149.0 accepts the exact `--model` argument but does not report the served model in its JSONL stream. +3. Either `modelVerified: true` with `modelEvidence: "provider-report"`, or a Codex receipt with `reportedModel: null`, `modelVerified: false`, and `modelEvidence: "pinned-argv"`, or a gateway (`deepseek`/`minimax`) receipt with `modelVerified: false` and `modelEvidence: "pinned-argv"` when the endpoint does not echo the requested slug. For Claude's `fable` and `opus` aliases, the concrete provider report must belong to the requested family. Codex 0.149.0 accepts the exact `--model` argument but does not report the served model in its JSONL stream. Gateway reports match case-insensitively because third-party endpoints are inconsistent about slug casing. 4. A non-empty output file. The receipt also carries elapsed time, token usage when the CLI exposes it, and cost when available. Keep it with the arena or review artifacts so parent-harness comparisons are evidence-based. diff --git a/plugins/pstack/skills/poteto-mode/scripts/runner/model-matrix.test.ts b/plugins/pstack/skills/poteto-mode/scripts/runner/model-matrix.test.ts index e0d6df1e..af138b1a 100644 --- a/plugins/pstack/skills/poteto-mode/scripts/runner/model-matrix.test.ts +++ b/plugins/pstack/skills/poteto-mode/scripts/runner/model-matrix.test.ts @@ -1,7 +1,13 @@ import { describe, expect, it } from "bun:test"; import { readdirSync, readFileSync } from "node:fs"; import { join } from "node:path"; -import { EFFORTS, type Effort } from "./types.ts"; +import { GATEWAY_SPECS } from "./flex-providers.ts"; +import { + EFFORTS, + GATEWAY_PROVIDERS, + type Effort, + type GatewayProvider, +} from "./types.ts"; const PLUGIN_ROOT = join(import.meta.dir, "../../../.."); const DISPATCH_PATH = join( @@ -51,12 +57,22 @@ const SHEET_ROLES = [ const SETUP_SECTION_ORDER = [ "### 2. Load current state", "### 3. Parse per-family efforts", - "### 4. Collect one requested effort per family", - "### 5. Probe the four requested pairs", + "### 4. Choose role assignments, then collect efforts", + "### 5. Probe the assigned pairs", "### 6. Render, preserving role families", "### 7. Confirm and commit", ] as const; +const FLEX_MATRIX_HEADER = [ + "Family", + "Provider", + "Model", + "Default effort", + "Selectable efforts", + "API key variable", + "Base URL default", +] as const; + interface MatrixRow { family: string; upstreamChoice: string; @@ -317,7 +333,13 @@ describe("model matrix", () => { expect(setup).toContain("Do not invent a precedence rule."); expect(setup).toContain("Do not probe or write while any inconsistency is unresolved."); expect(setup).toContain("A failed probe writes nothing:"); - expect(setup).toContain("Run one probe per family"); + expect(setup).toContain("Run one probe per assigned family"); + expect(setup).toContain("There is no requirement to assign every matrix family."); + expect(setup).toContain("`architect runners` to keep at least two entries"); + expect(setup).toContain("span at least two distinct providers"); + expect(setup).toContain( + "A failed model demands explicit repair or role reassignment before saving." + ); expect(setup).toContain("normalized complete role map from step 2"); expect(setup).toContain("starts with `claude-fable-` or `claude-opus-`"); expect(setup).toContain("preserving the provider, effort, role, and lane order"); @@ -328,6 +350,49 @@ describe("model matrix", () => { expect(setup).toContain(""); }); + it("keeps the flex matrix additive, parseable, and aligned with the runner", () => { + const dispatch = readFileSync(DISPATCH_PATH, "utf8"); + const lines = dispatch.split(/\r?\n/); + const start = lines.findIndex((line) => line.trim() === "## Flex model matrix"); + expect(start).toBeGreaterThan(-1); + let end = lines.length; + for (let i = start + 1; i < lines.length; i++) { + if (lines[i].startsWith("## ")) { + end = i; + break; + } + } + const table = lines + .slice(start + 1, end) + .map((line) => line.trim()) + .filter((line) => line.startsWith("|")); + expect(table.length).toBe(2 + GATEWAY_PROVIDERS.length); + expect(splitRow(table[0]).join("|")).toBe(FLEX_MATRIX_HEADER.join("|")); + expect(isSeparator(splitRow(table[1]))).toBe(true); + const seen: GatewayProvider[] = []; + for (const line of table.slice(2)) { + const cells = splitRow(line); + expect(cells.length).toBe(FLEX_MATRIX_HEADER.length); + const [family, provider, model, defaultEffortRaw, selectableRaw, keyVar, baseUrl] = + cells; + expect(GATEWAY_PROVIDERS as readonly string[]).toContain(provider); + const gateway = provider as GatewayProvider; + seen.push(gateway); + expect(family).toBe(gateway); + expect(/^[A-Za-z0-9.-]+$/.test(model)).toBe(true); + const selectable = selectableRaw.split(/\s+/).map(asEffort); + expect(selectable).toContain(asEffort(defaultEffortRaw)); + expect(keyVar).toBe(GATEWAY_SPECS[gateway].apiKeyVar); + expect(baseUrl).toBe(GATEWAY_SPECS[gateway].baseUrlDefault); + expect(baseUrl.startsWith("https://")).toBe(true); + } + expect(seen).toEqual([...GATEWAY_PROVIDERS]); + // The stock quad and first-run sheet must not carry flex descriptors: + // upstream's own checks parse descriptors with a lowercase-only, + // three-provider grammar and must never see a flex lane. + expect(firstRunSheet(setup)).not.toMatch(/deepseek:|minimax:/i); + }); + it("binds Claude-native dispatch to the matrix mapping", () => { const dispatch = readFileSync(DISPATCH_PATH, "utf8"); const nativeStart = dispatch.indexOf("## Native lanes"); diff --git a/plugins/pstack/skills/setup-pstack/SKILL.md b/plugins/pstack/skills/setup-pstack/SKILL.md index 9a0e7441..95c3962b 100644 --- a/plugins/pstack/skills/setup-pstack/SKILL.md +++ b/plugins/pstack/skills/setup-pstack/SKILL.md @@ -1,11 +1,11 @@ --- name: setup-pstack -description: Configure pstack's provider-qualified models, per-family requested effort, and parent-owned routes per role. Verifies native and external Claude, Codex, and Grok lanes before writing the override sheet. Use for /setup-pstack, "configure pstack models", or changing pstack's model choices. +description: Configure pstack's provider-qualified models, per-family requested effort, and parent-owned routes per role. Verifies native and external Claude, Codex, Grok, DeepSeek, and MiniMax lanes before writing the override sheet. Use for /setup-pstack, "configure pstack models", or changing pstack's model choices. --- # Setup pstack -Configure one portable model sheet for the current parent harness. Read [`provider-dispatch.md`](../poteto-mode/references/provider-dispatch.md) before probing or writing anything. Its model matrix, descriptor grammar, and route table are the contract. Choose one requested effort per matrix family. Do not add a second configuration file, a runtime resolver, or a weaker-model fallback. +Configure one portable model sheet for the current parent harness. Read [`provider-dispatch.md`](../poteto-mode/references/provider-dispatch.md) before probing or writing anything. Its model matrices (stock and flex), descriptor grammar, and route table are the contract. Role assignments are selected first; then choose one requested effort per assigned matrix family. Do not add a second configuration file, a runtime resolver, or a weaker-model fallback. Claude Code writes `~/.claude/pstack-models.md` and loads it from `~/.claude/CLAUDE.md` with: @@ -35,19 +35,23 @@ Treat the normalized values as current role-to-family assignments. Overlay those ### 3. Parse per-family efforts -Read the model matrix. Every non-alias value must match `:@`. Map it to exactly one matrix family by `(provider, model)`, require its effort to appear in that row's Selectable efforts cell, and collect the effort. `inherit-parent` and `auto` rows carry no family effort. +Read the model matrices, stock and flex. Every non-alias value must match `:@`. Map it to exactly one matrix family by `(provider, model)`, require its effort to appear in that row's Selectable efforts cell, and collect the effort. `inherit-parent` and `auto` rows carry no family effort. An unmatched provider/model, out-of-domain effort, duplicate role, or unknown role is inconsistent state. Stop, show the conflicting rows verbatim, and ask for an explicit matrix family or alias replacement. If one or more families have mixed efforts, show every conflicting family and role row, then ask for one normalized effort per family from its Selectable efforts cell. Do not invent a precedence rule. Do not probe or write while any inconsistency is unresolved. One distinct effort per family is the current value. A family with no non-alias occurrence is unassigned; use its matrix Default effort as the proposed value and label it unassigned rather than calling it current. -### 4. Collect one requested effort per family +### 4. Choose role assignments, then collect efforts -Ask exactly four effort questions, one each for Fable, Sol, Grok, and Opus. Name each model, its current or proposed value, and the Selectable efforts from its matrix row. Empty input keeps a current value or accepts the matrix proposal for an unassigned family. On a first run, state the four matrix defaults before asking. On a rerun, state the four parsed values without offering to reset customized role lanes. +Role assignments come first. Show the current role-to-family map (loaded and normalized from step 2, or the first-run map from step 7) and ask whether to keep it or change named roles. Keeping it is the default. A changed role may use any stock or flex matrix family, `inherit-parent`, or `auto`. Apply only role changes the operator names; never offer a reset of a customized sheet to the first-run assignments. -### 5. Probe the four requested pairs +The assigned families are exactly the matrix families that appear in the resulting role map. An unassigned family gets no effort question and no probe. There is no requirement to assign every matrix family. -Probe only the four selected `provider:model@effort` pairs. Run one probe per family, even when two families share a provider. Do not enumerate or offer older models as substitutes. A failed probe writes nothing: report the failing pair and provider, stop, and keep the active sheet plus parent integration bytes unchanged. A failed first run creates neither artifact. +Then ask one effort question per assigned family. Name each model, its current or proposed value, and the Selectable efforts from its matrix row. Empty input keeps a current value or accepts the matrix proposal for a newly assigned family. On a first run, state the assigned families' matrix defaults before asking. On a rerun, state the parsed values without re-opening the role choices already made above. + +### 5. Probe the assigned pairs + +Probe only the assigned families' selected `provider:model@effort` pairs. Run one probe per assigned family, even when two families share a provider. Do not enumerate or offer older models as substitutes. A failed probe writes nothing: report the failing pair and provider, stop, and keep the active sheet plus parent integration bytes unchanged. A failed model demands explicit repair or role reassignment before saving. A failed first run creates neither artifact. | Family | Pair source | Claude parent route | Codex parent route | Availability proof | |---|---|---|---|---| @@ -55,8 +59,10 @@ Probe only the four selected `provider:model@effort` pairs. Run one probe per fa | Sol | Sol matrix row + selected effort | `codex exec` | native `spawn_agent` | `codex login status` plus one-turn probe or native one-turn probe | | Grok | Grok matrix row + selected effort | Grok CLI | Grok CLI | `grok models` must list the requested model; one-turn probe | | Opus | Opus matrix row + selected effort | native Agent `pstack-opus-` | Claude CLI | native one-turn probe or `claude auth status --json` plus one-turn probe | +| DeepSeek | DeepSeek flex row + selected effort | external runner | external runner | `DEEPSEEK_API_KEY` present; isolated config dir free of OAuth credentials; one-turn probe confirms the endpoint | +| MiniMax | MiniMax flex row + selected effort | external runner | external runner | `MINIMAX_API_KEY` present; isolated config dir free of OAuth credentials; one-turn probe confirms the endpoint | -Use a tiny read-only probe that returns a unique marker. A login-status command alone proves credentials, not that the requested model and effort flags run. Record native and external results separately. Never call the external launcher for the parent's own provider. On a Claude parent, the Fable and Opus probes are one-turn runs of the mapped `pstack--` agent. On a Codex parent, the Sol probe is native `spawn_agent` with the selected `reasoning_effort`. Every other pair uses the external runner with the selected effort flag. +Use a tiny read-only probe that returns a unique marker. A login-status command alone proves credentials, not that the requested model and effort flags run. Record native and external results separately. Never call the external launcher for the parent's own provider. On a Claude parent, the Fable and Opus probes are one-turn runs of the mapped `pstack--` agent. On a Codex parent, the Sol probe is native `spawn_agent` with the selected `reasoning_effort`. Every other pair, flex families always included, uses the external runner with the selected effort flag. A flex probe doubles as the base-URL confirmation: it proves the documented default (or the operator's override) actually serves the lane's model. Receipts and native transcripts prove the requested effort and the route. They do not prove a provider's hidden applied reasoning depth. There is no implicit timeout, weaker-model fallback, same-provider external fallback, or second mutable configuration source. @@ -67,11 +73,11 @@ Build the new sheet in memory. Do not write it yet. - First run: start from the complete role assignments in step 7. - Rerun: start from the normalized complete role map from step 2, preserving each loaded row's lane order and family (or alias) per lane. -After effort selection, ask whether to keep those role-to-family assignments or change named roles. Keeping them is the default. Apply only role changes the operator names; never offer a reset of a customized sheet to the first-run assignments. A changed role may use one of the four probed matrix families, `inherit-parent`, or `auto`. +The role assignments were already chosen in step 4; do not re-open them here. Require every documented role to remain present and non-empty, `architect runners` to keep at least two entries, and the final role map to contain at least one assigned matrix family. There is no requirement to assign every matrix family. The sheet stores effort only in role descriptors, so an unassigned family's selection cannot persist without adding a second source of truth. -Require the final role map to contain at least one descriptor from each matrix family. The sheet stores effort only in role descriptors, so an unassigned family's selection cannot persist without adding a second source of truth. +Validate panel diversity: `arena runners` and `interrogate reviewers` must span at least two distinct providers. A single-provider panel is written only after the operator explicitly confirms the reduced diversity; record that confirmation in the setup report. -Rewrite every matrix-family descriptor to `provider:model@`. Leave `inherit-parent` and `auto` unchanged. An effort-only rerun cannot change a role's family. Changing Grok's effort updates every Grok occurrence and does not move a Sol role onto Grok. Refuse an unqualified slug, an unavailable route, a model other than the four matrix families, or a provider/model mismatch. +Rewrite every matrix-family descriptor to `provider:model@`. Leave `inherit-parent` and `auto` unchanged. An effort-only rerun cannot change a role's family. Changing Grok's effort updates every Grok occurrence and does not move a Sol role onto Grok. Refuse an unqualified slug, an unavailable route, a model outside the stock and flex matrix families, or a provider/model mismatch. ### 7. Confirm and commit @@ -109,12 +115,12 @@ interrogate reviewers: claude:fable@max, codex:gpt-5.6-sol@max, grok:grok-4.6@xh Render the parent integration in memory before either write. On Claude, the integration is the single `@~/.claude/pstack-models.md` include in `~/.claude/CLAUDE.md`. On Codex, it is the exact sheet bytes between one `` and `` pair in `~/.codex/AGENTS.md`. Replace that whole bounded block on a rerun. Insert one block at the end on first run. If either marker is missing, duplicated, or reversed, stop and report inconsistent state instead of guessing a boundary. -Snapshot every target's current bytes. Write the sheet and parent integration only after all four probes pass and the operator confirms. Read both targets back and compare them with the in-memory render. If either write or readback fails, restore every snapshot and report the failure. An unchanged rerun must produce byte-identical sheet and integration content after normalization. +Snapshot every target's current bytes. Write the sheet and parent integration only after every assigned family's probe passes and the operator confirms. Read both targets back and compare them with the in-memory render. If either write or readback fails, restore every snapshot and report the failure. An unchanged rerun must produce byte-identical sheet and integration content after normalization. Do not copy the model sheet between harnesses without rerunning the parent-specific probes; route availability can differ even on the same host. ### 9. Behavioral smoke -Before declaring setup complete, run one small read-only mixed panel from this parent: all four chosen descriptors, distinct output/receipt paths, and an independent cross-judge. Launch Claude-native agents and every external process in the background with retained handles, then drain them. Verify the native transcript entries and every external receipt. A structural config check or unit test is not a substitute. +Before declaring setup complete, run one small read-only mixed panel from this parent: every assigned family's chosen descriptor, distinct output/receipt paths, and an independent cross-judge when at least two providers are assigned. Launch Claude-native agents and every external process in the background with retained handles, then drain them. Verify the native transcript entries and every external receipt. A structural config check or unit test is not a substitute. Report the sheet path, parent route table, requested-effort probe results, smoke results, and external elapsed/token/cost receipts. Re-running this skill re-probes and updates the same sheet. Do not claim the provider exposed hidden applied-effort observability.