Files
small-scripts/specs/bg-model-call.spec.md
Paul O'Reilly f50561ea78 feat(mm-worktree-task): run from any planner-backed repo — cp_cli dir + context resolution (--context, MM_WORKTREE_TASK_CP_*), empty-docs guard, tests + spec
cp_cli lives in agent-runtimes; previously every planner call ran 'python3 -m lib.cp_cli' from the current repo with no --context, so the script only worked inside agent-runtimes. The context now resolves from ~/.cpconfig by matching project_id to the repo directory name. Verified live from hatchmate (item + parent + documents fetched via the hatchmate context).

Claude-Session: https://claude.ai/code/session_01JgJbWzVJioVznVTFyQFEfx
2026-09-11 16:27:08 +12:00

7.2 KiB

bg-model-call

Purpose

Submit a prompt to a third-party model via the agent-runtimes control plane, wait for completion, and write the result to a file — enabling the ScheduleWakeup polling pattern for second-opinion consultations without blocking the main Claude Code session.

Usage

bg-model-call [OPTIONS]

Required flags

Flag Description
--model <name> Model alias: minimax, sonnet, haiku, opus
--prompt <file> Path to a file containing the prompt text
--output <file> Path where the result will be written

Optional flags

Flag Default Description
--template <name> (derived from --model) Override the task template name (without .yaml). Takes precedence over the --model → template mapping.
--project <id> bg-model-call Project ID sent to the CP, used for task grouping in agent-monitor
--url <cp-url> $CP_URL, then http://localhost:8100 Control plane base URL
--task-name <name> bg-model-call-<model> Short task name shown in agent-monitor
--timeout <seconds> 1800 Max seconds to wait for the task before declaring it failed
--poll-interval <seconds> 10 Seconds between CP state polls
--dryrun, -n — Resolve all values and print what would be submitted; do not call the CP or write any files
--help, -h — Show usage information and exit 0

Model-to-Template Mapping

A constant defined at the top of the script:

Model alias Template name
minimax minimax-planning
sonnet sonnet-code-repo
haiku haiku-code-repo
opus opus-code-repo

The --template flag overrides this mapping entirely.

Behaviour

  1. Parse and validate all flags. Exit 1 with an error message if required flags are missing or values are invalid.
  2. Resolve the CP URL: --url flag → $CP_URL env var → http://localhost:8100.
  3. Resolve the template name: --template flag if given, otherwise look up --model in the mapping table. If the model alias is not in the table, exit 1.
  4. Read the prompt text from the --prompt file. If the file is not readable, exit 1.
  5. Locate the dispatch-task CLI:
    • Check if dispatch-task is on PATH.
    • Otherwise try <script-dir>/../../../projects/agent-runtimes/scripts/dispatch-task as a fallback (for dev environments).
    • If neither is found, fall back to direct curl POST to $CP_URL/tasks.
  6. Submit the task to the CP via the resolved dispatch-task (or curl fallback):
    • --name <task-name>
    • --project-id <project>
    • --prompt-file <prompt-file>
    • --template <template>
    • --cp-url <cp-url>
    • --format json (to capture the task_id from stdout)
    • The dispatch-task tool uses CP_AUTH_TOKEN from the environment if present.
  7. Extract the task_id from the submission response.
  8. Write a .bg-model-call-<task-id> tracking file in the same directory as --output (content: task ID, model, template, timestamp). This avoids collisions between concurrent calls.
  9. Poll the CP using wait-for-tasks (or the dispatch-task --wait pattern):
    • Locate wait-for-tasks using the same PATH + fallback logic as dispatch-task.
    • If not found, poll manually via curl GET $CP_URL/tasks/<task-id> in a sleep loop.
    • Poll every --poll-interval seconds up to --timeout seconds total.
  10. Once the task reaches a terminal state:
    • Succeeded: Fetch logs via GET $CP_URL/tasks/<task-id>/logs. Write the log content to --output. Write <output>.done sentinel (content: task_id=<id> + newline + completed_at=<timestamp>). Remove the tracking file.
    • Failed / timed_out / cancelled: Write <output>.failed sentinel (content: task_id=<id>, state=<state>, error=<detail if available>). Remove the tracking file. Exit 1.
  11. Exit 0 on success.

Output Files

File When written Content
<output> On success Raw text from GET /tasks/<id>/logs
<output>.done On success task_id=<id>, completed_at=<timestamp>
<output>.failed On failure/timeout task_id=<id>, state=<state>, error=<detail>
.bg-model-call-<task-id> After submission task_id=<id>, model=<alias>, template=<name>, submitted_at=<timestamp>

The .done sentinel is the signal the caller polls for. The caller reads <output> once .done appears.

Dryrun Behaviour

When --dryrun or -n is passed, the script:

  1. Resolves all values (CP URL, template, task name, project ID).
  2. Reads (and validates) the prompt file.
  3. Prints a summary of what would be submitted — no CP calls, no file writes.

Example output:

[dryrun] bg-model-call — no changes will be made
  CP URL:       http://localhost:8100
  Model:        minimax
  Template:     minimax-planning
  Task name:    bg-model-call-minimax
  Project ID:   bg-model-call
  Prompt file:  /tmp/my-prompt.txt
  Prompt size:  142 bytes
  Output file:  /tmp/result.txt
  Sentinel:     /tmp/result.txt.done
  dispatch-task: /home/user/dev/claude/projects/agent-runtimes/scripts/dispatch-task

Edge Cases

Case Handling
--model missing and --template missing Error: --model is required (or use --template to specify a template directly), exit 1
--prompt missing Error: --prompt is required, exit 1
--output missing Error: --output is required, exit 1
--model not in mapping table Error: Unknown model alias '<name>'. Valid values: minimax, sonnet, haiku, opus, exit 1
Prompt file not readable Error: Cannot read prompt file: <path>, exit 1
--timeout not a positive integer Error: --timeout must be a positive integer, exit 1
--poll-interval not a positive integer Error: --poll-interval must be a positive integer, exit 1
CP submission fails Write <output>.failed, exit 1
Task times out (wait exceeds --timeout) Write <output>.failed with state=timed_out, exit 1
Output directory does not exist Error: Output directory does not exist: <dir>, exit 1

Security

  • No credentials in the script. CP authentication (if required) is handled by dispatch-task reading CP_AUTH_TOKEN from the environment. The bg-model-call script only passes --cp-url and never handles tokens directly.
  • Prompt content is passed via file (--prompt-file), never as a command-line argument. This avoids the prompt appearing in ps output.

Examples

# Submit to MiniMax and poll in the background; check for result from a wakeup
echo "Review this spec for security issues: $(cat spec/auth.md)" > /tmp/minimax-prompt.txt
bg-model-call \
  --model minimax \
  --prompt /tmp/minimax-prompt.txt \
  --output /tmp/minimax-result.txt \
  --project my-project &

# Later (from ScheduleWakeup), check for completion:
if [[ -f /tmp/minimax-result.txt.done ]]; then
  cat /tmp/minimax-result.txt
fi

# Dryrun to inspect resolved values
bg-model-call \
  --model sonnet \
  --prompt /tmp/my-prompt.txt \
  --output /tmp/result.txt \
  --dryrun

# Use a specific template instead of the model mapping
bg-model-call \
  --template minimax-planning \
  --prompt /tmp/prompt.txt \
  --output /tmp/result.txt \
  --dryrun