Files

199 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Budget Manager v1
This milestone implements deterministic accounting for the initial hypotheses.
It does not run investigation tools, invoke an LLM for evidence assessment, build
diagnoses, or start an investigation loop. `docs/architecture.md` remains the
authoritative specification, particularly sections 16, 18, 19, 25 and 31.
## Configuration and deployment tuning
Copy `config/budget.example.toml` to the ignored `config/budget.local.toml` and
load it with `BudgetConfig.load(path)`. This is a separate config from the initial
hypothesis allocations in `config/analysis.example.toml`. These are conservative
starting values, not format constants or empirically calibrated scoring rules:
| Setting | Default | Meaning |
| --- | --- | --- |
| `default_action_cost` | 10 | Positive integer cost for an action without an override |
| `action_costs` | empty in Python | Per-action positive integer costs; example TOML includes future tool names |
| `max_reward_bps` | 8000 | Maximum refund in basis points (8000 = 80%); range 0–9999 |
| `new_evidence_score` | 100 | Deterministic score for unseen, nonempty evidence; range 0–100 |
| `low_information_threshold` | 20 | Information scores at or below this value count as low gain |
| `max_low_information_steps` | 3 | Consecutive low-gain settlements before abandonment |
The example config assigns `source.read_context` a cost of 5, `source.find_text`
and `database.lookup` 10, and `research.lookup` 20. These names describe accounting
overrides only: they neither implement tools nor authorize arbitrary operations.
The future Tool Router must independently validate every action.
The complete effective policy is frozen in the first budget event of an analysis
revision. Changing the config cannot alter an existing policy, refill balances,
undo expenses, or reopen terminal accounts. An existing snapshot or identical
operation can be replayed with a different config; new operations reject with
`BUDGET_POLICY_CHANGED`. Use the frozen config to continue an existing revision;
new revisions can adopt the tuned config. Every event includes the effective
policy, so historical accounting remains reproducible after deployments change.
Tune costs, refund ceiling, novelty score, threshold and consecutive-step limit
using actual result quality and cost data. The initial hypothesis allocation
remains separately configurable. No automatic online adaptation is implemented.
The invariants below deliberately cannot be disabled by configuration.
## Calculation and invariants
The unit is `investigation_units`, unrelated to LLM token counts, money or
provider retry counts. The LLM can supply only a bounded semantic score and a
reason through the future trusted controller. It cannot choose actual costs,
refunds, remaining balances, or budget policy.
For each completed action:
```text
information = min(deterministic_novelty, semantic_score)
reward = floor(cost * max_reward_bps * information / 1_000_000)
reward = min(reward, cost - 1)
remaining = initial - total_spent + total_rewarded
```
Both scores range from 0 to 100. Full configured reimbursement requires both
scores to reach 100. A score is an ordinal accounting input, not a confidence
percentage. For example, cost 10 and both scores 100 produce reward 8 under the
default policy; a semantic score of 50 produces reward 4. Even the most generous
allowed policy always has `0 <= reward < cost`: each settled action consumes at
least one whole unit. Thus a hypothesis cannot fund infinite investigation.
Novelty v1 hashes canonical JSON evidence (sorted object keys, preserved array
order). New nonempty evidence gets `new_evidence_score`; empty or already seen
evidence gets zero. Deduplication spans hypotheses and prior analysis revisions
in the same case package. Fresh revision budgets do not make old evidence novel.
Transport metadata and query IDs should be kept outside the evidence payload.
This detects exact normalized content reuse, not semantic equivalence. Future
EvidenceStore/scoring work may provide richer deterministic novelty signals;
this milestone does not pretend to assess factual correctness or entailment.
The controller supplies JSON evidence and safe references to existing package
sources. Source hashes, evidence, computed novelty, semantic assessment reason,
cost and reward are all retained. Accounting checks references and integrity, not
whether an assessment is scientifically justified. Tests simulate assessments;
no live model or paid provider is used.
## Durable controller API
```python
from viano_bug_analysis.budget import BudgetConfig, BudgetManager
budget = BudgetManager(store, BudgetConfig.load("config/budget.local.toml"))
budget.initialize(case_id, package_id)
budget.reserve(case_id, package_id, "H1", "query-0001", "source.read_context")
# A future controller invokes the allowed read-only tool after reservation.
budget.record_result(
case_id, package_id, "H1", "query-0001",
{"finding": "Example stored observation"}, source_refs=[source_ref],
)
# In the next iteration the controller validates a bounded semantic assessment.
result = budget.settle(
case_id, package_id, "H1", "query-0001",
semantic_score=50, reason="Example assessment of this stored result",
)
```
- `initialize` verifies a completed current-revision hypothesis batch, registry,
allocations, context and original successful LLM response. It creates equal
balances from the controller-owned allocations; it cannot allocate more IDs.
- `reserve` durably debits the configured cost before external work. Unknown
hypotheses, blank IDs, unaffordable costs and terminal accounts are rejected.
Only one unsettled action per hypothesis is allowed. Different hypotheses can
have their own pending actions; action IDs are scoped to revision/hypothesis.
- `record_result` persists evidence, source references and their hashes. It
changes the action to `awaiting_assessment`, with no immediate reimbursement.
- `settle` accepts only an integer score 0–100 and a nonblank reason. The score is
bound to the already stored action result, and the manager computes the reward.
- `fail` closes a reserved action without reimbursement. Failed or uncertain
executions still cost budget and count as low information. A pending action
survives restart and blocks another reservation; the manager never silently
cancels it or retries external work. The controller decides whether the same
external operation can safely resume or must be closed with `fail`.
- `reject` marks an account terminal without refunding or deleting its history;
a pending action must first be settled or failed.
- `snapshot` validates/replays the ledger without changing budgets.
Identical repeated operations return the current package/state with `reused=True`.
Changed inputs under the same operation/hypothesis/action identity are rejected.
Repeated initialization cannot refill balances, including after index-write
failure, service restart, transport under another filename or config changes.
`BudgetResult` returns the package path, revision, event reference and current
state. Its `investigable` property lists nonterminal accounts without pending
actions; `ready_for_evaluation` is true only when all accounts are terminal and
no action awaits completion. An empty investigable list alone is not sufficient
while asynchronous actions are pending.
## Status and lifecycle boundaries
Budget account status is separate from qualitative hypothesis confidence and
the immutable hypothesis birth/creation registry. The manager owns `new`,
`investigating`, `budget_exhausted`, `abandoned_low_information_gain` and the
controller-requested `rejected` budget eligibility marker. It does not infer
`supported`/`plausible`, diagnose causes, create replacements or restore slots.
The birth document's `budget_accounting = not_started` describes generation
time and remains immutable; `artifacts.budget_ledger` is authoritative for the
subsequent accounting state.
The cheapest cost in the configured override/default table is the affordability
floor. Immediately after initialization and after settlement, a balance below
that floor becomes `budget_exhausted`. Pending actions are not marked exhausted
before their possible reward is evaluated. If affordable but the consecutive
low-gain limit is reached, the account becomes `abandoned_low_information_gain`.
A higher information score resets that counter. Exhaustion takes precedence if
both conditions occur. Request-specific insufficient funds reject that request;
a cheaper permitted action can still be considered.
Mutations are allowed only in `GENERATING_HYPOTHESES` or
`INVESTIGATING_HYPOTHESES`. Budget initialization does not start investigation
or transition the case. An exhausted set exposes eligibility for final
evaluation, but does not create a diagnosis or route the case to Human Review.
Those decisions belong to the future controller/evaluator. Reopening retains
old artifacts and starts accounting from the new revision's accepted allocations.
## Persistence, validation and limits
Each operation appends one immutable event/snapshot:
```text
analysis/budgets/0001/events/00000001.json # policy + initial balances
analysis/budgets/0001/events/00000002.json # reservation
analysis/budgets/0001/events/00000003.json # result
analysis/budgets/0001/events/00000004.json # assessment + computed reimbursement
```
The manifest's `artifacts.budget_ledger` points to the latest event. Each event
has format/version, identities, original hypothesis/registry hashes, sequence,
parent/hash, effective policy, request and fully derived accounting state. The
existing CheckpointStore publishes event, checkpoint and pointer atomically,
increases `package_generation`, preserves unknown files and checks the prepared
generation. A per-case budget lock prevents duplicate dispatch; short existing
case/worker locks protect publication. No lock waits on an external tool or LLM.
Replay verifies event schemas, checkpoint bindings, immutable origins, lineage,
source hashes and every computed state. Pointer deletion, changed allocations,
costs, rewards, balances or broken lineage are controlled errors, never a reset.
Checks detect inconsistent artifacts; hashes are not digital signatures and do
not authenticate a maliciously rewritten complete case history.
`budget-policy-v1`, `budget-request-v1` and `budget-event-v1` JSON schemas are
bundled with the Python package. Byte/event limits in `BudgetLimits` are adjustable
operational safeguards, independent of format v1: defaults are 2 MiB per event,
1 MiB per evidence payload and 10,000 events across retained budget revisions.
Limits reject explicitly rather than truncating history or dropping expenses.
Snapshots contain cumulative state, so long-lived cases may need larger limits;
incremental replay optimizations are a later performance task.
The CaseAssembler excludes checkpoint-declared budget events from raw case input
identity, preserving replay of earlier phases. Undeclared/unknown files remain
preserved and part of raw input inventory as before.
For asynchronous external testing, see [async-tests-v1.md](async-tests-v1.md).
`BLOCKED` pauses the case, not accounting history: balances and pending
reservations survive unchanged. Confirmed feedback resumes the same revision;
only subsequent result assessment changes its budget. Human/automated tests
have separately configurable explicit costs, with no idle-time charges.