Watch
0
0
Fork
You've already forked hyperhive
0

archetypes: prefill the create form from flake-declared agent archetypes

A flake declares agent archetypes in its `hyperhiveArchetypes` output,
`<name> = { module; meta; }`. The hyperhive flake ships `reviewer`
(default permissions without `execution`).

swarm-builder answers `$SWARM.builder.archetypes` with the `meta` of
every archetype its own hyperhive source declares. swarm-controller asks
once the queue connects and every 10 minutes, checks each archetype as
`POST /api/agents` checks its settings, and stores the result in the
`agent-archetypes` KV bucket when it changed. `GET /api/archetypes`
serves it, and the create form's archetype select prefills permissions,
resource caps and inference from it.

The agent created is an ordinary agent with its own config repo; the
archetype's module is not applied yet.

Refs #5386
This commit is contained in:
atlas 2026-10-17 00:24:08 +02:00 • committed by mara
commit 51035eac1d

View file

@ -54,6 +54,11 @@ import {
requestPermissions,
togglePermission,
} from "./CreateAgentPermissions.js";
import {
archetypeKey,
prefill,
type ArchetypeList,
} from "./CreateAgentArchetype.js";
import "./CreateAgentForm.css";
// Mirrors swarm-controller's `HiveEntry` — same shape `HivesPage`
@ -131,6 +136,16 @@ export function CreateAgentForm({
null,
);
const [selected, setSelected] = useState<string[]>([]);
// `null` until `/api/archetypes` answers; `archetype` is the picked one's
// `archetypeKey`, `""` for none.
const [archetypes, setArchetypes] = useState<ArchetypeList | null>(null);
const [archetypesError, setArchetypesError] = useState<ProblemDetails | null>(
null,
);
const [archetype, setArchetype] = useState("");
const picked = archetypes?.archetypes.find(
(a) => archetypeKey(a) === archetype,
);
// Loaded once, not re-fetched on submit: the roster changes rarely
// enough (a nix-level swarm config change) that staleness within one
@ -165,6 +180,31 @@ export function CreateAgentForm({
})().catch((e: unknown) => setCatalogueError({ detail: String(e) }));
}, []);
useEffect(() => {
(async () => {
const r = await fetch("/api/archetypes");
if (!r.ok) {
setArchetypesError(await readApiError(r));
return;
}
setArchetypes((await r.json()) as ArchetypeList);
})().catch((e: unknown) => setArchetypesError({ detail: String(e) }));
}, []);
// Fills permissions, caps and inference from the picked archetype; each
// stays editable, and the request carries only those fields.
function pickArchetype(key: string) {
setArchetype(key);
const p = prefill(
archetypes?.archetypes.find((a) => archetypeKey(a) === key),
catalogue?.default ?? [],
);
setSelected(p.permissions);
setCpu(p.cpu);
setMemory(p.memory);
setInference(p.inference);
}
const hiveOptions: SelectOption[] = (hives ?? []).map((h) => ({
value: h.name,
label: h.name,
@ -199,6 +239,7 @@ export function CreateAgentForm({
const data = (await r.json()) as CreateAgentResponse;
setResult({ status: "done", nodeId: data.node_id });
setName("");
setArchetype("");
if (catalogue)
setSelected(
initialSelection(catalogue.all, catalogue.default, extraPermissions),
@ -230,6 +271,12 @@ export function CreateAgentForm({
problem={catalogueError}
/>
)}
{archetypesError && (
<ApiErrorPanel
context="failed to load the archetype list"
problem={archetypesError}
/>
)}
<form class="create-agent-form" onSubmit={submit}>
<TextField
id="agent-name"
@ -256,6 +303,29 @@ export function CreateAgentForm({
: "select a hive"
}
/>
{archetypes && archetypes.archetypes.length > 0 && (
<>
<SelectField
id="agent-archetype"
label="archetype"
value={archetype}
onChange={pickArchetype}
disabled={!catalogue}
options={[
{ value: "", label: "none" },
...archetypes.archetypes.map((a) => ({
value: archetypeKey(a),
label: a.name,
})),
]}
/>
{picked && (
<p class="create-agent-archetype-description">
{picked.description}
</p>
)}
</>
)}
<FormField label="permissions" htmlFor="agent-permissions">
<div id="agent-permissions">
{catalogue ? (