Implementation: IMPLEMENTED (E1, E5 flywheel, E6 vendor adapters) · Exposure: PREVIEW. Served by allternit-api’s Agency Kernel Decision Runtime (
cmd/allternit-api/src/agency_api/decisions/). The local scorer is the sidecar in domains/decision-runtime/, managed by Allternit Desktop. Computer use starts calling it in phase E2.- Bounded. The answer is always one of your option ids, or
nullwhen it abstains. - Pure. A decision has no side effects. Before you act on one, check that the chosen option is still valid (the screen may have changed).
- Not authorization. A decision is never a permission. Policy runs first; a decision can only add caution.
Create a decision
Allternit-Request-Id.
Request body
Response
Example
allow_abstain is false, the response is 503 with error.code ERR_DECISION_UNAVAILABLE and the attempts in error.detail.
Backends and escalation
The router walks the chain in order (defaulthead,local,vendor,typesafe,oracle, set with ALLTERNIT_DECISIONS_CHAIN). It stops at the first answer at or over the kind’s threshold; a later answer replaces an earlier one. Backends that are not configured are left out; backends that cannot see images are skipped for a decision that carries one.
Vendor backends
Both vendor backends are off by default and gated twice: the key has to be configured on the server (it comes from the server’s secret environment, is sent only as the vendor’s bearer token, and never appears in logs, attempts or errors), and the decision’sproject has to be enabled for that backend in ALLTERNIT_DECISIONS_VENDOR_PROJECTS:
* enables every decision, with or without a project. A request that names a vendor in backends for a project that is not enabled leaves it out of the chain. Endpoint and model can be overridden with ALLTERNIT_DECISIONS_VENDOR_URL / _MODEL and ALLTERNIT_DECISIONS_TYPESAFE_URL / _MODEL. Both vendors bill per token; get the spend approved before enabling a project.
To compare backends on the same decisions, run domains/decision-runtime/bench/compare_backends.py. It sends each case once per backend through /v1/decisions (same adapters as production) and prints a latency and accuracy table. It refuses metered backends unless --allow-metered is passed.
Thresholds per kind: default 0.6, verify 0.8, safety 0.9, escalate 0.7. Override with a JSON object in ALLTERNIT_DECISIONS_THRESHOLDS, for example {"default": 0.5, "element": 0.55}.
The local scorer
The sidecar binds a loopback port at app start and does nothing heavy until the first decision. That decision starts its preparation: it installs its pinned Python packages, downloads the model (about 3 GB) into the app’s data folder, and loads it. Until it is ready, decisions escalate past it; its status and download progress are inGET /health on the sidecar and in Desktop’s decision-runtime:get-status. No model weights ship in the app.
To get the most out of it, put what stays the same first in context (the goal, the element map) and what changes per step last or in question, with blank lines between blocks. The scorer keeps the state at each block boundary, so the next decision over the same screen only reads what changed.
Measured on an M1 Pro (2026-10-09, Qwen3.5-4B 4-bit, machine under load): 19 of 19 obvious Calculator decisions right, p50 154 ms, p95 252 ms; a decision that reuses the screen costs about 130 ms, and one forward pass costs about 110 ms however few tokens it reads. Qwen3.5-2B ran at a 65 ms p50 but got only 9 of 19 right, so 4B stays the default. Newer Apple silicon is faster.
Report the outcome
Returns the stored decision.
GET /v1/decisions/{id} returns it too. The store keeps the context’s sha256, never the context itself, plus the options, choice, probabilities, backend, latency, attempts and outcome. These rows are the training data for the trained heads, and an outcome also feeds the flywheel below.
The flywheel
A head is a tiny, fast decider registered for a decision kind. It earns its place on live traffic:- Shadow. Every decision of the kind runs each registered head after the answer is stored, off the latency path. The head’s choice is kept next to the served choice and, once you report it, the outcome.
- Promotion. When a head’s accuracy against outcomes reaches the kind’s bar over a minimum number of labelled answers, and its confidence is not higher than its accuracy, it goes live: it is tried first in the chain (the
headslot). If it has no answer, or answers under the threshold, the decision escalates as usual. - Demotion. When a live head’s accuracy drops under a lower bar, it goes back to shadow. Metrics count only answers since the last change of state, so every state has to be earned again.
success confirms label (or the served choice); failure with a different label names the right option, otherwise it rules out the served choice; error and skipped do not count.
Override per kind with JSON in
ALLTERNIT_DECISIONS_FLYWHEEL, for example {"default": {"min_samples": 300}, "element": {"promote_at": 0.98}}.
The first head is head.lookup: an exact-match lookup over your own past outcomes. Same kind, same context, same question and the same options in the same order: it answers with the option that succeeded there before (one success gives 0.67, five give 0.86; failures count against it). It reads one indexed row and answers in well under 10 ms. It never sees images, it learns from the first outcome of each decision only, and it is per account. With no traffic it stays in shadow.
List heads
state (shadow, live or retired), state_since, the bar, metrics since the last change of state (decisions, answered, coverage, agreement with the served choice, labelled, accuracy, overconfidence, head p50_latency_ms / p95_latency_ms), next (the change the metrics call for, if any) and the last ten audit entries. Counts only: no contexts or options.
Change a head’s state
ALLTERNIT_DECISIONS_ADMINS (comma separated; * for a single-user Desktop) may change it; others get 403. retired takes a head out of shadow and serving until it is set back to shadow.
Compatibility with contract v0.3
The published v0.3 contract describes a wider decision surface (operation, scale, range, require_level, threshold_action). The server implements the choice decision above. It accepts the v0.3 names state (for context), candidates (for options), label (for an option’s text) and max_latency_ms (for latency_budget_ms). The other operations and calibration levels are not served yet.