How the application is built¶
The application is one ankka service. Its components fall into six kinds, and each part of the problem is given to the kind that can hold its guarantee.
| Component kind | What it holds here | Examples |
|---|---|---|
| Event sourced entity | every record, with its full history | request, facet, question, source, finding, claim, assessment, sufficiency, gap, search plan, search execution, package, approach, decision, design, stage run |
| Workflow | a multi-step process that must resume after a crash | ingestion, verification, investigation, question investigation, impact |
| Agent | one model call per handler, with a versioned prompt and a typed reply | request agent, search agent, evidence agent, design agent |
| View | a read model for search and listing | the chunk store, measurements, the dependency graph, listings of sources, records, assessments, executions and designs |
| Consumer | a reaction to a change | start verification when a record is proposed; start an impact walk when a source or record changes |
| HTTP endpoint | the edge, each with its own access rule | the operator's API, the reviewer's pages, a health probe |
Pure logic with no state, such as the rule sets, the parser, the chunker and the rank fusion, is plain
Scala in the rules, retrieval and adapters packages, called from workflow steps.
The two processes¶
Two workflows carry almost all the work.
Ingestion runs once per source. It fetches the source through an adapter, records the content under its hash (the same content again is a duplicate and adds nothing), parses it into sections with stable character offsets, cuts and embeds chunks, records any measurements, judges the source's eligibility, and extracts findings and claims. Each extracted record then gets its own verification workflow, started by a consumer, which checks it deterministically and then against the source passage. Evidence is therefore extracted and verified before any question asks for it.
Investigation runs once per customer request. It interprets the request into facets, checks the interpretation by reconstructing the request from it, waits for a reviewer to confirm, derives the scientific questions, and starts one question investigation per question. Each of those plans and runs its search, applies eligibility, resolves the hits' context, and waits; assessment and sufficiency judgements are asked for per question. When the evidence is judged enough, the investigation builds one evidence package per question, proposes candidate approaches, waits for a reviewer's decision, writes the design, and waits for approval.
The diagram uses four colours. Blue is a model proposing, amber is a deterministic check, green is a named reviewer deciding, and grey is an outcome that is recorded and kept.
Who is responsible for what¶
Each responsibility has one owner, and the boundaries are where ankka's guarantees are.
- An entity enforces the rules about itself. A search plan refuses a plan without both kinds of query; a decision refuses a second decision and an unlisted reviewer; a design refuses approval of a version that is not under review, and never changes an approved version. These checks run inside the entity's command handler, so no caller can bypass them, and a refusal persists nothing.
- A rule set decides about a proposal. Facet, coverage, eligibility, applicability, package and
design rules are pure functions in the
rulespackage. A workflow step calls them with the proposal and the records it needs, and acts on the result. They are tested without the runtime. - An agent only proposes. An agent handler builds a prompt, calls the model, and decodes the reply into a case class. It writes nothing. Its only defence of its own is an output guardrail, used once: the design agent refuses a reply that asserts a percentage chance of success.
- A workflow sequences and retries. It calls the agent through one helper that records the call on the run's provenance entity, applies the gate, writes the accepted records, and decides the next step: continue, retry with the defect, or record the failure and pause.
- A view answers questions across records. Retrieval reads the chunk view; the impact walk reads the dependency view; the API's listings read their views. Nothing that must be exact is decided from a view, because a view is eventually consistent.
- An endpoint is thin. It translates HTTP to commands and back. The API and the review pages call the same commands, so a reviewer's form and an operator's request are checked by the same code.
Proposals, records and rules shows the types that carry these responsibilities.
Provenance¶
Every workflow run has a stage-run entity. Each stage starts by asking it whether this stage has
already completed for exactly these inputs, identified by a hash of the input and the prompt version;
if so the stage is skipped, which is what makes a resumed workflow safe. When the stage completes or
fails, the entity records the attempt, the input references, the output reference, the checks that ran
with their results, and the model calls with prompt version, model and latency. GET
/api/v1/runs/{id} returns it.
Reactions to change¶
Two consumers keep the records consistent with each other. When a finding or claim is proposed or reproposed, one starts its verification. When a source's status changes, or a record is rejected, quarantined or reproposed, another starts an impact walk: a workflow that follows the dependency view from the changed record to everything built on it and flags each dependent for review. A flag names the changed record and version; an approved design keeps its approved content and carries the flag beside it. How evidence is verified shows both.