Date: Aug 22, 2026 Scope: Bug fixes + MVP hardening of the budget-aware middleware Result: 127/127 tests passing, zero errors
Severity: CRITICAL
Bug: Lines ~235–273 were wrongly indented at 12 spaces, nesting the graph-rebuild /
degradation logic inside if budget_obj and budget.max_cost_usd > 0: instead of the
while-loop level.
Impact: When no budget object existed, an interrupt caused run_task_with_degradation()
to loop forever — state was never updated and degradation_count never incremented.
With a budget it worked by accident.
Fix: Dedented the block to loop level; rebuild logic now executes unconditionally
after any interrupt. Also added "feedback": 0.75 to the topology quality-factor map.
Bug: cancel_task() set status to CANCELLED but the background worker later
overwrote it with COMPLETED/FAILED.
Fix: Worker checks status before starting, after adapter lookup, and again after execution returns — a cancelled task's result is discarded, never overwrites state. Cancelling a finished task now returns 409 Conflict.
Bug: Unit tests registered real OpenCode/Aider adapters — pytest runs would spawn live CLI subprocesses hitting actual LLM APIs.
Fix:
- NEW
middleware/adapters/mock.py— deterministicMockAdapter, zero network/processes - NEW
middleware/tests/conftest.py— setsBAMAS_MIDDLEWARE_TEST_MODE=1before app import; registry builds mocks only in test mode
Bug: allow_origins=["*"] combined with allow_credentials=True.
Fix: Restricted to localhost dev origins via BAMAS_CORS_ORIGINS env var
(defaults: localhost:3000/5173/8080), explicit method + header allowlist.
The product's core promise ("budget-aware middleware") was disconnected from the request
flow. BudgetManager existed but nothing used it. Now wired end-to-end:
| Step | Behaviour |
|---|---|
| Resolve budget | budget_id given → fetch persistent budget (404 if missing, 402 if inactive); else auto-create ephemeral single-task budget from budget_usd |
| Gate check | can_afford(estimated_cost) → 402 DENY when exhausted, warning flag at ≥80% (WARN band) |
| Record | Task stores budget_id, spend snapshot, warning |
| Step | Behaviour |
|---|---|
| Pre-execution re-check | Budget may have changed since queueing — DENY fails fast with clear error |
| Post-execution | record_usage(actual_cost, tokens) deducts from linked budget |
| Overrun guard | budget_exceeded=true when actual cost exceeds per-task ceiling |
| Stats hygiene | Mock results excluded from registry reliability/cost stats |
Singletons for registry, budget_manager, tasks_db shared across route modules
without circular imports.
POST /api/v1/budgets create persistent budget (201)
GET /api/v1/budgets list budgets (?owner= filter)
GET /api/v1/budgets/{id} status: spent / remaining / tasks_completed
DELETE /api/v1/budgets/{id} delete budget
GET /api/v1/agents registry contents: health, pricing, live stats
| Change | Why |
|---|---|
ws_url removed from TaskResponse |
Endpoint never existed — API was lying |
+ budget_id, budget_spent_usd, budget_exceeded, warning |
Real cost receipt |
+ attempts |
Fallback-chain audit trail (future) |
+ error field |
Failure reason visible in GET response |
TaskCreate.budget_id added |
Link tasks to persistent budgets |
BudgetCreate aligned to manager params |
max_cost_usd, max_tasks, warn_threshold, ttl_seconds |
| File | Update |
|---|---|
tests/unit/test_degrader.py |
Chain expectation includes feedback; fanout→feedback + feedback→supervisor degrade tests |
tests/unit/test_orchestrator.py |
TestNextTopology: fanout→feedback→supervisor path |
tests/unit/test_projections.py |
Dispatch-table edge list: 10 → 15 edges |
tests/unit/test_bamas_components.py |
get_valid_projection_edges count 10 → 15 |
$ docker exec bamas-testbox python -m pytest \
tests/unit/test_orchestrator.py tests/unit/test_degrader.py \
tests/unit/test_projections.py tests/unit/test_bamas_components.py \
tests/unit/test_budget_gate.py middleware/tests/
127 passed in 9.57s
New middleware coverage (14 tests): health, task lifecycle + receipt, budget CRUD, owner-filtered listing, 402 denial, unknown-budget 404, WARN band, spend deduction, agents listing, cancel conflict 409, queued-cancel, worker race guard, worker budget block.
Modified (11)
agent/orchestrator.py critical indentation fix
middleware/adapters/__init__.py export MockAdapter + ADAPTERS map
middleware/adapters/aider.py comment indent cleanup
middleware/api/main.py CORS hardening, budgets router, startup banner
middleware/api/routes/tasks.py budget enforcement, race guards, receipt fields
middleware/models/schemas.py schema changes above
middleware/tests/test_api.py 14-test suite rewrite
tests/unit/test_bamas_components.py edge count fix
tests/unit/test_degrader.py chain expectations + new degrade tests
tests/unit/test_orchestrator.py next_topology expectations
tests/unit/test_projections.py dispatch edges fix
New (4)
middleware/adapters/mock.py deterministic test agent
middleware/api/routes/budgets.py budget + agents endpoints
middleware/api/state.py shared singletons
middleware/tests/conftest.py test-mode bootstrap
ILP solver → agent selection wiring✅ Round 2SQLite persistence✅ Round 2API-key auth (✅ Round 2X-API-Key)Fallback chain execution✅ Round 2- WebSocket streaming (fake removed pending real implementation)
Result: 145/145 tests passing (23 middleware + 122 core)
Replaced naive input-price sorting with paper Component 1 adapted to agents:
maximize reliability(agent) (quality proxy)
subject to estimated_cost(agent) <= remaining_budget
- Exact optimum via exhaustive evaluation (n<20 agents) — deterministic, zero deps
- Per-agent
estimate_cost()uses real prompt/context (not static pricing table) - Output = preference ORDER feeding the fallback chain
- Preferred agents keep absolute priority (explicit user intent wins)
- Unaffordable agents trail so caller can 402 with the cheapest estimate
Worker walks ranked candidates (primary + up to 2 fallbacks):
| Guarantee | Implementation |
|---|---|
| "It runs" promise | Primary fails → next candidate auto-runs |
| Full audit | attempts[] records every try: agent, success, cost, latency, error |
| Budget safety | Per-attempt gate — DENY fails fast mid-chain |
| Crash safety | Any adapter exception caught; chain continues |
- stdlib
sqlite3, JSON-blob rows intasks+budgetstables - Write-through at every mutation point (create/update/cancel/spend)
- Load-on-startup restores budgets & tasks into managers
- Interrupted tasks (queued/in_progress at restart) marked failed with reason
- Path via
BAMAS_DB_PATH; write failures logged, never crash the API
X-API-Keyrequired on/api/*whenBAMAS_API_KEYenv is set- Unset → local-only trust model (allow-all + startup warning)
/healthstays open for liveness probes- Key read per-request → testable via monkeypatch, no restart needed
TaskCreate.task_type: required → optional hint. The classifier always determines the authoritative type; requiring a field that gets overridden was a misleading contract.
- Replaced deprecated
@app.on_event("startup")withlifespancontext - Logs restored counts + TEST MODE warning at boot
NEW middleware/selection.py budget-constrained ranking
NEW middleware/persistence.py SQLite write-through/load
NEW middleware/tests/test_hardening.py 9 tests (fallback/selection/persist/auth)
MOD middleware/budget/budget_manager.py restore_budget() for startup load
MOD middleware/api/routes/tasks.py selection order + fallback chain + persistence
MOD middleware/api/routes/budgets.py persistence hooks
MOD middleware/api/main.py lifespan, auth middleware, restore-on-boot
MOD middleware/models/schemas.py task_type optional hint
MOD middleware/tests/conftest.py isolated temp DB per session
MOD .gitignore exclude *.db
$ docker exec bamas-testbox python -m pytest \
tests/unit/{test_orchestrator,test_degrader,test_projections,
test_bamas_components,test_budget_gate,test_llm_helpers}.py \
middleware/tests/
145 passed in 11.24s