Watch
0
0
Fork
You've already forked hyperhive
0

add a triage archetype: haiku, a triage prompt, flows for new issues and prs

This commit is contained in:
atlas 2026-11-11 22:16:24 +01:00 • committed by mara
commit 70e4c63ff7
5 changed files with 166 additions and 10 deletions

View file

@ -17,12 +17,29 @@ swarm can also turn an archetype agent into a custom one later
The hyperhive flake ships these:
| archetype | what it sets |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `reviewer` | permissions `messaging`, `inbox`, `execution`: the default set without `meta`. With `execution` it can run `hive-forge`, `git` and other commands. |
| `android-dev` | installs `android-tools` (`adb`, `fastboot`), Gradle and a JDK; default permissions. |
| `reverse-engineer` | sets `services.hyperhive.agent.rea.enable`: installs REA, Ghidra 12.1 and procps, and allows all REA MCP tools; default permissions and the agent's normal memory cap. |
| `ruth` | the manager agent. Inference model `sonnet`; permissions: the default set plus `manage_root_agent`, `logs` and `metrics`. Its module is empty. The swarm UI's setup assistant creates ruth from it. |
| archetype | what it sets |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `reviewer` | permissions `messaging`, `inbox`, `execution`: the default set without `meta`. With `execution` it can run `hive-forge`, `git` and other commands. |
| `android-dev` | installs `android-tools` (`adb`, `fastboot`), Gradle and a JDK; default permissions. |
| `reverse-engineer` | sets `services.hyperhive.agent.rea.enable`: installs REA, Ghidra 12.1 and procps, and allows all REA MCP tools; default permissions and the agent's normal memory cap. |
| `ruth` | the manager agent. Inference model `sonnet`; permissions: the default set plus `manage_root_agent`, `logs` and `metrics`. Its module is empty. The swarm UI's setup assistant creates ruth from it. |
| `triage` | labels and assigns every new issue and pull request, then routes it. Inference model `haiku`; default permissions. It has no forge subscription: [its flows](#notification-flows) send it a todo for each new or reopened issue and each new pull request. |
Run one `triage` agent per swarm. Its flows send to `members`, so every `triage`
agent gets every new item, and two of them would both label and assign it.
- **Flows already in place.** If the operator's flow document already sends new
issues to an agent, as a custom flow on the notification router board does,
creating a `triage` agent gives each new issue two todos. Switch one off on the
board: the custom flow, or the archetype's flow
([below](#on-the-board)).
- **Forge rights.** A new agent's forge account is a write collaborator on its own
config repo and has read on `internal/docs`, and nothing else. Labelling and
assigning an issue or a pull request in a project repo takes write access to
that repo, so the triage agent can't do either until the operator adds it as a
write collaborator on each repo it should triage, in the forge's repo settings
or with `hive-forge repo-add-collaborator <agent>`. Without that grant its
label and assign calls fail.
An existing ruth, created before this archetype, keeps her own config repo and
model. Set `sonnet` in the swarm UI's inference settings for her if wanted.
@ -107,7 +124,7 @@ created from the archetype.
hyperhiveArchetypes.triage.meta.flows = [
{
id = "new-issues";
name = "new issues go to triage";
name = "new issues get a greeting";
nodes = [
{ id = "src"; kind = "source"; events = [ "issues" ]; }
{ id = "opened"; kind = "match"; when = [ { field = "action"; equals = "opened"; } ]; }
@ -116,8 +133,8 @@ hyperhiveArchetypes.triage.meta.flows = [
kind = "todo";
to = "members";
op = "upsert";
key = "forge:{{ repo }}#{{ number }}:triage";
text = "triage {{ repo }}#{{ number }}: {{ title }}";
key = "forge:{{ repo }}#{{ number }}:greet";
text = "greet {{ repo }}#{{ number }}: {{ title }}";
}
];
edges = [

View file

@ -81,4 +81,93 @@
};
};
};
triage =
let
# One flow per event and action; every todo goes to `members`, under a
# `forge:<repo>#<number>:` key so the built-in closed flow clears it.
newItem =
{
id,
name,
event,
action,
text,
}:
{
inherit id name;
nodes = [
{
id = "src";
kind = "source";
events = [ event ];
}
{
id = "action";
kind = "match";
when = [
{
field = "action";
equals = action;
}
];
}
{
id = "tell";
kind = "todo";
to = "members";
op = "upsert";
key = "forge:{{ repo }}#{{ number }}:triage-${action}";
inherit text;
}
];
edges = [
{
from = "src";
to = "action";
}
{
from = "action";
to = "tell";
}
];
};
in
{
module =
{ lib, ... }:
{
services.hyperhive.agent.icon = lib.mkDefault ./icons/triage.svg;
};
meta = {
icon = builtins.readFile ./icons/triage.svg;
description = "Labels and assigns every new issue and pull request, then routes it. It runs on haiku, holds the default permissions and wakes on router todos for new and reopened issues and new pull requests, not on forge subscriptions.";
prompt = builtins.readFile ./prompts/triage.md;
inference = {
model = "haiku";
};
flows = [
(newItem {
id = "issue-opened";
name = "new issues go to triage";
event = "issues";
action = "opened";
text = "{{ repo }}#{{ number }}: a new issue was created. make sure it's labeled and has a matching assignee.";
})
(newItem {
id = "issue-reopened";
name = "reopened issues go to triage";
event = "issues";
action = "reopened";
text = "{{ repo }}#{{ number }}: an issue was reopened. make sure it's labeled and has a matching assignee.";
})
(newItem {
id = "pr-opened";
name = "new pull requests go to triage";
event = "pull_request";
action = "opened";
text = "{{ repo }}#{{ number }}: a new pull request was created. make sure it's labeled and has a reviewer.";
})
];
};
};
}

View file

@ -0,0 +1,15 @@
<svg width="300" height="300" viewBox="0 0 300 300" role="img" xmlns="http://www.w3.org/2000/svg">
<title>triage</title>
<desc>Triage archetype icon: a funnel sorting items into one outlet, cyan on dark</desc>
<defs>
<clipPath id="clip"><circle cx="150" cy="150" r="140"/></clipPath>
</defs>
<g clip-path="url(#clip)">
<rect width="300" height="300" fill="#00080a"/>
<polygon points="80,90 220,90 168,156 168,214 132,196 132,156" fill="none" stroke="#00e5ff" stroke-width="12" stroke-linejoin="round"/>
<circle cx="116" cy="62" r="9" fill="#00e5ff"/>
<circle cx="150" cy="52" r="9" fill="#00e5ff"/>
<circle cx="184" cy="62" r="9" fill="#00e5ff"/>
</g>
<circle cx="150" cy="150" r="140" fill="none" stroke="#00e5ff" stroke-width="3" opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 793 B

View file

@ -0,0 +1,18 @@
# Triage
You make sure nothing new sits unowned. Each issue and pull request gets
labels and one owner, and you send it to the agent best placed to act on it.
You route; you don't do the work.
You read the item and check the forge before you act, since notices go stale.
Labels come from the repository's own set, and priority labels are the
operator's, never yours. One item has one owner: if someone was assigned on
purpose, you leave them and tell the operator. When you can't tell what an item
is or who should take it, you ask; a wrong guess costs more than a question.
Once the classifier has approved the current head, you request the reviewer,
and again only after a push moves the head.
You tell the operator an item is ready to merge only when CI is green, approvals
sit on the current head, it is mergeable and has no dependencies. You never
merge. You make one lint pass per wake, then stop.

View file

@ -291,7 +291,7 @@ let
# output and prints it as JSON. A derivation or a path in `meta` would
# make that evaluation build or copy something; a function would not
# print at all.
name = "the built-in archetypes carry a module and a meta of plain data, and reviewer can run commands and android-dev installs its toolchain and reverse-engineer turns rea on, and each sets an svg icon as a default, and each has a non-empty prompt under the 64 KiB cap, and ruth runs sonnet with manage_root_agent, logs and metrics";
name = "the built-in archetypes carry a module and a meta of plain data, and reviewer can run commands and android-dev installs its toolchain and reverse-engineer turns rea on, and each sets an svg icon as a default, and each has a non-empty prompt under the 64 KiB cap, and ruth runs sonnet with manage_root_agent, logs and metrics, and triage runs haiku with three flows that send to members";
ok =
let
plain =
@ -326,7 +326,24 @@ let
(archetypes.android-dev.module { inherit pkgs lib; }).services.hyperhive.agent.icon
(archetypes.reverse-engineer.module { inherit lib; }).services.hyperhive.agent.icon
(archetypes.ruth.module { inherit lib; }).services.hyperhive.agent.icon
(archetypes.triage.module { inherit lib; }).services.hyperhive.agent.icon
]
&& metas.triage.inference.model == "haiku"
&&
lib.sort (a: b: a < b) (map (f: f.id) metas.triage.flows) == [
"issue-opened"
"issue-reopened"
"pr-opened"
]
&& lib.all (
f:
let
todos = lib.filter (n: n.kind == "todo") f.nodes;
in
todos != [ ]
&& lib.all (n: n.to == "members" && lib.hasPrefix "forge:{{ repo }}#{{ number }}:" n.key) todos
) metas.triage.flows
&& !(metas.triage ? watch)
&& metas.ruth.inference.model == "sonnet"
&& lib.all (p: lib.elem p metas.ruth.permissions) [
"messaging"