Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: set the review classifier's keys from the classifier settings

The classifier's Anthropic and ACP provider keys were written by hand in the
secret store's UI. The controller now stores them: GET
/api/review-classifier/keys says whether each is set and when it changed, and
PUT /api/review-classifier/keys/{runtime} writes one. No route answers a key.

The controller's store policy gains create and update on the two leaves with
no read of their values, and a metadata read for the set state. The swarm UI's
classifier settings card gets a key form beside the settings, saved apart
because it goes to the secret store.
This commit is contained in:
iris 2026-11-11 18:29:29 +01:00 • committed by mara
commit 6875a4f0f8
5 changed files with 628 additions and 20 deletions

View file

@ -350,8 +350,11 @@ identity: the host that runs the store has it, and a services host with the
store elsewhere gets the classifier only once it holds a copy of the pair (see
below). Set `deploy.review-classifier.enable` directly to override either way.
The operator writes the key of the runtime the swarm settings choose by hand,
in the secret store's UI under the OIDC login, field `api_key`:
The operator sets the key of the runtime the swarm settings choose in the
swarm UI: the **classifier settings** card on `/jobs` takes a key for the
chosen runtime and shows whether one exists and when it changed, never the
key. You can also write the same two keys in the secret store's UI under the
OIDC login, field `api_key`:
- the API key runtime's Anthropic key at
`secret/swarm/services/review-classifier/anthropic-api-key`

View file

@ -18,7 +18,7 @@ place, and every one reads from [`swarm-controller`](../../../swarm-controller/R
| `/agents/<name>/terminal` | one agent's live terminal |
| `/agents/<name>/subagents/<subagent>/terminal` | one of that agent's subagents' live terminal |
| `/reports` | three folded cards, each read only once opened: the **alerts** the swarm's Grafana holds now, the firing ones, with a button for the pending, no-data and error ones and a link to Grafana's own alert list; read through the controller with a Viewer service-account token, and a cross-repo **issues** list, and a cross-repo **pull requests** list |
| `/jobs` | the controller's job graph, where agent creation and credential mints show progress, and under it the swarm builder's recent builds (agent config PRs, and agents built from `internal/meta` before a deploy), with what nix printed when one failed, and the review classifier's recent runs, a try-out on a real PR, and its settings |
| `/jobs` | the controller's job graph, where agent creation and credential mints show progress, and under it the swarm builder's recent builds (agent config PRs, and agents built from `internal/meta` before a deploy), with what nix printed when one failed, and the review classifier's recent runs, a try-out on a real PR, and its settings and keys |
| `/messages` | the swarm-wide message feed: every message between agents, and to them from the operator, reminders and compaction, with a history window and a filter on sender or recipient; a folded **broadcast** card above the feed sends one message to more than one agent at once, and a folded **schedules** card holds the scheduled prompts: add, edit, pause, resume and remove |
An agent created here or with `swarmctl agent create` starts `paused`; set it

View file

@ -233,7 +233,8 @@ leaves under `secret/data/swarm/services/attic/` (`push-token`, `pull-token`,
`url`, `cache` and `public-key`). That shows the key tree and each key's
versions and timestamps, and no value: the store refuses every `secret/data/`
read. The writes are the leaves the operator fills in by hand: the review
classifier's keys, and the cache's tokens and facts, which configure an external
classifier's keys (which `swarm-controller` also writes for the swarm UI, with
`create` and `update` and no `read` of the value), and the cache's tokens and facts, which configure an external
cache or replace the in-swarm cache's own. The cache's host units create those
leaves only while the store holds none, so a saved value survives their next run
and every deploy. The cache's signing key, `attic/jwt-secret`, isn't writable here.

View file

@ -284,8 +284,33 @@ does. Every error is problem+json with the reason in `detail`:
- **500** when the controller can't read or write the settings
When the controller can't read the settings, a try-out answers the same 503
or 500, and a review posts a no-verdict comment. The operator writes each
runtime's key by hand in the secret store
or 500, and a review posts a no-verdict comment. The keys aren't in this
document; see [The keys](#the-keys).
### The keys
`GET /api/review-classifier/keys` answers whether each runtime has a key:
```json
{
"api_key": { "set": true, "updated": "2026-11-11T10:00:00Z" },
"acp": { "set": false, "updated": null }
}
```
`PUT /api/review-classifier/keys/{runtime}` (`api-key` or `acp`) takes
`{"api_key": "…"}`, stores it at that runtime's leaf and answers the same
document. No route answers a key. The controller's store policy holds `create`
and `update` on the two leaves with no `read` of their values, and `read` on
their metadata, which is where `set` and `updated` come from. The route trims a
key and refuses one that's empty, over 4096 bytes or holds whitespace. The
PUT needs the `Remote-User` header, and logs the operator and the runtime, never
the key. Every error is problem+json: **400** for a body that isn't this shape,
a runtime that isn't one of the two or a refused key, **401** for no
`Remote-User`, **503** when the store is unreachable or refuses.
The classifier reads the leaf on every run, so a new key applies from the next
run. The operator can still write the leaves in the secret store's UI
([`services.md`](../../2-handbook/swarm/services.md#review-classifier)).
The per-agent equivalent is [`inference.md`](inference.md), where the
@ -479,7 +504,8 @@ ACP provider, so each runtime has a model field of its own.
To switch to ACP, an admin:
1. writes the provider key in the secret store's UI under the OIDC login, at
1. stores the provider key, in the swarm UI's classifier settings or in the
secret store's UI under the OIDC login, at
`secret/swarm/services/review-classifier/acp-provider`, field `api_key`
2. PUTs the runtime and the provider:

View file

@ -327,8 +327,15 @@ limit gets a no-verdict review, and a try-out with more gets a 400.
the runtime, model, effort and limits, and the provider too while the runtime is
ACP, so switching back to API key keeps the stored provider for next time. If
the controller refuses it, the page shows the reason and leaves the form as you
typed it. A change takes effect from the next run, with no restart. The page
has no API key field: admins set each runtime's key in the bao UI.
typed it. A change takes effect from the next run, with no restart.
Under the settings form sits a **key** form for the runtime the form chooses.
Its label says whether that runtime has a key and when it last changed
(`GET /api/review-classifier/keys`, which never answers a key). The field is a
password input; **save key** sends it alone with `PUT
/api/review-classifier/keys/{runtime}`, apart from the settings, and clears the
field once the controller has stored it. Switching the runtime drops a key you
typed for the other one.
### Scheduled prompts