analyze’s LLM rules, and the whole Spec → Guard pipeline: spec scan, guard setup, guard generate) reaches the model through a pluggable transport:
The choice between Claude Code and API is a saved, per-user setting;
agent is a per-run mode for an orchestrating agent. All three send identical prompts and parse identical schema-validated JSON; only the delivery differs.
First run
The very firsttruecourse command you run, whichever it is, asks once and saves the answer:
truecourse config llm
setup takes flags for non-interactive use (CI, dotfiles); passing --transport skips every prompt:
Where the selection lives
The selection lives in~/.truecourse/config.json: per-user, written 0600 inside a 0700 directory, deliberately not the committable per-repo .truecourse/config.json. TRUECOURSE_HOME relocates the whole directory.
api block persists even while transport is claude-code, so flipping between the two never re-asks for credentials.
Where the key comes from, in order: llm.api.apiKey, then the variable named by llm.api.apiKeyEnv, then the provider’s standard variable (ANTHROPIC_API_KEY, OPENAI_API_KEY, or COPILOT_API_KEY). Bedrock has none of these: omitted credentials fall through to the ambient AWS chain. Store no key at all and TrueCourse reads it from the environment on every run.
TRUECOURSE_LLM_TRANSPORT=claude-code|api overrides the saved selection for a single run or a CI job.
In API mode nothing shells out to claude, so its login preflight is skipped; instead an unusable provider configuration aborts up front, before any pipeline work or cost, with a pointer to truecourse config llm setup. Credentials are entered through the CLI or the config file only: the dashboard reads the selection but never edits it.
Per-run override: —llm-transport
--llm-transport <cli|agent|api> overrides the saved selection for one command: cli forces Claude Code, api forces the configured provider, agent uses the mailbox. Accepted by analyze, spec scan, guard setup, and guard generate.
On
analyze, --llm / --no-llm is a separate flag: it decides whether LLM rules run; --llm-transport decides how to reach the model.The agent mailbox
Inagent mode the tool doesn’t call the model itself: for each prompt it writes requests/<id>.json ({ stage, system, user, schema, … }) into the --io directory and waits for a matching responses/<id>.json ({ text }). An orchestrating agent that is itself an LLM (e.g. a Claude Code routine) watches that directory and answers each prompt. This lets guard generation and analyze’s LLM rules run inside a headless cloud session with no claude binary and no API key.
Next steps
Models & environment
Per-stage model selection, concurrency, and timeouts.
Storage
Where the per-user config lives, and everything else on disk.