> ## Documentation Index
> Fetch the complete documentation index at: https://docs.energy.nlead.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# Agentic Process Support (Design)

> Moving the bridge's process support from hard-wired loops and flags to configurable process agents with cases, an approval inbox, numbered rules and measured outcomes - a design, not approved and not implemented

> [!WARNING]
>
> **Design only.** Nothing on this page is implemented, and none of it
> starts without explicit approval - PR by PR, in the order at the end.
> Today the process support is the orchestrator's fixed sequence of loops
> ([Pipeline](/setup/pipeline.md)), switched by `auto_*` flags, with one LLM
> classifier on the Erstkontakt mailbox.

The examples this design follows come from the exnatonCONFERENCE 2026
(Zurich, 21 October 2026): Exnaton's agent harness and its Exception
Handler, live on one Austrian tenant, and the product ideas drawn from it
("Market Exception Handler", "Market Process Skills"). What Exnaton showed
is the shape; what the bridge already has is the material. The design
keeps everything deterministic that must stay deterministic - schedules,
EDIFACT, deadlines - and makes agentic exactly the part that is agentic in
people's heads today: noticing an exception, deciding what to do about it,
doing it, and learning from the answer.

## What the conference proved, and what it means here

> **An agent is a model plus a harness**
>
> Everyone buys the model from the same providers; the harness is the
> difference. Exnaton's harness has three parts: **Access** (which
> systems the agent may read and write, with the rights of the people,
> often fewer), **Skills and memory** (codified expertise: runbooks,
> market processes, case notes), **Control** (approvals, guardrails,
> logs; every change is a case a human approves; every rejection is
> learned from). The bridge has all three as material already: the
> process API with its scopes, the docs and the regulatory KB, the audit
> trail and the event catalog. It has none of them as a *harness*.

> **The Exception Handler pattern works, measured**
>
> One Austrian tenant: 42,426 accounts, billability 96.7 % to 99.79 %
> in five days, 1,313 accounts recovered, every case an EDA process
> (ECON, ANFORDERUNG\_PT, code 94). The loop is: validation failed →
> reprocess; transmission faulty → request again; not sent → request
> again; resend rejected → check master data or insist; after every
> action the agent re-reads the status; what is left, the agent writes
> to the grid operator. That loop is, process for process, the bridge's
> PARTIN/ZOE/APERAK/MSCONS exchange with Netzbetreiber - today spread
> over `resend_rejected`, `nachversand`, `korrektur_mails`,
> `quittungs_nachfass`, `stammdaten_anfragen`, `auskunft_antworten` and
> `aktivierung_nachfass`, each its own loop with its own flag.

> **Four product decisions worth copying**
>
> 1. **Autonomy per agent in three steps:** *propose only*, *act with
>    approval* (every write waits for an operator; reads and proposals
>    do not), *act*. Dry run for the first week.
> 2. **Rules as numbered, citable data, not prose.** A shared policy
>    (P1 to P30) applies to every agent; an agent's own rules continue
>    the numbering; a run cites rules by number ("per policy P13").
> 3. **An inbox, not a log.** Tabs: *To approve*, *For a person*,
>    *Improvements*, *Failed*, *All open*, *Done*. Each case carries a
>    risk score, a category (`ESCALATE_INTERNALLY`,
>    `NEEDS_INVESTIGATION`, `SWEEP`), the actions, the age, and an
>    *Instruct the agent* field in plain language that becomes policy.
> 4. **A fixed outcome taxonomy** so impact can be measured: *fixed by
>    the agent*, *fixed by a human with the agent*, *resolved by the
>    counterpart after an agent-initiated message*, *no action needed*,
>    *dismissed*. Plus *stale* (open for more than 7 days).

> **Three things to do better**
>
> 1. **Event triggers.** Exnaton's harness runs on cron only; "On an
>    event" is greyed out. A market-side agent lives on events and
>    deadlines: an APERAK is due 45 minutes after a UTILMD, the
>    day-ahead gate closes at 14:30, MaBiS has its own calendar. The
>    bridge already computes all of these; the agents must be triggered
>    by them.
> 2. **Enumerate the checks, run all of them, every time.** Two Wattson
>    runs on the same bill flagged different discrepancies; both valid,
>    which checks ran varied per invocation. The bridge's rule: checks
>    are deterministic code, run in full, and the model writes the
>    explanation and plans the detour - never the other way round.
> 3. **A step cap is a round, not a failure.** 13 of 108 runs failed,
>    `WorkerStepLimitExceeded` on stage. Here a cap ends a round with a
>    case ("not finished: n of m partners checked"), never silently.

Inference cost is irrelevant to the design: a run over 4,883 accounts cost
2.3M tokens and \$1.32. What matters is data sovereignty (raised in almost
every one of Exnaton's 20 utility interviews) and the regulatory clock.

## Where the bridge stands

| | Today | Gap |
| - | - | - |
| **Loops** | \~20 routines hard-wired in `BridgeService.orchestrate()` in a fixed order (forecast generation, backup CSV, PARTIN answers, APERAK, auto-ZOE, mailbox, day steps, gate watchdog, reBAP, confirmations, contract check, Slack log) | Order, cadence and triggers are code; a new routine is a code change |
| **Policy** | 25 `auto_*` and threshold settings (`auto_aperak`, `auto_zoe`, `auto_nachversand`, `classifier_min_konfidenz`, `quittung_nachfass_karenz_stunden`, …) | Each flag is one bit of autonomy for one loop; no shared rules, nothing a run can cite |
| **Exceptions** | 111 cataloged event types, alert rules in `notify.py`, Wiedervorlagen, Nachversand-Vormerkungen, Zurückstellungen, Ausnahmen - each its own ledger | An exception is a warning line and a ledger entry, not a case with evidence, proposals, owner, outcome |
| **LLM** | `ClaudeReplyClassifier` on Erstkontakt replies: nine-way verdict with confidence and quote, regex fallback, verdict cache, `classifier_disagreement` events | One classifier for one mailbox; no tool use, no planning, no memory |
| **Previews** | `dry_run` on `acknowledge_received`, `answer_partins`, `resend_rejected`, `auto_zuordnung`, `erstkontakt_mailbox`, `bestellungen/preview`, `partin/preview` | Not every write has a preview; previews are not kept as proposals |
| **Approval** | The console (process key) and the operator-app design: preview → confirm → execute, four eyes, device keys ([App actions](/setup/mobile-actions.md)) | Designed for a phone, not yet the general approval path |
| **Knowledge** | 28 setup pages, the regulatory KB (31 scopes, DE with MaBiS/GPKE/WiM/MPES) over MCP | Not loadable as agent skills, not versioned against source documents |
| **Measurement** | Marktrollen (*aktiv / erwartet*), Prognosen, Fahrplan/Ist, the Slack interaction log | No outcome per exception, no approval rate, no time-to-resolve |

In Exnaton's words, the bridge is "traditional software that knows set
paths": step 3 fails, an event is emitted, a human reads the console. The
design below is the detour.

## Design rules

Numbered, so the rest of this page and later PRs can cite them.

| # | Rule |
| - | - |
| **D1** | **Checks are code, explanations are the model's.** Every check an agent can run is an enumerated, deterministic function with a name; a run executes all checks of its skill, every time. The model reads their results, writes the explanation, ranks and plans. It never decides whether a check applies. |
| **D2** | **Every write is a case.** Nothing reaches a market partner, the trader, Engrate or the customer that is not an *action* of a *case* with a *preview*. Reads and proposals never wait; writes wait according to the agent's autonomy and the action's policy. |
| **D3** | **The bridge makes the plan, the model proposes it.** An action's targets, message content and references come from the existing service function's dry run (the plan, with its hash), as in the operator-app design. The model chooses *which* function with *which* parameters and says *why*; it never composes an EDIFACT, a CSV or a schedule. Mail text drafted by the model is part of the preview, word for word. |
| **D4** | **Rules are numbered data.** A shared policy (P1…) binds every agent; each agent's rules continue the numbering; a case cites the rules it acted on. An operator instruction on a case becomes a proposed rule, adopted by a human. |
| **D5** | **Triggers are events, deadlines and schedules, in that order of importance.** An agent declares what wakes it: event types from the catalog, deadlines from the domain (`mabis_fristen`, gate closure, APERAK clocks), and a schedule as the fallback. No agent polls. |
| **D6** | **Autonomy is per agent, policy per action.** Three levels per agent: `propose`, `act_with_approval`, `act`. Independently, each action type carries its policy: one person, four eyes, or never from an agent. Four eyes is the operator-app mechanism, unchanged. |
| **D7** | **Fixed outcome taxonomy.** Every case closes as exactly one of: `fixed_by_agent`, `fixed_by_human_with_agent`, `resolved_by_counterpart`, `no_action_needed`, `dismissed`. A case open for more than 7 days is *stale*. |
| **D8** | **No new access.** An agent's tools are the process-scope service functions the console already calls, with the same scopes, rate limits and audit. The agent reads what `/v1/process` and `/v1/console` return; it writes through previews only. |
| **D9** | **Everything on the audit share.** Agent definitions, rules, cases, runs and the proposals' plans are JSON ledgers next to today's; the event catalog remains the contract; the Slack log carries agent actions like any other interaction. A restart loses nothing. |
| **D10** | **Data minimisation towards the model.** A run gets the facts a check produced (references, codes, verdicts, counts, the partner's text where a reply must be understood), not the audit share. No MaLo or MeLo, no customer data, unless a skill declares it and the policy allows it. Model operation and data processing agreements are configuration, not code. |
| **D11** | **The deterministic pipeline stays deterministic.** Aggregate, publish, submit, APERAK generation, ZOE and ORDERS content, CONTRL: unchanged, never behind a model. Agents sit beside the pipeline and handle its exceptions. |
| **D12** | **A round ends with a case, never silently.** A step or action cap, a tool failure, a model refusal: the run closes its round with what it checked and what is left, as a case in *Failed* or *For a person*. |

## Target picture

```mermaid theme={null}
flowchart TB
    subgraph trig ["Triggers"]
        EV[event log<br/>111 cataloged types]
        DL[deadlines<br/>APERAK 45 min · gate 14:30 · MaBiS]
        CR[schedules<br/>cron, Europe/Berlin]
        MAN[manual · Run now]
    end
    subgraph agents ["Process agents (configurable)"]
        DEF[(agent definitions<br/>purpose · skills · rules · autonomy · triggers)]
        RUN[run loop<br/>checks → read → plan → propose → act → re-read]
    end
    subgraph skills ["Skills"]
        CHK[checks<br/>deterministic, enumerated]
        TOOLS[tools<br/>service functions · dry run = preview]
        KB[knowledge<br/>docs · regulatory KB per scope]
    end
    subgraph control ["Control"]
        CASES[(cases ledger<br/>evidence · proposals · rules cited · outcome)]
        INBOX[inbox<br/>console · operator app]
        APPR[preview → confirm → execute<br/>one person · four eyes]
    end
    subgraph existing ["Existing, unchanged"]
        PIPE[day-ahead pipeline<br/>aggregate · publish · submit]
        SVC[service functions<br/>/v1/process]
        AUD[(audit share · events.jsonl)]
        MKT[partners · trader · Engrate · customer]
    end
    EV & DL & CR & MAN --> RUN
    DEF --> RUN
    RUN --> CHK & TOOLS & KB
    TOOLS --> SVC
    RUN --> CASES --> INBOX --> APPR --> SVC --> MKT
    SVC --> AUD --> EV
    PIPE --> AUD
```

### The module: `agents/`

A new package beside `services/`, with the same clean split as the rest of
the bridge: pure definitions and the run loop in the package, I/O behind
adapters with fakes.

| Part | What it is | Where it lives |
| - | - | - |
| **Agent definition** | Purpose (name, one line, goal sentence handed to the model as standing context), skills, rules, autonomy, triggers, behaviour (dry run, memory, caps) | `agents/<slug>.json` on the audit share, edited in the console, versioned (every save a new version with author and time; a run records the version it ran on) |
| **Shared policy** | P1…Pn, one rule per line, Markdown; cannot be changed per agent | `agents/policy.md`, versioned the same way |
| **Skill** | A named bundle of *checks* (deterministic functions returning findings), *tools* (service functions with a parameter schema, a `preview` and an `execute`, and the action policy), and *knowledge* (pages the model may read). Skills are code in the bridge; which ones an agent carries is configuration | `agents/skills/*.py`; the catalog test that guards the events catalog guards the skill registry too |
| **Trigger bus** | Subscribes agents to event types, deadline windows and cron; dedups per (agent, trigger key); enforces one run per agent at a time; records every wake and why | `agents/triggers.py`, fed by the event log and the orchestrator tick |
| **Runner** | The model loop: system context (purpose, rules, skill descriptions), the findings of the checks, tool calls as structured output, step and action caps, the run log | `adapters/agent_runner.py` behind a `Protocol`, with the `ClaudeAgentRunner` (today's `ClaudeReplyClassifier` generalised: structured outputs, tool use, model per agent) and an `InMemoryAgentRunner` for tests - no network in the suite, as today |
| **Cases** | The ledger of exceptions and proposals (below) | `cases.json` on the audit share, plus `runs/<run id>.jsonl` with every tool call and result |
| **Inbox** | Console tab and operator-app views over the cases; approve, reject, instruct, assign | `/v1/process/cases*` (process scope), `/v1/mobile/cases` (read), actions through the operator-app preview/execute path |
| **Analytics** | Outcomes per agent and per category, approval rate, median time to resolution, stale cases; domain indicators per agent | `/v1/process/agents/analytics`, console tab |

### The run loop

```mermaid theme={null}
sequenceDiagram
    participant T as Trigger bus
    participant R as Runner
    participant S as Skills (checks, tools)
    participant C as Cases
    participant O as Operator
    T->>R: wake(agent, trigger, context)
    R->>S: run every check of the agent's skills
    S-->>R: findings (deterministic, with references)
    R->>R: model: explain, group, rank, plan (cites rules)
    loop per finding
        R->>S: tool.preview(parameters)  -- dry run, nothing sent
        S-->>R: plan, plan hash, targets
        R->>C: open or update case with proposal
        alt autonomy = act and policy = one person
            R->>S: tool.execute(preview id)
            R->>S: re-read status
        else
            C-->>O: inbox: to approve / for a person
        end
    end
    R->>C: close the round (what was checked, what is left)
```

Only proposals leave the loop; an executed action is a proposal whose
approval was the agent's own autonomy, recorded as such. After every
action the agent re-reads the status it acted on (Exnaton's loop), and a
finding that persists past the re-read is a case for a person, not a
retry.

### Cases

A case is the unit of work an operator sees. Today's ledgers
(Wiedervorlagen, Nachversand-Vormerkungen, Zurückstellungen, Ausnahmen)
stay as they are; a case references them and the agent updates them
through tools.

| Field | Content |
| - | - |
| `id`, `agent`, `opened_at`, `status` | `open`, `to_approve`, `for_person`, `failed`, `done` |
| `category` | From the skill, fixed per finding type: `ESCALATE_INTERNALLY`, `NEEDS_INVESTIGATION`, `SWEEP`, `DEADLINE`, `COUNTERPART_SILENT`, `DATA_MISSING`, … |
| `risk` | `low`, `medium`, `high` with a score; from the skill's check, not the model. A deadline inside the warning window is always `high` |
| `subject` | Partner (MP-ID), Regelzone, delivery day, message reference - whatever the finding is about |
| `evidence` | References into what the bridge already holds: event ids, EDIFACT references, mail ids, confirmation reports - never copies |
| `explanation` | The model's text, with the rules it cites by number |
| `proposals[]` | Each: tool, parameters, preview id, plan hash, policy (one person / four eyes / never), state (`proposed`, `approved`, `executed`, `rejected`, `expired`), who, when |
| `timeline[]` | Status changes, operator instructions, agent rounds, counterpart answers |
| `outcome` | One of D7's five, set on close; `stale` is derived |

Three categories of the inbox map directly onto what the bridge
distinguishes already: *To approve* (a proposal waits for a person),
*For a person* (the agent found something it has no tool for: a partner's
question, a contract to sign, a decision), *Improvements* (the agent
proposes a rule or a correction to master data or a contact address -
today's `kontakt_adresse_setzen`, `rahmenvertrag_setzen`).

### Rules

```markdown theme={null}
# Shared policy (agents/policy.md)
P1  Never send a market message whose content you composed. Only tool previews are sent.
P2  Never act inside the gate window (warn_from to closure) on the day ahead.
P3  A deadline-bound action (APERAK, CONTRL) may be executed under autonomy "act"
    when its policy is "one person" and the preview shows no partner you were told to defer.
P4  A partner with a Zurückstellung is not written to until it is lifted.
P5  One mail per partner per case per day.
...
# Agent rules (agents/mako-exception-handler.json, numbering continues at P31)
P31 A PARTIN rejection with CONTRL error 23 whose partner acknowledged a later file of ours is resolved; close as resolved_by_counterpart.
P32 A code Z29 on PARTIN is answered by the PARTIN check itself; do not resend.
```

Rules P31 and P32 are today's `standing_rejections` logic, written down
where a run can cite it. An operator instruction on a case ("Add to
policy: in case of a code 94 rejection of ANFORDERUNG\_PT, verify an active
membership first") lands as a *proposed rule* in *Improvements*; adopting
it is an edit to the agent's rules by a person with the process key.
"Self-learning" in Exnaton's sense (the agent appends to its own memory)
is off by design: memory here is the cases ledger and the rules, both
human-owned.

### Triggers

| Kind | Source | Example |
| - | - | - |
| **Event** | The event log, by type, with a filter on `data` | `aperak_needs_review`; `schedules_submitted` with `accepted: false`; `fahrplan_vom_uenb_geaendert`; `marktdaten_unbekannt` |
| **Deadline** | The domain's clocks, as a window before the moment | APERAK due in 20 minutes with the inbox not yet acknowledged; gate closure minus `gate_closure_warn_minutes` with the day not final; a MaBiS activation deadline (`mabis_fristen`) past without activation |
| **Schedule** | Cron with a timezone | Daily 07:00 Europe/Berlin: the partner sweep; hourly: the confirmations |
| **Manual** | *Run now* in the console or the app, with parameters | A sweep for one Regelzone |

The trigger bus is the orchestrator tick's successor: `orchestrate()`
keeps the heartbeat and the hash-driven day steps (D11) and otherwise
becomes "fire what is due". Each routine it hard-wires today becomes a
registered agent run with its trigger, in the same order at first, so
PR 2 changes no behaviour.

### Autonomy and action policy

| Autonomy | Reads | Proposals | Writes |
| - | - | - | - |
| `propose` | yes | yes | never; everything to the inbox |
| `act_with_approval` | yes | yes | after one person approves (or two, per action) |
| `act` | yes | yes | immediately for actions with policy *one person*; four-eyes actions still wait |

Action policy is server-side configuration per tool, as in the
operator-app design, and an agent cannot raise it:

| Action (tool) | Wraps | Policy (proposal) |
| - | - | - |
| Acknowledge received messages | `acknowledge_received` | one person (deadline-bound) |
| Resend rejected PARTINs | `resend_rejected` | four eyes |
| Nachversand after a registration problem | `nachversand` | four eyes |
| Korrektur mail, Quittungs-Nachfass, Stammdaten-Anfrage, Aktivierungs-Nachfass, Auskunft | the mail functions | one person |
| Auto-Zuordnung, default flags | `auto_zuordnung` | four eyes |
| Set or lift a Zurückstellung, Ausnahme, Nachversand-Vormerkung, contact address | the ledger setters | one person |
| Record a contract state | `vertrag_setzen` | one person |
| Protokoll-Notiz | `notiz_erfassen` | one person |
| Stop the service | `set_running(False)` | one person, refused in the gate window |
| Orchestrate, manual pipeline steps, `PUT /v1/console/config`, mailbox moves, single or custom Zuordnungen, PARTIN fan-out, Bestellungen, imports, external writes | - | **never from an agent** |

The last row is the operator-app design's "never on the phone" list, kept
verbatim: an agent is an operator with fewer rights, not more.

## The first agents

Each maps onto loops and flags that exist. The flags stay as the
migration's compatibility layer and disappear with PR 2.

**1 · MaKo Exception Handler**

**Purpose:** keep every Netzbetreiber, ÜNB and BIKO exchange of the BKV
in a positively acknowledged state, and say early where it will not be.

**Triggers:** `aperak_needs_review`, `contrl_sent`, a negative APERAK
or CONTRL received (new event), `erstkontakt_fanout_retry`,
`partin_failed`, `zuordnung_failed`, `bestellung_failed`,
`marktdaten_unbekannt`; deadline: APERAK due, Quittungs-Nachfass
grace (`quittung_nachfass_karenz_stunden`) past, MaBiS activation
deadline past; schedule: daily sweep over `market-partners/overview`.

**Skills:** `de-mako-quittungen` (checks: every received interchange
acknowledged in time, every sent file acknowledged or chased, standing
rejections per stream), `de-mako-registrierung` (checks: error 23 and
Z29 patterns, Nachversand evidence), `de-mabis-aktivierung` (checks:
activations against expected series, deadlines), `de-mako-nachrichten`
(knowledge: the APERAK, PARTIN, ZOE and Bestellungen pages, the KB's
MaBiS and GPKE processes). Tools: the eleven rows above.

**Replaces:** `auto_aperak`, `auto_answer_partin`, `auto_nachversand`,
`auto_zoe`, and the Nachfass/Korrektur/Stammdaten/Auskunft loops.

**First step, as in the conference notes:** propose-only for a month
on real traffic. Measure how many proposals a person would have
approved unchanged.

**2 · Day-ahead watch**

**Purpose:** every delivery day submitted and accepted before the gate,
on the customer's forecast where one exists, and the trader's file in
time.

**Triggers:** `gate_closure_warning`, `forecast_backup_at_trader_deadline`,
`forecast_null_zurueckgehalten`, `forecast_generation_skipped`,
`backup_publish_failed`, `schedules_submitted` with `accepted: false`
or `imbalances`, `vertragsgrenze_nahe`, `fahrplan_vom_uenb_geaendert`;
deadline: trader pickup, gate closure.

**Skills:** `de-fahrplan-dayahead` (checks: tomorrow's state and
source, the badge facts from `mobile_badge`, contract limits), tools:
Protokoll-Notiz, a customer notification mail (new, templated), stop
the service. **Never** a pipeline step (D11): the agent explains
which day is at risk and why, drafts the mail to the customer, and
hands the rest to a person.

**Replaces:** nothing in the pipeline; it replaces the operator
reading three warnings and a chart to work out which of them is the
problem.

**3 · Partner onboarding**

**Purpose:** every Netzbetreiber with customers in its grid knows the
BKV, has our PARTIN and ZOE, and has answered.

**Triggers:** `erstkontakt_mailbox` (a reply classified), schedule:
daily; `kundenhochlauf_quellstand` (new confirmed supply starts).

**Skills:** today's `ClaudeReplyClassifier` becomes this agent's
`verstehe-antwort` check (same nine verdicts, same cache, same regex
fallback and disagreement events), plus `erstkontakt-stand` (checks:
who was written to, who answered, Wiedervorlagen open). Tools:
Erstkontakt mail, Wiedervorlage close, contact address, Zurückstellung.

**Replaces:** `auto_mailbox`, `auto_mailbox_sortieren`, the
confidence threshold becomes rule P4x ("act on a verdict below 0.7
only with approval").

**4 · Settlement watch (later)**

**Purpose:** every quarter-hour of every settled day has an Ist, the
reBAP data is complete, and a TSO's change to a schedule is understood.

**Triggers:** `marktdaten_importiert`, `fahrplan_bestaetigung_fehler`,
`rebap_abruf_fehler`; schedule: the MaBiS calendar (Kategorie A, B, C
due dates).

**Skills:** `de-mabis-bilanzkreisabrechnung` (checks: MSCONS coverage
per Bilanzierungsgebiet against expected series, sums without values
(Z30), price list gaps). Tools: Aktivierungs-Nachfass, a BIKO
enquiry mail (new). Needs the Fahrplan/Ist data the console already
shows; parked until the first settlement months are in.

## Configuration

An agent definition, as the console's wizard (Purpose → Skills → Policy &
limits → Triggers & behaviour → Review) writes it:

```json theme={null}
{
  "slug": "mako-exception-handler",
  "name": "MaKo Exception Handler",
  "description": "Keeps the BKV's market communication positively acknowledged and flags what it cannot fix.",
  "goal": "Keep every Netzbetreiber, ÜNB and BIKO exchange of the BKV in a positively acknowledged state, and say early where it will not be. Propose; a human approves.",
  "skills": ["de-mako-quittungen", "de-mako-registrierung", "de-mabis-aktivierung", "de-mako-nachrichten"],
  "rules": "P31 ...\nP32 ...",
  "autonomy": "propose",
  "triggers": [
    {"kind": "event", "type": "aperak_needs_review"},
    {"kind": "event", "type": "schedules_submitted", "where": {"accepted": false}},
    {"kind": "deadline", "clock": "aperak_due", "lead_minutes": 20},
    {"kind": "schedule", "cron": "0 7 * * *", "timezone": "Europe/Berlin", "parameters": {"scope": "all"}}
  ],
  "behaviour": {
    "dry_run": true,
    "model": "claude-opus-5",
    "worker_model": "claude-haiku-5-5",
    "max_steps_per_round": 60,
    "max_actions_per_run": 20,
    "max_targets_per_action": 20,
    "quiet_hours": null
  },
  "scope": {"control_areas": ["TenneT", "Amprion", "50Hertz", "TransnetBW"], "mp_ids": null},
  "version": 7,
  "saved_by": "ops-rn",
  "saved_at": "2026-11-03T09:12:00+01:00"
}
```

What is configurable and what is not:

| Configurable in the console | Fixed in code |
| - | - |
| Which agents exist, their purpose and goal | Which skills exist, and what each check does |
| Which skills an agent carries | A tool's preview and execute, its parameter schema |
| The agent's rules; the shared policy | A tool's action policy ceiling (an agent can only be stricter) |
| Autonomy level, dry run, caps, model | The "never from an agent" list |
| Triggers: events, deadline leads, schedules | The event catalog; the deadline clocks |
| Scope: Regelzonen, partners, delivery-day horizon | Scope separation of the API; audit |

Everything configurable is a versioned JSON ledger on the audit share,
with the same overlay semantics as the runtime configuration: an
environment can seed agents at deploy time, the console changes them
without a restart, and deleting the overlay reverts.

### Skills from the regulatory KB

Exnaton has one hand-written skill per country ("austrian-market-
processes"). The KB has 31 scopes with data, DE among them with MaBiS,
GPKE, WiM and MPES. Idea 2 of the conference notes packages the KB as
loadable skills per scope and process (`de-mako-mscons`, `de-mabis-aperak`,
`ch-sdat-switch`, `at-eda-eeg`), versioned against the source documents.
In this design a knowledge skill is a generated file in the skill
registry: the process's goal, steps, deadlines, message types, rejection
codes and their meaning, with the KB document and chunk ids it was
generated from and the document version. A test fails when the KB's
document version moves and the skill was not regenerated. The same files,
exposed over MCP by the bridge, are the "Market Process Skills" the notes
propose handing to Exnaton for a DE pilot.

### The process API as a tool surface

Partnership option A in the notes is "an API an agent can read state from
and write actions to". The skill registry's tools are exactly that, and
the bridge exposes them as an MCP server under the process scope (read
tools and preview tools; execute stays behind the preview/execute path
with a key of its own). An external orchestrator - Exnaton's, SAP Joule's -
then sees the same cases and the same previews an internal agent does,
and can approve nothing it could not approve as an operator.

## Measuring it

The analytics the inbox needs, per agent and in total, from the cases
ledger alone:

| Measure | What it says |
| - | - |
| Cases by outcome (D7), cumulative and per week | Whether the agent fixes things or files them |
| Approval rate, unchanged vs edited vs rejected | Whether proposals are right; the figure that decides when `propose` becomes `act_with_approval` |
| Median time to resolution, stale cases | Whether the inbox is worked |
| Runs, failed runs, rounds ended by a cap | The harness's own health |

And one domain figure per agent, the way Exnaton's billability curve
("96.7 % → 99.79 % since baseline") carries its case study:

| Agent | Figure | Today's source |
| - | - | - |
| MaKo Exception Handler | Share of expected partner streams positively acknowledged; APERAKs sent within the deadline | `market-partners/overview`, `standing_rejections`; the APERAK loop's run log |
| Day-ahead watch | Days submitted and accepted before `warn_from`; days on the customer's forecast rather than the backup | `delivery-days`, `forecasts` |
| Partner onboarding | Netzbetreiber with customers in supply that have confirmed; open Wiedervorlagen older than the grace period | Marktrollen, `wiedervorlagen` |
| Settlement watch | Quarter-hours with an Ist of Kategorie A plus B; Zeitreihen *aktiv / erwartet* | Fahrplan/Ist, Zeitreihen |

Each has a baseline the day the agent goes live, and the console shows the
curve since, as the Fahrplan/Ist chart shows deviation today.

## What stays as it is

* The day-ahead pipeline and its hash-based change detection; the
  orchestrator's heartbeat; the gate-closure watchdog's emergency alert
  (an agent may add context to it, never replace it).
* EDIFACT generation and parsing, the reference rules, CONTRL and APERAK
  content, the Bestellungen previews.
* The event catalog as the public contract, the Slack interaction log, the
  alerting rules in `notify.py`. Agent actions are events like any other
  (`case_opened`, `proposal_made`, `proposal_approved`, `action_executed`,
  `rule_proposed`, `rule_adopted`, `run_failed`), cataloged and tested.
* Scopes and keys. The runner acts under a service identity (`agent:<slug>`)
  that appears in `api_call` events and the audit exactly as a caller.
* The operator-app design: device keys, preview/execute, four eyes,
  idempotency, the "never on the phone" list.

## Data protection

* A run receives findings, not the audit share (D10). Each skill declares
  which fields of a finding may reach the model; partner free text is
  allowed only for `verstehe-antwort`, as the classifier sends it today.
* The model, its region and the processing agreement are configuration
  (`classifier_model` becomes per agent; the provider base URL a
  setting), so EU model operation is a deployment choice.
* Run logs hold tool calls and results as the bridge saw them, on the
  audit share, under the same retention as the EDIFACT cache.
* Nothing the model writes reaches a partner without a person or an
  explicitly configured autonomy having seen the preview (D2, D3).

## Order of work

**1. PR 1 - Cases and the inbox, no model**

The cases ledger, `/v1/process/cases*`, the console tab with the six
views and the outcome taxonomy. Today's warnings that already name a
subject become cases opened by code: standing rejections, open
Wiedervorlagen, `gate_closure_warning`, `forecast_backup_at_trader_deadline`,
`vertragsbefund`, `fahrplan_vom_uenb_geaendert`, `marktdaten_unbekannt`.
Operators close them with an outcome. Result: the taxonomy and the
inbox are real before any agent is, and the baseline figures exist.

**2. PR 2 - Agent definitions and the trigger bus**

The `agents/` package, definitions as ledgers, the console wizard, the
trigger bus on the event log and the clocks. Every routine in
`orchestrate()` is registered as an agent run with a schedule trigger
in today's order and today's flag as its autonomy; `orchestrate()`
becomes heartbeat, day steps and "fire what is due". No behaviour
change; the flags are read from the definitions from now on.

**3. PR 3 - Tools and previews**

The skill registry with the eleven tools, each with preview (the dry
run), execute (through the operator-app preview/execute path, extended
to the console) and its policy. The functions without a dry run get
one, with the "preview sends nothing" test of the app design. Checks
for `de-mako-quittungen` and `de-mako-registrierung` written as
enumerated functions over what the overview and the EDIFACT search
return.

**4. PR 4 - The runner, Agent 1 in propose-only**

`adapters/agent_runner.py` with the Claude runner and the in-memory
fake; the run loop with caps and rounds; the MaKo Exception Handler on
real traffic, propose-only, dry run off for reads. A month of
proposals against what the operators did. The approval rate decides
the next level.

**5. PR 5 - Rules and instructions**

Shared policy and agent rules as versioned ledgers, rule citations in
explanations, *Instruct the agent* on a case, proposed rules in
*Improvements*, adoption by a person. Analytics tab.

**6. PR 6 - Agents 2 and 3, flags retired**

Day-ahead watch and partner onboarding; the classifier folded into
Agent 3; the `auto_*` flags removed from `Settings` in favour of the
definitions, with a migration that writes today's flag values into
the seeded definitions.

**7. PR 7 - Knowledge skills from the KB, MCP surface**

Generated `de-*` knowledge skills with KB version pins and the drift
test; the tool registry exposed over MCP under the process scope.
This is the piece that can be handed to a partner.

## Open decisions

**1. Autonomy ceiling**

Should any agent ever reach `act` for a message to a market partner,
or is `act_with_approval` the permanent ceiling for everything but the
deadline-bound APERAK?

**2. Who approves**

The process key today identifies a role, not a person. Four eyes needs
two people; the operator-app design brings named device keys. Is the
console's approval bound to a named console identity (OIDC) before
PR 3, or is the app the only four-eyes surface?

**3. Gate window**

D5 lets a deadline wake an agent inside the gate window; P2 forbids
writes there. Should Agent 2 be allowed its customer mail inside the
window, or only the Notiz?

**4. Model and region**

EU model operation for every agent from day one, or only where
partner free text is involved (Agent 3)?

**5. Rule adoption**

Adopting a proposed rule: one person with the process key, or four
eyes like a PARTIN resend?

**6. Case retention**

Cases and run logs: retained like the EDIFACT cache (200 days), or
with the audit trail (indefinitely)?

**7. Partner skills**

Which three knowledge skills are generated first, and which one is
offered to Exnaton free for a DE pilot?


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.