Dry-run mode
Dry-run is a diagnostic path. By default it prints a safe simulation for any configured codename, without touching the scheduler, GitHub, Slack, AWS, Playwright, an LLM, or a real worktree. Runners that declare native dry-run support can also execute their lifecycle hooks with outside-world calls stubbed.
A developer with nothing configured (no gh auth, no AWS, no Slack, no Claude) can run a dry-run and see the sequence Alfred would follow. The output is a narrated, step-numbered trace.
Condensed companion to docs/DRY_RUN.md.
How it differs from doctor mode
Section titled “How it differs from doctor mode”ALFRED_DOCTOR=1 short-circuits a runner to a preflight-only check: it verifies host configuration and exits before the lifecycle starts.
Dry-run is the opposite: it shows the firing path and, for native dry-run runners, executes that path with outside calls stubbed. Use doctor mode to answer “is this host configured correctly?”; use dry-run to answer “what does a firing do, step by step?”.
Try it in 2 minutes
Section titled “Try it in 2 minutes”From a fresh checkout (no install needed), put lib/ on PYTHONPATH:
git clone https://github.com/luminik-io/alfred-os.git ~/code/alfred-oscd ~/code/alfred-osPYTHONPATH=lib python3 examples/bin/echo_summarise.py --dry-runYou get a step-numbered trace of the full lifecycle and an exit code of 0. The same works for examples/bin/hello.py (the minimal agent) and bin/lucius.py (the feature-dev agent).
After install, use the Alfred CLI:
alfred dry-run luciusalfred dry-run drakealfred dry-run allalfred dry-run lucius --nativeEvery configured codename resolves through this command. By default Alfred
prints a safe simulation that never touches the scheduler, GitHub, Slack, AWS,
Playwright, an LLM, or a real worktree. Pass --native when you want a runner
that declares native dry-run support to execute with every side-effecting seam
stubbed.
Activating it
Section titled “Activating it”Two equivalent switches:
- The
ALFRED_DRY_RUNenvironment variable, set to any truthy value (1,true,yes,on). - The
--dry-runCLI flag, accepted by the example runners andbin/lucius.py.
A runner that sees --dry-run calls agent_runner.set_dry_run(), which writes ALFRED_DRY_RUN=1 back into the process environment so every downstream code path, and any subprocess-spawned child, agrees on the mode.
What is stubbed vs real
Section titled “What is stubbed vs real”Everything inside Alfred runs for real: the lock, preflight narration, event log, prompt construction, and the runner’s own result-branching logic. Calls to the outside world stay stubbed.
Every side-effecting boundary is stubbed behind a single is_dry_run() helper in the agent_runner package:
| Boundary | Dry-run behaviour |
|---|---|
claude_invoke, codex_invoke, invoke_agent_engine | Return a clearly-marked synthetic result (cost_usd=0.0, result_text labelled [dry-run] synthetic ...). No LLM is ever invoked. |
SpendState | Write a separate spend-dryrun-<date>.json ledger. The real per-day counters are never touched, so a dry-run can’t trip a daily cap. |
set_global_block | Log the provider-limit block it would set; the real scheduler block file is never written. |
slack_post | Log the line it would post (severity included) and return success. The webhook is never hit. |
claim_issue, release_issue, gh_pr_create, gh_issue_edit, and the other gh helpers | Log the gh call that would run and return success. No gh subprocess is spawned. |
make_worktree / remove_worktree | Create a self-contained throwaway git repo in a temp dir, coherent enough for a runner to inspect, then remove it. Nothing is fetched from or pushed to a real remote. |
With nothing configured, the runners also substitute clearly-labelled fake data: a synthetic issue from pick_issue, a dry-run-org/<repo> placeholder when GH_ORG is unset, and a dry-run-repo slug when the repo env vars are missing. Preflight still runs and still reports what is missing. In dry-run, the runner narrates the gap and continues instead of exiting.
See docs/DRY_RUN.md for the full seam table and for how to add dry-run support to your own runner.