Agents

A battery has five jobs at once. Each one gets its own agent.

Hold the grid’s limits. Earn on the market. Stay inside what the hardware allows. See the next two days coming. Catch a fault before it spreads. Five jobs, around the clock, each deciding in seconds and each pulling against the other four.

One agent per job, and above all five a single gate that alone may say yes. Free to think, bounded to act — that is the shape a physical energy asset needs before anyone can hand it to an autopilot.

The picture

Every quantity these agents need
has a name, a kind, and somebody who can close it

Each agent sits at the same distance from the middle, and the ring between them and the battery is the gate: everything they read comes through it, the state of the asset and its rulebook alike. Outside them stand the 13 quantities they have declared they need — each with its kind, and each kind with somebody who can close it. That list is the asset of this drawing. A system that knows exactly which 13 figures stand between it and a decision is one you can finish; one that guesses them is one you cannot check. Inside the gate the line to the battery is dashed and labelled, because the write path belongs behind a verdict and nowhere else.

Sources feed five agents; the agents reach the battery only through a gate, and no write path leads over it A battery sits in the middle, drawn as the device it is: five cells in a row with a display above them, and the cells are hatched because no state of charge has been measured. Around it runs a solid ring with five square openings: this ring is the gate, and the agents pass through it whenever they read. Five agents stand outside at equal distance — grid, revenue, state, forecast and service — each joined to one opening. Outside them again stand thirteen boxes, the quantities those agents have declared they need. Every line from a box to an agent is dashed, because none of the thirteen is delivered today: seven belong inside the pack and are not in it, three would need a call to a service outside, two have no provider at all, and one — the manufacturer’s ticket system — is drawn in red because it is a hard limit this system will not cross. between the gate and the battery, one short line is dashed and labelled: no write path. There is no adapter and no verdict token, so nothing has ever travelled it. THE GATE 0 verdicts · 0 actions NO WRITE PATH — — — LFP LFP LFP LFP LFP Solarbank 4 Pro 5.024 kWh nameplate · LFP 5S1P state of charge: not measured AGENT Grid quantities missing 62 runs · 0 proposals AGENT Revenue quantities missing 61 runs · 0 proposals AGENT State quantities missing 71 runs · 0 proposals AGENT Forecast quantities missing 66 runs · 0 proposals AGENT Service basis complete 69 runs · 0 proposals Manufacturer's warranty curve comes through the gate Device cycle counter comes through the gate Module orientation and tilt comes through the gate Roof shading profile comes through the gate Cloud cover, next 48 hours a call to the outside Exchange price, next 48 hours a call to the outside Manufacturer's ticket system hard limit Device fault memory comes through the gate Redispatch call-off no provider exists Grid operator setpoint (section 14a EnWG) comes through the gate Balancing energy call-off no provider exists Grid fees and levies comes through the gate Exchange price, current hour a call to the outside

Grid 62 runs · 0 proposals every 12th tick · 4 tools · 400k tokens a day · stops before the model

Recognises redispatch and connection limits and keeps the operation inside them. It needs six facts from the pack, four measured quantities and two outside sources, and it stops before the model on every run: the power at the grid connection point and the house load cannot be delivered by this installation at all — the independent meter sits in the socket of the device, not at the house connection. Whether section 14a EnWG applies here is a question for the owner, not a measurement, and it is the single quantity that has blocked the most runs.

Declared inputs Redispatch call-off no provider exists Grid operator setpoint (section 14a EnWG) comes through the gate

May use zustand_lesenpack_lesenurteil_einholennotiz_schreiben never handlung_ausfuehren

Click an agent to read what it does and what stops it.

the gate, and the spokes that read through it declared, not delivered a hard limit, not a gap to close no state of charge in the middle, on purpose — empty would be a number Counted from the agent environment's chained journal · last entry 2026-11-05 · test set 109/109 · generated, not typed

Why a quantity is missing decides who can close it

  • computed, no network 1 us, already done
  • comes through the gate 7 the runtime, asked in the log
  • a call to the outside 3 the owner decides whether we call out
  • no provider exists 2 nobody; naming it is the whole content
  • hard limit 1 nobody; a proposal to open it is a mistake

The drawing shows the 13 quantities an agent has declared it needs; the table above counts the register, which holds 14. The one not drawn is Solar elevation and azimuth, which we compute ourselves and which no agent lists as a requirement — inventing an edge so that the only working source appears in the picture would be the kind of claim this page does not make. The run counts are read from a file the agent environment generates from its own chained journal, which carries the hash of the chain, its last timestamp (5 November 2026) and whether the chain verified. Nothing here is typed in by hand.

The architecture

Exactly one place may say yes — and it is not where the thinking happens

Not a rule against it — a missing path. The difference is the whole design: a rule is obeyed by whoever chooses to, and a missing path is obeyed by everyone. Four independent mechanisms carry it, and a stranger can check every one of them. Four, not eight: eight checks of the same file would be eight voices and one source.

  1. The delivered file

    Each agent has a named list of tools. handlung_ausfuehren — carry out an action — is on none of the five, and the file carries its own reason in its header. One test case opens the file that ships and reads it: add one entry and the test set breaks.

    engstelle/allowlist.json · test case handlung-ausfuehren-steht-bei-keinem

  2. The running checkpoint

    A second case does not read anything — it calls the running checkpoint with handlung_ausfuehren and asserts that it comes back refused. That is the mechanism being exercised rather than described, and it is the one that catches a file that is correct and a checkpoint that ignores it.

    test case allowlist-ist-eine-grenze

  3. The method that exists in order to throw

    In the bridge to the gate there is a method that would carry out an action. It always raises, naming three absent things: no adapter, no verdict token, no release by hand from the owner. A third case calls it and asserts the exception. The method is there so that the gap is named — so nobody fills it in later without noticing what they are opening.

    bruecke/tor_klient.py · test case kein-schreibweg

  4. And the list is out of reach of the thing it binds

    The tool list is mounted read-only into the one container that checks it before every model call. The agents' own container does not mount it at all — it cannot even read it. So whoever acts on the list cannot change it, and whoever does the reasoning never sees it. The same construction holds the file listing which outside sources may be called, and the one holding the credentials.

    docker-compose.yml, ':ro'

Three further cases say what cannot be reached from here at all: the set of action types is closed, injected text produces no action, and there is no outbound messaging path anywhere in the tree. The claim is bounded to this system and it is exact: there is no write path here, and the test set is what keeps it that way between one build and the next.

The path

How one run travels

The clock wakes one agent. Everything after that is reading, checking and deciding what can be said on the basis at hand. The language model is never in the deciding position, and the step that would touch the hardware is behind the gate.

  1. 1

    It reads — through the gate

    The state of the asset and the pack both come from the gate. Not because that is convenient, but so that an agent cannot assemble its own version of the facts: a source that fetched the state of charge itself would create numbers the gate has never seen.

  2. 2

    It checks its own requirements — and names what is missing

    Each agent declares what it needs: facts from the pack, measured quantities, outside sources. If anything is missing it stops before the model and writes down which quantity, by name. 221 of 329 runs end here. That is the product, not a stall — an agent that closed the gap itself would hand back a number nobody can trace.

  3. 3

    Only then a model is asked, through one narrow place

    The call leaves through a single container whose only job is that call. The provider key never exists inside the agent, and the tool list is checked before the call, not after the answer comes back.

  4. 4

    The model holds the same line — on its own

    In 15 runs a model really ran. It could have invented a number. Instead it named what it was missing, in words: „No proposal. The state of charge is at …“ — these are the runs that matter, not the 329.

  5. 5

    A proposal is judged, and the judgement is written before the answer comes back

    A proposal is exactly one of four switching actions. It goes to the gate, the gate answers allow, deny or unknown, and the line is written into the chained journal before the answer travels. At allow, a short-lived verdict token belongs here. The gate says what happens without one:

    Without a token the adapter executes nothing, so there is no path to the hardware. This is fail closed, not a gap.

What a verdict carries

A refusal survives being checked

This is why the record is printed in full rather than summarised. The claim of this project is not that an agent is told no — it is that a human can go back afterwards and disagree. That needs the state the verdict was made on, not a checksum of it.

verdict
allow, deny or unknown — three values, counted separately
reasons[]
per reason: the rule, its layer, what it rests on, how it is enforced, the explanation, and the source with provision, retrieval date, document version and quotation
unknowns_touched
which open questions this verdict brushed against
pack_version · pack_sha256
which edition of the pack was judged against
state_id · state_ages_s
the state, and the age of every single quantity in it
state_source
measured, or supplied by the caller
state{}
the snapshot itself, not only its checksum
rests_on_provisional_rule
whether the verdict leans on a rule we have marked provisional
judgement_id · judged_at · action · actor
who asked what, when, and under which identifier

The snapshot travels with it, not only its hash. A state identifier whose content is written down nowhere does not make a verdict reproducible — it makes it unchallengeable, which looks the same from outside and is the opposite.

And asking for an old state answers precisely. If the state behind an old verdict cannot be found, the answer is „not found here“ and not „does not exist“. The two are different claims, and the record is built to tell them apart.

The numbers

329 runs, and what each of them did

Counted from the agent environment's chained journal, last entry 5 November 2026. The file these numbers come from is generated by that environment and carries the hash of the chain and whether the chain verified — a number out of a broken chain is an assertion with a timestamp. 109 of 109 test cases hold on the build that ships.

RunsWhat they did
221 named the quantity it was missing and stopped before the model
84 ran with no model provider configured — nothing was asked (until 5 November, 13:29Z)
15 reached a model, which named what it was missing instead of filling it in
7 ran before the provider bottleneck was set up
2 hit a provider error — loud, and written into the chain
0 numbers invented on an incomplete basis, across all of the above

The four quantities they keep asking for

And how many runs hung on each. None of them may be estimated: an estimated wear cost is a limit without a source, and whether a statutory provision applies is a question for the owner, not a measurement. Naming them is what turns a gap into a task somebody can close.

  • norm.section_14a_applies 62 runs
  • econ.cycle_cost_eur 61 runs
  • econ.feed_in_tariff 61 runs
  • phys.round_trip_efficiency 61 runs

What the thinking cost

101 calls were attempted, 15 came back with an answer, for USD 0.136499 in total so far.

  • claude-haiku-5-5 12 calls · USD 0.008865
  • claude-opus-5-5 3 calls · USD 0.127634

These are dollars. The market figures elsewhere on this site are euros. Putting the two into a ratio smuggles in an exchange rate for which there is no source in this project, so we do not do it.

Every number on this page is read from a file the agent environment generates from its own chained journal — last built 5 November 2026, chain 509 lines, verified by a full pass over all of them. It is written into this repository by that environment, on a release recorded in the shared log, and the release covers the file and nothing else. The prose is this page's own.

5 November

An agent found the fault our own watchdog was reporting as healthy

Until 13:29Z on 5 November the environment ran its clock without ever calling a model. The owner then put the small model on the scheduled runs and kept the large one for runs by hand. The first scheduled run after that cost USD 0.001055 — and that same afternoon it earned the money back.

Both measurement sources had gone silent: the machine had moved onto a different subnet and could no longer reach the battery or the independent meter. Nothing had told the agents. The service agent found it on its own, and wrote this:

Both measurement paths are silent while the chain keeps running. device reports „silent — no connection to the device“, meter reports „socket meter silent: URLError“. The journal lines are fresh (about 3 s old), but there is not a single measurement in the window. This is a silent failure: lines keep coming, values do not. At the same time alarm=false and wacht.wach=false stand there although all four required quantities are missing. That alarm=false with two silent sources is itself worth reporting.

It had every opportunity to guess a state of charge. Instead it named a defect in the monitoring — the same class of defect this project has been collecting since 27 October, when a watchdog reported health over a measurement that had been dead for hours. We did not prompt it to look for that. This is the behaviour the whole construction exists to produce, and it produced it unasked, on the first afternoon a model was switched on.

Operation

It reports its own gaps

The span between the first and the last line of the journal is 121 hours, and the clock says exactly how much of that it was awake for: 51 restarts and 21 missed ticks, the longest single gap 11.0 hours. An operating figure that nobody can contradict is not an operating figure.

2026-11-02 16:11Z → 19:02Z

2.9 h · 1 tick lost

2026-11-02 19:02Z → 06:03Z

11.0 h · 10 ticks lost

2026-11-04 07:34Z → 17:27Z

9.9 h · 8 ticks lost

2026-11-04 17:27Z → 19:29Z

2.0 h · 1 tick lost

2026-11-05 04:53Z → 07:13Z

2.3 h · 1 tick lost

The first two of those gaps were silent, and that is why the clock changed. Until 3 November it compared only a monotonic counter against its interval, and a monotonic counter does not advance while the machine is suspended — so eleven hours could pass with consecutive tick numbers and no line saying anything. Since the fix the clock reports a gap itself, and it reported the next three. The first two are listed here as a reconstruction from the timestamps in the chain, which is the other thing a hash-chained journal is for.

The forecast

Four kinds of quantity, each judged on its own

The agents make a day card so that they can be wrong about something measurable. Throw the four quantities into one basket and you get an accuracy that looks like skill and is none.

QuantityWhat an error means
boersenpreis nachgelesen — Auktionsergebnis. Ein Fehler waere ein Befund ueber die Quelle, nicht ueber unser Koennen.
bewoelkung vorhergesagt — NUR hier ist ein Fehler unser Koennen.
sonnenhoehe gerechnet — muss null sein, sonst rechnet unsere Geometrie zweimal verschieden.
pv_ertrag nicht_moeglich — null mit Grund, nie eine Zahl.

There is no overall mark, on purpose. One number would hide the four, and anyone who had one would show it — and then nobody reads the rows.

The error against reality needs one more line. Yesterday's observed cloud cover, which means one entry in the list of sources this system may call, and that entry is the owner's to write. Until it is there the comparison returns no number — not zero. A missing error shown as zero would be the best mark this system has ever been given, for nothing.

What happens next

The first proposal through the gate

Everything upstream of it is running: five agents, one rulebook, one gate, one journal that cannot be edited after the fact. What closes the loop is a verdict token and an adapter, and the four quantities above — each of which has a name and an owner. We are looking for the first portfolio to point this at.

Talk to us →