Skip to content

feat(agents,budget): per-persona spending-cap primitive (OpenRouter Phase 0)#411

Merged
thinmintdev merged 1 commit into
mainfrom
feature/phase0-budget
May 29, 2026
Merged

feat(agents,budget): per-persona spending-cap primitive (OpenRouter Phase 0)#411
thinmintdev merged 1 commit into
mainfrom
feature/phase0-budget

Conversation

@thinmintdev
Copy link
Copy Markdown
Contributor

Summary

  • New [persona.budget] TOML block + BudgetLedger (append-only JSONL at /var/lib/hal0/agents/{id}/personas/{pid}/spend.jsonl)
  • /api/agents/{id}/personas/{pid}/budget REST surface (GET / PUT / check / charge)
  • React UI editor in personas-tab.jsx (PersonaBudgetPanel + TanStack hooks)
  • Persona seed (hermes + coder) keeps an empty budget block; operator opts in

Why now

DA review of the OpenRouter integration plan flagged this as P0 must-fix #3 (fusion 4.4x cost + recursion = $200/credit drain risk). Lands BEFORE V1 OpenRouter provider + V2 fusion MCP so they have a budget gate from day 1.

Scope locked

  • Per-persona only for v0.3 (PLANNING.md §5 Q2 default). Per-agent + platform caps deferred to v0.4 — both are strict supersets of this primitive.
  • Append-only JSONL ledger: no SQLite dependency, fsync per write, operator-inspectable with tail -f + jq.
  • hard_cap=True default (block over budget); hard_cap=False is warn-only.
  • Race tolerance: check-then-record is NOT serialised; periodic over-spend within one window tolerated (documented in module docstring).
  • No provider charges to this primitive yet — V1 OpenRouter upstream wires it in.

Test plan

  • Pure-Python unit tests: Budget dataclass, parse_budget, BudgetLedger round-trip, check_budget edge cases (no cap → allow; over daily → block; over per-call → block; hard_cap=False → warn; most-restrictive wins; day-boundary aggregation)
  • Persona TOML round-trip preserves budget block (incl. seed empty budget + explicit zero + hard_cap=False + other-fields preserved)
  • REST CRUD + check + charge happy path + 404 unknown agent/persona + 400 malformed body / invalid estimate / invalid charge
  • ruff format --check / ruff check / mypy / pytest (58 passed)
  • UI clean rebuild + tsc --noEmit typecheck
  • Live LXC smoke deferred to V1 (no charge surface yet)

Idempotency

hal0 agent reprovision hermes after the operator PUTs a budget preserves the caps: _phase_persona_seed calls seed_default_personas(overwrite=False) which skips existing files. Only --repair re-writes seeds back to canonical empty.

Refs openrouter-research-2026-05-28/PLANNING.md §3 Phase 0 + §5 Q2.

🤖 Generated with Claude Code

…hase 0)

DA must-fix #3 from the OpenRouter integration analysis: without a
spending-cap envelope, fusion (4.4x cost) + recursing Hermes loops
could drain $200/credit overnight. Land the primitive BEFORE adding
any paid-surface provider (V1 OpenRouter upstream, V2 hal0-fusion MCP).

Adds `[persona.budget]` TOML block + a pure-Python BudgetLedger
(append-only JSONL at /var/lib/hal0/agents/{id}/personas/{pid}/spend.jsonl)
+ a check/charge API + a UI editor.

API:
- GET    /api/agents/{id}/personas/{pid}/budget
- PUT    /api/agents/{id}/personas/{pid}/budget
- POST   /api/agents/{id}/personas/{pid}/budget/check  (dry-run pre-call gate)
- POST   /api/agents/{id}/personas/{pid}/budget/charge (post-response record)

Scope decision (PLANNING.md §5 Q2): per-persona only for v0.3.
Per-agent + platform caps are containing scopes deferred to v0.4.
Race-tolerance: append-only ledger + read-then-check is eventual
consistency; periodic over-spend within one window is acceptable.

No provider charges to this primitive yet. V1 (OpenRouter as Hermes
upstream) will wire it in as a pre-call gate + post-response record.

Refs openrouter-research-2026-05-28/PLANNING.md §3 Phase 0 + §5 Q2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@thinmintdev thinmintdev merged commit 600b56b into main May 29, 2026
5 of 7 checks passed
@thinmintdev thinmintdev mentioned this pull request May 29, 2026
4 tasks
thinmintdev added a commit that referenced this pull request May 29, 2026
End-of-stream cut for v0.3. Bundles MCP-completion, memory-map redesign,
Settings → Updates fix (#386), silent-eviction dispatcher recovery (#392),
ADR-0020 OpenRouter callback skeleton (#409), persona spending-cap
primitive (#411), δ-harness Hermes coverage (#410), and the docs/internal
pin + dashboard-v3 walkthrough (#389/#390).

After this tag, active scope rolls to v0.4 (install-mode reconciliation
+ UI polish + fully-implemented Agents/UI/Install bootstrapped) and v0.5
(MCP admin + memory wiring across UI and agents).

CHANGELOG merged from two coexisting Unreleased blocks into a single
[v0.3.2-alpha.1] section; added missing entries for #392 (dispatcher),
#387 (async-job polling contract), and the docs PRs #389/#390.

pyproject 0.3.1-alpha.1 → 0.3.2-alpha.1. uv.lock resynced (was stuck at
0.3.0a1 from prior drift).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@thinmintdev thinmintdev deleted the feature/phase0-budget branch May 29, 2026 15:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant