Release v0.4.8
What's Changed
PR #25: feat: automatic context handoff on budget exhaustion (GH-22)
Author: @moneypennyrasener
Summary
Implements automatic context handoff when a Claude Code session exhausts its --max-budget-usd limit mid-stage, and fixes the existing max-turns handoff to preserve conversation context via --continue.
Two changes per the spec in issue #22:
1. Budget-based handoff (new feature)
When a stage hits its budget cap, fastflow now:
- Detects
subtype: "error_max_budget_usd"from--output-format json - Runs
claude --continue -p "<handoff prompt>"on the same session so it has full context to write.fastflow/handoff.md - Starts a fresh session that reads the handoff and resumes the original task
- Loops up to
MaxResumptionstimes if the fresh session also runs out of budget
2. Fix max-turns handoff to use --continue
The existing runHandoffCycle was starting a new session for create_handoff, losing all conversation context. Both budget and max-turns handoffs now use --continue for the create_handoff step.
Changes
internal/config/
Config.Defaults.MaxBudgetUsd— global fallback budget limitStage.MaxBudgetUsd— per-stage override (pointer, so explicit0overrides global)Config.EffectiveBudget(stage)— resolution helper (stage wins over default)- Validation: rejects negative budgets at both levels
- New tests:
config_test.go,validate_test.go
internal/templates/files/orchestrator.json
- Adds
defaultsblock withmaxBudgetUsd: 0and_maxBudgetNotedocumenting recommended values - Sets
planstage tomaxBudgetUsd: 10.00as a working example
internal/runner/claude.go
ClaudeInvoker.MaxBudgetUsd: when > 0, switches CLI to--output-format json+--max-budget-usd; non-budget runs keep--printstreaming unchangedclaudeJSONResultstruct + subtype constants (success/error_max_budget_usd/error_max_turns)InvokeResultgainsHitBudgetCap,SessionID,TotalCostUsdrunWithJSONParsing(): captures and parses structured Claude CLI JSON outputInvokeContinue(): runsclaude --continuefor handoff creation step
internal/runner/runner.go
executeStage: setsinvoker.MaxBudgetUsdfrom config, adds budget exhaustion loop callingrunBudgetHandoffCyclerunBudgetHandoffCycle: two-step handoff (continue → fresh session)
internal/runner/stream.go
- Captures
session_idandis_errorfrom stream-json result events - Populates
HitBudgetCap,HitMaxTurns,SessionIDonInvokeResultfor verbose mode
Test plan
-
go test ./internal/...— all tests pass -
TestEffectiveBudget_*— stage override, default fallback, zero override, no budget -
TestValidate_*Budget*— negative stage budget, negative default budget, valid budgets -
TestLoadConfigWithBudget— round-trip JSON load with budget fields -
TestClaudeJSONResultParsing— success, budget exhausted, max turns subtypes -
TestTemplateContainsBudgetConfig— orchestrator.json template has required fields - End-to-end: run a stage with a low
maxBudgetUsdto trigger handoff (requires live Claude CLI)
Notes
- When
maxBudgetUsdis not configured (default0), behaviour is identical to before — no overhead, no JSON output-format switch. - The
_maxBudgetNotefield in orchestrator.json is a documentation comment field (ignored by the config loader). - Handoff file is cleaned up after a successful budget handoff cycle.
Closes #22
Full Changelog: v0.4.7...v0.4.8
What's Changed
- feat: automatic context handoff on budget exhaustion (GH-22) by @moneypennyrasener in #25
Full Changelog: v0.4.7...v0.4.8