Watch
0
0
Fork
You've already forked hyperhive
0

Set resource caps and inference when creating an agent

POST /api/agents takes optional limits and inference objects, checked by
the same rules as the two PUT routes before anything is queued and stored
ahead of the job graph. The create form gets collapsed sections for both,
reusing the fields of the detail-panel editors.
This commit is contained in:
iris 2026-10-15 11:32:48 +02:00 • committed by mara
commit e36a15de91
8 changed files with 389 additions and 100 deletions

View file

@ -48,6 +48,21 @@
align-items: flex-start;
gap: 0.75em;
}
/* Optional settings, collapsed so the plain name-and-hive form stays short. */
.create-agent-section {
align-self: stretch;
}
.create-agent-section > summary {
cursor: pointer;
color: var(--muted);
}
.create-agent-section-body {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.75em;
margin-top: 0.75em;
}
.create-agent-result {
margin-top: 1em;
}

View file

@ -36,6 +36,14 @@ import {
type SelectOption,
} from "../ui/select-field/SelectField.js";
import { Button } from "../ui/button/Button.js";
import { capsBody, LimitsFields } from "../agent/detail/AgentLimits.js";
import {
draftBody,
draftProblem,
EMPTY_INFERENCE_DRAFT,
InferenceFields,
type InferenceDraft,
} from "../agent/detail/AgentInference.js";
import "./CreateAgentForm.css";
// Mirrors swarm-controller's `HiveEntry` — same shape `HivesPage`
@ -88,6 +96,13 @@ export function CreateAgentForm({
const [hives, setHives] = useState<HiveEntry[] | null>(null);
const [hivesError, setHivesError] = useState<ProblemDetails | null>(null);
const [result, setResult] = useState<SubmitState>({ status: "idle" });
// The optional settings sections, left empty unless the operator fills them.
const [cpu, setCpu] = useState("");
const [memory, setMemory] = useState("");
const [inference, setInference] = useState<InferenceDraft>(
EMPTY_INFERENCE_DRAFT,
);
const inferenceProblem = draftProblem(inference);
// Loaded once, not re-fetched on submit: the roster changes rarely
// enough (a nix-level swarm config change) that staleness within one
@ -119,10 +134,19 @@ export function CreateAgentForm({
e.preventDefault();
setResult({ status: "submitting" });
try {
// A section left empty is left out, so the request is the plain
// name-and-hive one unless the operator set something.
const limits = capsBody(cpu, memory);
const settings = draftBody(inference);
const r = await fetch("/api/agents", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ name, hive }),
body: JSON.stringify({
name,
hive,
...(Object.keys(limits).length > 0 ? { limits } : {}),
...(Object.keys(settings).length > 0 ? { inference: settings } : {}),
}),
});
if (!r.ok) {
setResult({ status: "error", problem: await readApiError(r) });
@ -142,7 +166,9 @@ export function CreateAgentForm({
<Panel title="create agent" icon="🤖" onClose={onClose}>
<p class="create-agent-intro">
Create a new agent's swarm-level identity. This only queues the job
— check <Link href="/jobs">jobs</Link> to watch it settle.
— check <Link href="/jobs">jobs</Link> to watch it settle. Resource
caps and inference are optional; leave them closed to keep the
defaults.
</p>
{hivesError && (
<ApiErrorPanel
@ -176,10 +202,31 @@ export function CreateAgentForm({
: "select a hive"
}
/>
<details class="create-agent-section">
<summary>resource caps</summary>
<div class="create-agent-section-body">
<LimitsFields
cpu={cpu}
memory={memory}
onCpu={setCpu}
onMemory={setMemory}
/>
</div>
</details>
<details class="create-agent-section">
<summary>inference</summary>
<div class="create-agent-section-body">
<InferenceFields draft={inference} onChange={setInference} />
</div>
</details>
<Button
variant="primary"
type="submit"
disabled={result.status === "submitting" || !hivesReady}
disabled={
result.status === "submitting" ||
!hivesReady ||
inferenceProblem !== null
}
>
{result.status === "submitting" ? "creating…" : "create"}
</Button>