docs(readme): the offline walkthrough now reaches portfolio mode and the report
Steps 6 and 7, each run verbatim from a fresh clone before being written. Step 6 is the portfolio pass, which the walkthrough could not reach until the scripted door was wired into portfolio mode. It is also the clearest single demonstration the framework has: all four reference projects carry a cost line 01.1 at four different amounts, so one unchanged proposal yields one ValidatedProposal and three Rejection -- the gate is anchored to each project's own baseline, not to the proposal's internal arithmetic. The shared verdict id is explained rather than hidden: a verdict is keyed on the candidate, not the project, and that key is how a later run finds the earlier judgement. Step 7 documents the value report and, more importantly, the gap a downloader hits first: nothing in the shipped code writes a savings ledger. Measured, not assumed -- no .save call on a ledger exists outside the library API. That is by design and is now said out loud: the ledger records savings actually realized in the world, which is not a conclusion the system may draw from its own proposals. A validated proposal is a claim; a ledger entry is a result. The refusal a downloader sees before creating one is quoted, and the library snippet that creates one is shown. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0118noV9rCfrdREH26XqZB5z
This commit is contained in:
parent
415ebbb7f2
commit
8d258e9a3b
1 changed files with 69 additions and 1 deletions
70
README.md
70
README.md
|
|
@ -37,7 +37,7 @@ above is the verification.
|
|||
|
||||
## Walk the whole chain offline
|
||||
|
||||
Five commands, no API key, no network, no cost. They exercise the real loop — context navigation
|
||||
Seven commands, no API key, no network, no cost. They exercise the real loop — context navigation
|
||||
over the knowledge base, the maker/checker debate, the deterministic validator, the verdict — with
|
||||
**scripted stand-ins for the agents' answers**. Every scripted invocation prints a banner saying so,
|
||||
because a scripted run that reads like a model run would be worse than having no offline mode at
|
||||
|
|
@ -101,6 +101,74 @@ uv run python -m portfolio_optimiser.costsim --projects 4 --profile local
|
|||
Modelled upper bounds per role and model, with the source of each price quoted. `--profile local`
|
||||
prices the free local backend; the estimate is a ceiling, not a bill.
|
||||
|
||||
**6 — Run the whole portfolio, and watch the gate anchor to each project separately.** The same
|
||||
flag works across every bundled reference project at once:
|
||||
|
||||
```bash
|
||||
cat > replies.json <<'JSON'
|
||||
{
|
||||
"proposer": "{\"measure\":\"scope_reduction\",\"affected_items\":[{\"code\":\"01.1\",\"quantity\":1,\"unit_cost\":850000}],\"claimed_saving_nok\":40000}",
|
||||
"checker": "Rigging and site operations can absorb this reduction. VERDICT: APPROVE"
|
||||
}
|
||||
JSON
|
||||
|
||||
uv run python -m portfolio_optimiser.run --portfolio --scripted-replies replies.json
|
||||
```
|
||||
|
||||
One `ValidatedProposal`, three `Rejection`. All four reference projects carry a cost line `01.1`,
|
||||
but at four different amounts — so a claim stated against one project's estimate is refused for the
|
||||
other three. Nothing about the proposal changed between them; what changed is the project's own
|
||||
numbers, which is the whole point of anchoring the gate to a cost baseline rather than to the
|
||||
proposal's internal arithmetic.
|
||||
|
||||
The four lines quote the same `verdict id=`. That is not a bug: a verdict is keyed on the
|
||||
*candidate* it judges, not on the project it was judged in, so an identical proposal mints an
|
||||
identical id by design — that key is how a later run finds the earlier judgement.
|
||||
|
||||
A portfolio pass reports what happened to every project. Projects that raised are printed to
|
||||
stderr with their error, and the command exits non-zero; the projects that completed still print
|
||||
their outcome, because one dead project must not discard the rest of the pass. A pass stopped by
|
||||
the global token cap says so, separately from a pass stopped because a savings goal was reached —
|
||||
running out of budget and hitting your target are not the same event.
|
||||
|
||||
**7 — Report what has actually been realized:**
|
||||
|
||||
```bash
|
||||
uv run python -m portfolio_optimiser.run --report --ledger savings-ledger.json
|
||||
```
|
||||
|
||||
This reads a savings ledger and prints per-project and portfolio totals with each entry's
|
||||
provenance. It makes no model calls and changes nothing.
|
||||
|
||||
**The ledger is an input, and the framework will not write it for you.** It records savings that
|
||||
were *actually realized* — a contract was changed, an invoice came in lower — which is a fact about
|
||||
the world, not a conclusion the system is entitled to draw from its own proposals. A validated
|
||||
proposal is a claim; a ledger entry is a result. Keeping them apart is deliberate, and it is why no
|
||||
command here produces a ledger as a side effect. Run `--report` before creating one and it says so
|
||||
plainly (`run report refused: savings ledger not found`).
|
||||
|
||||
You write entries when the saving materializes:
|
||||
|
||||
```python
|
||||
from portfolio_optimiser.ledger import LedgerEntry, SavingsLedger, to_ore
|
||||
|
||||
ledger = SavingsLedger()
|
||||
ledger.add_realized(
|
||||
LedgerEntry(
|
||||
project_id="FV42-GSV-E1",
|
||||
dimension="rigg",
|
||||
candidate_identity="33fba649cade8529",
|
||||
amount_ore=to_ore(40000),
|
||||
verdict_id="33fba649cade8529",
|
||||
provenance="expert Kari Nordmann, 2026-08-05, realized via contract amendment",
|
||||
)
|
||||
)
|
||||
ledger.save("savings-ledger.json")
|
||||
```
|
||||
|
||||
Amounts are held in øre as integers, and `to_ore` is the only conversion — money is quantized once,
|
||||
per amount, before anything is summed.
|
||||
|
||||
> `--live-dry-run` is a different, narrower drill: it builds contracts, clients and budget against
|
||||
> your own configuration and **stops before the first model call**. It verifies the setup; it does
|
||||
> not run the loop. `--scripted-replies` runs the whole loop. The two are mutually exclusive and
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue