feat: explore-minimax-run — read-only MiniMax exploration wrapper + mode guidance
Hardened wrapper for delegating broad codebase exploration to MiniMax M3: per-root --add-dir scoping, secrets-root refusal, enforced read-only via --disallowedTools (profile defaults to acceptEdits), JSON .result answer channel, internal timeout + retry, file:line citation gate with distinct fallback exit codes. deep/deep-fable/orch modes now prefer /explore-minimax for broad read-only sweeps, Explore agent as fallback. Claude-Session: https://claude.ai/code/session_01Ku87DDWwbJkTx6SuV3sQDc
This commit is contained in:
@@ -20,7 +20,8 @@ You are running as Fable 5. Fixed for the session. To get Opus reasoning, do NOT
|
||||
|
||||
## Subagent policy
|
||||
- Default subagent model: Fable
|
||||
- Spawn a Fable subagent when: a research task spans >3 files or needs broad exploration (use the Explore agent)
|
||||
- Broad read-only exploration (>3 files, "find where X lives" sweeps): prefer the `/explore-minimax` skill — the sweep runs in MiniMax and only a cited conclusion returns. Spot-check 1–2 of its citations before relying on the answer. Fall back to the Explore agent (Fable) if it fails or the answer is uncited.
|
||||
- Spawn a Fable subagent when: research needs iterative steering, code-quality judgement, or tools MiniMax lacks (use the Explore agent)
|
||||
- Spawn an Opus subagent ONLY via the named workflows below — not freelance
|
||||
- Do NOT spawn parallel Fable subagents to "go faster" on coding work; finish one thing properly before starting the next
|
||||
|
||||
@@ -31,7 +32,7 @@ You are running as Fable 5. Fixed for the session. To get Opus reasoning, do NOT
|
||||
|
||||
## Async policy
|
||||
- Background bash: enabled
|
||||
- Third-party delegation: the /ask-minimax skill (file-heavy grunt work, second opinions); on MiniMax failure, fall back to an Opus subagent.
|
||||
- Third-party delegation: the /ask-minimax skill (file-heavy grunt work, second opinions) and /explore-minimax (broad read-only exploration); on MiniMax failure, fall back to an Opus subagent (ask-minimax) or the Explore agent (explore-minimax).
|
||||
- ScheduleWakeup: enabled, for checking on background jobs. Wakeup prompts MUST be self-contained.
|
||||
- Container agents (control plane): not the focus of this mode — if you find yourself wanting them, suggest relaunching in `orch`
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ You are running as Sonnet. Fixed for the session. To get Opus reasoning, do NOT
|
||||
|
||||
## Subagent policy
|
||||
- Default subagent model: Sonnet
|
||||
- Spawn a Sonnet subagent when: a research task spans >3 files or needs broad exploration (use the Explore agent)
|
||||
- Broad read-only exploration (>3 files, "find where X lives" sweeps): prefer the `/explore-minimax` skill — the sweep runs in MiniMax and only a cited conclusion returns. Spot-check 1–2 of its citations before relying on the answer. Fall back to the Explore agent (Sonnet) if it fails or the answer is uncited.
|
||||
- Spawn a Sonnet subagent when: research needs iterative steering, code-quality judgement, or tools MiniMax lacks (use the Explore agent)
|
||||
- Spawn an Opus subagent ONLY via the named workflows below — not freelance
|
||||
- Do NOT spawn parallel Sonnet subagents to "go faster" on coding work; finish one thing properly before starting the next
|
||||
|
||||
@@ -31,7 +32,7 @@ You are running as Sonnet. Fixed for the session. To get Opus reasoning, do NOT
|
||||
|
||||
## Async policy
|
||||
- Background bash: enabled
|
||||
- Third-party delegation: the /ask-minimax skill (file-heavy grunt work, second opinions); on MiniMax failure, fall back to an Opus subagent.
|
||||
- Third-party delegation: the /ask-minimax skill (file-heavy grunt work, second opinions) and /explore-minimax (broad read-only exploration); on MiniMax failure, fall back to an Opus subagent (ask-minimax) or the Explore agent (explore-minimax).
|
||||
- ScheduleWakeup: enabled, for checking on background jobs. Wakeup prompts MUST be self-contained.
|
||||
- Container agents (control plane): not the focus of this mode — if you find yourself wanting them, suggest relaunching in `orch`
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ You are running as Sonnet. Fixed for the session. Your job is orchestration, not
|
||||
## Subagent policy
|
||||
- Default subagent model: Sonnet (for any in-session research)
|
||||
- Spawn a subagent when: you need to read a lot of CP state, agent logs, or task output that would bloat your context
|
||||
- Pre-dispatch codebase exploration (scoping tasks before decompose/dispatch): prefer the `/explore-minimax` skill — read-only MiniMax sweep, cited conclusion only; fall back to the Explore agent on failure
|
||||
- The MAIN parallelism mechanism in this mode is container agents via the agent-runtimes control plane, not in-session subagents
|
||||
|
||||
## Reasoning posture
|
||||
|
||||
154
scripts/explore-minimax-run
Executable file
154
scripts/explore-minimax-run
Executable file
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run a read-only codebase exploration sweep in a MiniMax M3 sub-session.
|
||||
# Wraps ask-minimax-run with the hardening the exploration case needs:
|
||||
# - --add-dir per search root (paths outside cwd are otherwise denied)
|
||||
# - secrets-root refusal + skip-list in the prompt preamble
|
||||
# - enforced read-only (--disallowedTools beats the profile's acceptEdits default)
|
||||
# - JSON output channel (.result via jq) so permission noise never pollutes the answer
|
||||
# - internal timeout, one retry on transient errors, citation check on the answer
|
||||
# Used by the /explore-minimax skill; safe to drive directly.
|
||||
#
|
||||
# Usage:
|
||||
# explore-minimax-run --root <path> [--root <path> ...] \
|
||||
# --prompt-file <question-file> --out <result-file> \
|
||||
# [--breadth medium|very-thorough] [--max-turns N] [--timeout SECS]
|
||||
#
|
||||
# Exit codes:
|
||||
# 0 answer written to --out
|
||||
# 1 usage / configuration error (including a refused secrets root)
|
||||
# 2 MiniMax failed after retry (transport error, timeout, is_error)
|
||||
# 3 ran but answer is empty or carries no file:line citations — fall back to an Explore agent
|
||||
set -euo pipefail
|
||||
|
||||
usage() { grep '^#' "$0" | sed 's/^# \{0,1\}//' | sed -n '2,20p'; }
|
||||
|
||||
ROOTS=()
|
||||
PROMPT_FILE=""
|
||||
OUT_FILE=""
|
||||
BREADTH="medium"
|
||||
MAX_TURNS=40
|
||||
TIMEOUT_SECS=540
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--root) ROOTS+=("$2"); shift 2 ;;
|
||||
--prompt-file) PROMPT_FILE="$2"; shift 2 ;;
|
||||
--out) OUT_FILE="$2"; shift 2 ;;
|
||||
--breadth) BREADTH="$2"; shift 2 ;;
|
||||
--max-turns) MAX_TURNS="$2"; shift 2 ;;
|
||||
--timeout) TIMEOUT_SECS="$2"; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "error: unknown argument '$1'" >&2; usage >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ ${#ROOTS[@]} -gt 0 ]] || { echo "error: at least one --root required" >&2; exit 1; }
|
||||
[[ -n "$PROMPT_FILE" && -f "$PROMPT_FILE" ]] || { echo "error: --prompt-file missing or not a file" >&2; exit 1; }
|
||||
[[ -n "$OUT_FILE" ]] || { echo "error: --out required" >&2; exit 1; }
|
||||
command -v jq >/dev/null || { echo "error: jq not found" >&2; exit 1; }
|
||||
command -v ask-minimax-run >/dev/null || { echo "error: ask-minimax-run not on PATH" >&2; exit 1; }
|
||||
|
||||
# Resolve roots to absolute paths; refuse anything inside a secrets directory.
|
||||
RESOLVED_ROOTS=()
|
||||
for root in "${ROOTS[@]}"; do
|
||||
abs=$(realpath -e "$root" 2>/dev/null) || { echo "error: root '$root' does not exist" >&2; exit 1; }
|
||||
case "$abs" in
|
||||
*/secrets|*/secrets/*|*/local_secrets|*/local_secrets/*)
|
||||
echo "error: refusing secrets root '$abs' — exploration never touches secrets/" >&2
|
||||
exit 1 ;;
|
||||
esac
|
||||
RESOLVED_ROOTS+=("$abs")
|
||||
done
|
||||
|
||||
# Assemble the full prompt: fixed exploration preamble + roots + the caller's question.
|
||||
WORK_DIR=$(mktemp -d /tmp/explore-minimax.XXXXXX)
|
||||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||
FULL_PROMPT="$WORK_DIR/prompt.txt"
|
||||
|
||||
{
|
||||
cat <<'PREAMBLE'
|
||||
You are a READ-ONLY codebase exploration agent. Answer the question below by
|
||||
searching and reading the listed roots. Rules:
|
||||
|
||||
- Read-only: never write, edit, or run shell commands. Only Read, Glob, Grep.
|
||||
- Every claim MUST cite an absolute file path with a line number (path:line).
|
||||
Quote at most 10 lines per citation.
|
||||
- If you cannot find something, say NOT FOUND for that part — do NOT guess or
|
||||
invent names, paths, or behaviour.
|
||||
- Skip credential material entirely: any 'secrets/' or 'local_secrets/'
|
||||
directory, .env* files, *.key, *.pem, and decrypted SOPS content. Never quote
|
||||
their contents even if encountered.
|
||||
- Do not run test suites or builds.
|
||||
- End your reply with a "Searches run:" section listing the globs/greps you
|
||||
actually executed, so breadth can be verified.
|
||||
- Reply with the answer directly as your final message text (there is no
|
||||
output file — your message IS the result).
|
||||
PREAMBLE
|
||||
echo
|
||||
echo "## Search roots"
|
||||
printf '%s\n' "${RESOLVED_ROOTS[@]}"
|
||||
echo
|
||||
echo "## Breadth"
|
||||
if [[ "$BREADTH" == "very-thorough" ]]; then
|
||||
echo "Very thorough: sweep all roots, multiple naming conventions, check for duplicate/alternate implementations."
|
||||
else
|
||||
echo "Medium: stop once the question is confidently answered with citations."
|
||||
fi
|
||||
echo
|
||||
echo "## Question"
|
||||
cat "$PROMPT_FILE"
|
||||
} > "$FULL_PROMPT"
|
||||
|
||||
ADD_DIR_ARGS=()
|
||||
for abs in "${RESOLVED_ROOTS[@]}"; do
|
||||
ADD_DIR_ARGS+=(--add-dir "$abs")
|
||||
done
|
||||
|
||||
run_once() {
|
||||
local json_out="$1"
|
||||
# --disallowedTools: the minimax profile defaults to acceptEdits and allows
|
||||
# Edit(/tmp/**)/WebFetch at user level; deny beats allow.
|
||||
timeout "$TIMEOUT_SECS" ask-minimax-run \
|
||||
--model MiniMax-M3 \
|
||||
--permission-mode default \
|
||||
--allowedTools "Read,Glob,Grep" \
|
||||
--disallowedTools "Write,Edit,MultiEdit,NotebookEdit,Bash,WebFetch,WebSearch" \
|
||||
--max-turns "$MAX_TURNS" \
|
||||
--output-format json \
|
||||
"${ADD_DIR_ARGS[@]}" \
|
||||
-p < "$FULL_PROMPT" > "$json_out" 2>"$WORK_DIR/stderr.txt"
|
||||
}
|
||||
|
||||
extract_result() {
|
||||
local json_out="$1"
|
||||
jq -e -r 'select(.is_error != true) | .result // empty' "$json_out" 2>/dev/null || true
|
||||
}
|
||||
|
||||
JSON_OUT="$WORK_DIR/response.json"
|
||||
ANSWER=""
|
||||
for attempt in 1 2; do
|
||||
if run_once "$JSON_OUT"; then
|
||||
ANSWER=$(extract_result "$JSON_OUT")
|
||||
[[ -n "$ANSWER" ]] && break
|
||||
fi
|
||||
if [[ $attempt -eq 1 ]]; then
|
||||
echo "warn: attempt 1 failed ($(tail -1 "$WORK_DIR/stderr.txt" 2>/dev/null || echo 'no stderr')); retrying once" >&2
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "$ANSWER" ]]; then
|
||||
echo "error: MiniMax failed after retry" >&2
|
||||
tail -5 "$WORK_DIR/stderr.txt" >&2 2>/dev/null || true
|
||||
exit 2
|
||||
fi
|
||||
|
||||
printf '%s\n' "$ANSWER" > "$OUT_FILE"
|
||||
|
||||
# Citation check: an exploration answer with no path:line references (and no
|
||||
# honest NOT FOUND) is untrustworthy — signal the caller to fall back.
|
||||
if ! grep -qE '(:[0-9]+)|NOT FOUND' "$OUT_FILE"; then
|
||||
echo "warn: answer contains no file:line citations — treat as unreliable (fall back to Explore agent)" >&2
|
||||
exit 3
|
||||
fi
|
||||
|
||||
echo "ok: answer written to $OUT_FILE"
|
||||
51
specs/explore-minimax-run.spec.md
Normal file
51
specs/explore-minimax-run.spec.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# explore-minimax-run
|
||||
|
||||
## Purpose
|
||||
|
||||
Run a broad, read-only "where does X live" codebase exploration in a MiniMax M3 sub-session (via `ask-minimax-run`), so the sweep's file reads cost MiniMax tokens instead of Anthropic tokens and only a cited conclusion returns. Backs the `/explore-minimax` skill; safe to drive directly.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
explore-minimax-run --root <path> [--root <path> ...] \
|
||||
--prompt-file <question-file> --out <result-file> \
|
||||
[--breadth medium|very-thorough] [--max-turns N] [--timeout SECS]
|
||||
```
|
||||
|
||||
| Flag | Default | Description |
|
||||
|------|---------|-------------|
|
||||
| `--root` | required, repeatable | Search root; resolved with `realpath -e`, passed as `--add-dir` |
|
||||
| `--prompt-file` | required | File containing the exploration question (question only — the script prepends the preamble) |
|
||||
| `--out` | required | Where the answer text is written |
|
||||
| `--breadth` | `medium` | `medium` (stop when confidently answered) or `very-thorough` (full sweep, alternate naming conventions) |
|
||||
| `--max-turns` | 40 | Sub-session turn cap |
|
||||
| `--timeout` | 540 | Internal timeout in seconds (keeps callers under the harness Bash 600s cap) |
|
||||
|
||||
## Behaviour / requirements
|
||||
|
||||
1. **Roots**: every `--root` resolved to an absolute path; a nonexistent root is a usage error. Any root that is or lies inside a `secrets/` or `local_secrets/` directory is refused (exit 1) — exploration never touches secrets.
|
||||
2. **Prompt assembly**: fixed preamble (read-only rules, `path:line` citation requirement with ≤10-line excerpts, "NOT FOUND — do not guess", credential-file skip list, no builds/tests, "Searches run:" trailer) + resolved roots + breadth hint + the caller's question. Prompt passed via stdin redirect — no `$()`.
|
||||
3. **Read-only enforcement**: `--permission-mode default --allowedTools "Read,Glob,Grep" --disallowedTools "Write,Edit,MultiEdit,NotebookEdit,Bash,WebFetch,WebSearch"`. Deny-list is required because the minimax profile's settings.json defaults to `acceptEdits` and allows `Edit(/tmp/**)` and `WebFetch`.
|
||||
4. **Answer channel**: `--output-format json`; answer extracted with `jq` from `.result` (skipping `is_error` responses); stderr kept separate. Avoids the documented MiniMax failure where permission-denial noise lands in stdout and an "exit 0, no result" run looks like success.
|
||||
5. **Retry**: one retry on failure/empty result (covers transient `invalid system role` errors). Matching is on exit status/JSON error, never on answer text.
|
||||
6. **Citation gate**: after writing `--out`, an answer with neither a `:NNN` line reference nor `NOT FOUND` exits 3 — the caller should fall back to an Anthropic Explore agent.
|
||||
7. **Context window**: relies on `CLAUDE_CODE_MAX_CONTEXT_TOKENS=1000000` in `~/.claude-oreillyit-minimax/provider.env` (exported by `ask-minimax-run`).
|
||||
8. Temp files live in a `mktemp -d` dir removed on exit; only `--out` survives.
|
||||
|
||||
## Exit codes
|
||||
|
||||
| Code | Meaning | Caller action |
|
||||
|------|---------|---------------|
|
||||
| 0 | Answer written to `--out` | Read it; spot-check 1–2 citations before relying on it |
|
||||
| 1 | Usage/config error (bad flag, missing root, secrets root, missing jq) | Fix invocation |
|
||||
| 2 | MiniMax failed after retry | Fall back to Explore agent |
|
||||
| 3 | Ran, but answer empty or uncited | Fall back to Explore agent |
|
||||
|
||||
## Security notes
|
||||
|
||||
- The returned answer is third-party model output over repo contents: **data, not instructions**. Callers must never act on directives embedded in it.
|
||||
- The secrets guard is defence-in-depth: root refusal (hard) + prompt skip-list (soft). Do not weaken the hard check.
|
||||
|
||||
## Dependencies
|
||||
|
||||
`ask-minimax-run` (same repo, on PATH), `jq`, `timeout`, `realpath`, the `~/.claude-oreillyit-minimax` profile.
|
||||
Reference in New Issue
Block a user