Skip to content

Fix/lru eviction and effective fingerprint - #4

Merged
nayfly merged 3 commits into
mainfrom
fix/lru-eviction-and-effective-fingerprint
Jul 28, 2026
Merged

nayfly merged 3 commits into
mainfrom
fix/lru-eviction-and-effective-fingerprint

Conversation

@nayfly

@nayfly nayfly commented Jul 28, 2026

Copy link
Copy Markdown
Owner

No description provided.

Robert and others added 3 commits July 27, 2026 19:01
The dashboard was a form per endpoint -- fine for poking the API, useless for
what phase 1 actually requires: looking at what the engine decided and reviewing
every hard_deny by hand before enforcement goes on. That work was CLI-only.

The console shows a live decision feed with per-state filtering and a
distribution bar, the phase-readiness criteria with their verdict, and the
hard-deny events laid out for review. Two read-only endpoints back it:
/v1/decisions and /v1/rollout/report.

It lives inside AETHERYA rather than in a separate frontend because this page
exposes every decision and every action an agent proposed -- the most sensitive
data in the system. A separate app would need its own deployment, its own auth
and its own path to the audit trail: three new things to protect to show the
same data. Server-rendered with no build step, it ships in the container that is
already running and keeps the trail behind one boundary. The API stays open, so
a richer frontend can still be built on top without touching the engine.

Gated behind AETHERYA_CONSOLE_API_KEY when set, open otherwise like the rest of
the decision profile; the page and the route comment both say the port must not
be public.

Removed _dashboard_html (~11KB of unreachable HTML) rather than leaving it to
rot next to its replacement, and dropped the test that asserted its form
controls -- test_console.py covers the new page.

Verified against the running stack: rebuilt, healthy, and the console renders
real traffic across log_only, deny and hard_deny.

1194 tests, 100% coverage under CI's dependency set, mypy strict, ruff and black
clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The sidecar client requires editing the agent loop. Most agent runtimes —
OpenClaw, LiteLLM, anything that accepts a custom OpenAI-compatible provider —
cannot be edited that way. This gates them by changing what the agent talks to
instead of what it does: the gateway speaks chat-completions, forwards upstream
to OpenAI or Anthropic, and rules on every proposed tool call before the agent
sees it. Integration is a config change; OpenClaw is not touched.

A refused call is removed from the response and replaced with a line of text
explaining why, so the model can replan. A silently dropped call just gets
retried.

Notable decisions:

- Gating requires buffering. A verdict cannot be formed from half a tool call,
  so `stream: true` still returns SSE frames but emits them after the verdict.
  The protocol survives; token-by-token latency does not. Documented as a cost,
  not hidden.
- Streamed tool calls carry `index`. Clients reassemble by it; two calls emitted
  without one merge into a single malformed call.
- An actor absent from the capability matrix is denied on every call, and that
  is invisible until phase 3 because `deny` is not enforced before it. The
  gateway refuses to start at phase 3 with an unknown actor and warns otherwise.
- SDKs are imported by name, following llm_provider: both are optional extras,
  and a module-level import would make them mandatory for type-checking and for
  deployments using the other provider.
- Malformed tool arguments are gated as raw text rather than dropped. Dropping
  them would skip the check on exactly the input most likely to be evasive.
- Escalations are refused at phase 3, not held: a single chat-completions call
  has no approval round trip. Asserted in tests so the doc cannot drift.

Verified: the real OpenAI SDK parses the gateway's output in both streaming and
non-streaming modes, and a refused call does not reach the client. The live
upstream path needs an API key and is exercised by scripts/gateway_smoke.py,
which is not part of CI.

100% coverage, mypy strict + ruff + black clean, and green under a CI simulation
with the optional SDKs absent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The console told you "10 hard_deny events require manual review" and gave you
no way to record that you had. `hard_deny_reviewed` was hardcoded to False, so
the phase gate could never pass from the UI and the criterion was decorative.
This makes it real: verdicts are recorded against the audit `event_id` and the
criterion passes on those, and only those.

The store is append-only. A review is what unblocks enforcement, so who said
what and when has to survive; re-reviewing appends and the earlier verdict
stays on disk. Latest wins on read.

Three things deliberately do not pass the gate:

- Any `false_positive`. Reviewing everything is not approving it — enforcing a
  rule a human already called wrong is the exact outcome phase 1 exists to
  prevent. It also requires a note, because the note is what identifies the
  rule to narrow later.
- Events beyond `max_hard_deny_samples`. The report samples; reviewing what was
  sampled must not clear a gate covering events nobody was shown.
- A verdict on an id that is not a hard_deny, or not in the trail at all. It
  would be recorded, counted by nothing, and leave the operator believing they
  had reviewed something.

Fixes a bug in the criterion I introduced with it: a window with zero hard_deny
also never passed, so a well-behaved agent could never leave phase 1. Nothing to
judge is not the same as a judgement withheld.

Recording a review is the console's only write and is never open, even when the
reads are: without a key the verdict carries a reviewer name nobody had to
prove, which is worse than no attribution at all.

Verified end to end against a running server: the criterion holds at 2 of 3,
flips to PASS at 3 of 3, and a later false positive drops it back to HOLD with
the earlier verdict still in the history. CLI and console agree via
`rollout report --review-path`.

100% coverage, mypy strict + ruff + black clean, green under CI simulation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@nayfly
nayfly merged commit 52e814f into main Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant