an archetype carries its own notification-router flows for its agents
ArchetypeMeta gets flows, checked when the controller stores the record: every todo must go to the new members recipient, which the router expands to the agents of the archetype. The operator's flows document refuses members. The router reads the archetype records and each roster agent's archetype key, and its NATS grant gains read on both buckets.
This commit is contained in:
parent
ca339b7ba3
commit
1efbb72fdd
3 changed files with 883 additions and 29 deletions
|
|
@ -61,7 +61,9 @@ hyperhiveArchetypes.reviewer.meta.prompt = builtins.readFile ./prompts/reviewer.
|
|||
`prompt` is optional text that prefills `AGENTS.md`, [below](#the-prompt).
|
||||
`icon` is optional SVG text, at most 16 KiB, that the create form shows on the
|
||||
archetype's tile; `builtins.readFile ./icon.svg` gives it. The swarm refuses an
|
||||
`icon` that isn't SVG text or is over the limit, like any bad `meta`, and ignores
|
||||
`icon` that isn't SVG text or is over the limit, like any bad `meta`.
|
||||
`flows` is an optional list of notification router flows,
|
||||
[below](#notification-flows). The swarm ignores
|
||||
other keys in `meta`.
|
||||
- `module` is a NixOS module for the agent's container. The meta flake
|
||||
imports it into each agent created from the archetype.
|
||||
|
|
@ -93,6 +95,54 @@ archetype without `prompt` leaves the box alone.
|
|||
Where the files live:
|
||||
[the implementation page](../../4-implementation/swarm/archetypes.md#the-built-in-prompts).
|
||||
|
||||
## Notification flows
|
||||
|
||||
An archetype's `meta.flows` lists [notification router
|
||||
flows](../../4-implementation/swarm/notify-router.md#flows) that run for every
|
||||
agent of the archetype, next to the built-in flows and the ones in the
|
||||
operator's document. A `todo` node in them sends to `members`: every agent
|
||||
created from the archetype.
|
||||
|
||||
```nix
|
||||
hyperhiveArchetypes.triage.meta.flows = [
|
||||
{
|
||||
id = "new-issues";
|
||||
name = "new issues go to triage";
|
||||
nodes = [
|
||||
{ id = "src"; kind = "source"; events = [ "issues" ]; }
|
||||
{ id = "opened"; kind = "match"; when = [ { field = "action"; equals = "opened"; } ]; }
|
||||
{
|
||||
id = "tell";
|
||||
kind = "todo";
|
||||
to = "members";
|
||||
op = "upsert";
|
||||
key = "forge:{{ repo }}#{{ number }}:triage";
|
||||
text = "triage {{ repo }}#{{ number }}: {{ title }}";
|
||||
}
|
||||
];
|
||||
edges = [
|
||||
{ from = "src"; to = "opened"; }
|
||||
{ from = "opened"; to = "tell"; }
|
||||
];
|
||||
}
|
||||
];
|
||||
```
|
||||
|
||||
- A flow here has the shape of a flow in the operator's document, with two
|
||||
differences. Every `todo` must send to `members`. A flow that names an agent
|
||||
or an event field's agents, or asks for a review, belongs in the operator's
|
||||
document, and the swarm refuses it as it refuses any bad `meta`: the
|
||||
archetype leaves the list and the reason goes into `refused`. The operator's
|
||||
document in turn refuses `members`, since it has no archetype.
|
||||
- Flow ids are unique within the archetype.
|
||||
- Use a todo key that starts with `forge:{{ repo }}#{{ number }}:`, as the example
|
||||
does. The built-in `closed` flow clears todos by that prefix when the issue
|
||||
or PR closes.
|
||||
- A change to the archetype's flows, or to who belongs to it, takes effect within
|
||||
about 10 seconds of the next forge event. An agent outside the swarm gets
|
||||
nothing.
|
||||
- Custom agents have no archetype and get no archetype flows.
|
||||
|
||||
## How the list stays current
|
||||
|
||||
The swarm controller asks the builder for the list when the queue connects and
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ it stores. The operator's view is
|
|||
under the key `hyperhive`, when it differs from the stored one. The list
|
||||
leaves out an archetype that fails the check, and its name and reason go
|
||||
into the record's `refused` map and the controller's journal.
|
||||
An archetype's `flows` pass the router's [archetype check](notify-router.md#flows) here too; the
|
||||
notification router reads them from this record.
|
||||
4. `GET /api/archetypes` answers from that record, and the create form lists
|
||||
from it.
|
||||
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ each one runs, so one event can reach more than one leaf. A flow must be a tree:
|
|||
one source, one parent for every other node, every node reachable, no children
|
||||
under a `todo` or a `request_reviewer`. The most is 64 nodes, 16
|
||||
levels deep, and 100 custom flows. A `todo` node sends to the agents an event
|
||||
field holds (`assignees_added`, `todo_holders`, …) or to one named agent.
|
||||
field holds (`assignees_added`, `todo_holders`, …) or to one named agent. An archetype's flows also send to its members ([below](#archetype-flows)).
|
||||
|
||||
The router ships the eight flows above, each a source → match on `action` →
|
||||
todo (`conflict` and `conflict-cleared` also match `mergeability_changed`), and `head-classifier`: source `pull_request` and `pull_request_sync` →
|
||||
|
|
@ -274,6 +274,21 @@ Before the router has read any document, "before" means the built-ins, so a bad
|
|||
document never leaves it with no flows. The controller refuses to overwrite a
|
||||
document of a version it doesn't know.
|
||||
|
||||
**Archetype flows.** An archetype carries flows of its own in its record's
|
||||
`meta.flows` ([operator page](../../2-handbook/swarm/archetypes.md#notification-flows)).
|
||||
Each is an ordinary flow with one more recipient, `Recipients::Members` (`"members"`
|
||||
on the wire): every agent whose `agent-archetype` key names the archetype the
|
||||
flow came from. The controller checks each one with `Flow::validate_archetype` when it stores the record: the check
|
||||
of any flow, except that every `todo` goes to `members` and no node requests a
|
||||
review. The operator's document refuses `members` (`Flow::validate`), because it has
|
||||
no archetype. The router reads the `agent-archetypes` record and the
|
||||
`agent-archetype` key of each agent in its roster at most every 10 seconds, when an event arrives, and runs
|
||||
each archetype's valid, enabled flows after the built-ins and the document's flows, for the
|
||||
archetype's agents. The router logs a flow it can't use and leaves it out. A read that fails keeps the archetype flows it had.
|
||||
Each runs under the id `<archetype>:<id>`, so its todos' source is
|
||||
`router:<archetype>:<id>`. A recipient not in the roster gets nothing, the same as a named agent that doesn't exist.
|
||||
A todo key is the dedupe key as for any flow: the same key from the operator's document replaces the archetype's todo.
|
||||
|
||||
**Deploying in any order.** The controller, the router and the UI can be on
|
||||
different hosts. A router that predates `request_reviewer` reads a document
|
||||
holding such a node (a custom flow with one) as one it can't read and keeps the
|
||||
|
|
@ -283,6 +298,7 @@ as before. A router that has `request_reviewer`, with a
|
|||
controller that predates it, gets no answer to the request and logs that. A router that predates the `conflict` flows ignores the `pull_request_mergeability` events a newer controller sends, and gives no conflict todos; a controller that predates them sends none, so the new flows never run. A controller that predates this leaves the key absent, and the
|
||||
router runs the built-ins as before. A router that predates this ignores the
|
||||
document, so the page and the API show a state the router isn't yet running.
|
||||
A router that predates archetype flows ignores `meta.flows`, and a controller that predates them drops the field when it stores the record, so the agents of an archetype get no router todos from it until both are new. Deploy `swarm-nats-auth` before the router: without its read grant on the two archetype buckets, each read times out, the router logs it and runs without archetype flows, and the event that triggered the read waits for the timeout.
|
||||
|
||||
## Deliveries and dry runs
|
||||
|
||||
|
|
@ -355,11 +371,11 @@ route: the page, if newer, shows the failure.
|
|||
|
||||
## Credentials and grants
|
||||
|
||||
| Principal | Grant |
|
||||
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `swarm-notify-router`, in its container | store: `read` on `swarm/services/<router client id>/oidc/client`. Queue: its own consumer on `SWARM_EVENTS` (create, pull, ack) and `STREAM.MSG.GET` on it for the dry run, publish on `$SWARM.router.todo.*` and on `$SWARM.router.sink.request-reviewer`, create `ROUTER_TODOS`, create `NOTIFY_ROUTER_LOG`, publish its one subject and read it by sequence, its own `notify-router-state` bucket, read on the `notify-flows` bucket, `STREAM.INFO` on the agent-status bucket, which lists the agents, and subscribe to the two `$SWARM.router.api.*` request subjects and answer each once, for 10 seconds |
|
||||
| swarm-controller | publish on `$SWARM.events.forge.*` and on the two `$SWARM.router.api.*` request subjects; subscribe to `$SWARM.router.sink.request-reviewer` and answer each request once, for 30 seconds; creates and writes the `notify-flows` bucket (every bucket, by its `$KV.*.>` grant) |
|
||||
| an agent, under its own credential | create its own consumer `todo-<agent>` in the form whose filter is `$SWARM.router.todo.<agent>`, pull from it, and ack what it delivered |
|
||||
| Principal | Grant |
|
||||
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `swarm-notify-router`, in its container | store: `read` on `swarm/services/<router client id>/oidc/client`. Queue: its own consumer on `SWARM_EVENTS` (create, pull, ack) and `STREAM.MSG.GET` on it for the dry run, publish on `$SWARM.router.todo.*` and on `$SWARM.router.sink.request-reviewer`, create `ROUTER_TODOS`, create `NOTIFY_ROUTER_LOG`, publish its one subject and read it by sequence, its own `notify-router-state` bucket, read on the `notify-flows` bucket, read on the `agent-archetypes` bucket (with `STREAM.INFO`) and the `agent-archetype` bucket, `STREAM.INFO` on the agent-status bucket, which lists the agents, and subscribe to the two `$SWARM.router.api.*` request subjects and answer each once, for 10 seconds |
|
||||
| swarm-controller | publish on `$SWARM.events.forge.*` and on the two `$SWARM.router.api.*` request subjects; subscribe to `$SWARM.router.sink.request-reviewer` and answer each request once, for 30 seconds; creates and writes the `notify-flows` bucket (every bucket, by its `$KV.*.>` grant) |
|
||||
| an agent, under its own credential | create its own consumer `todo-<agent>` in the form whose filter is `$SWARM.router.todo.<agent>`, pull from it, and ack what it delivered |
|
||||
|
||||
No other client may publish on `$SWARM.router.todo.*`, `$SWARM.router.log`, the two
|
||||
`$SWARM.router.api.*` subjects, `$SWARM.router.sink.request-reviewer` or
|
||||
|
|
|
|||
Loading…
Reference in a new issue