Watch
0
0
Fork
You've already forked hyperhive
0

docs: scheduled prompts are swarm-level; the hive page is a read-only list

Drops docs/tools/scheduling.md and every description of hive-level
schedule creation, the worker, the removed MCP tools, the scheduling tool
group and the removed endpoints/events, pointing at docs/swarm/schedules.md.
This commit is contained in:
atlas 2026-10-04 21:23:18 +02:00
commit b4a2aeb5ba
18 changed files with 90 additions and 346 deletions

View file

@ -31,8 +31,8 @@ hand-maintained per-file tree drifts out of sync with the code.
user). `src/main.rs` is the `hive-c0re` binary — **daemon-only** user). `src/main.rs` is the `hive-c0re` binary — **daemon-only**
(`serve` + the periodic vacuum/sweep loops); the operator CLI lives in (`serve` + the periodic vacuum/sweep loops); the operator CLI lives in
the separate `hivectl` crate, which talks to the daemon over the host the separate `hivectl` crate, which talks to the daemon over the host
admin socket. Owns the sqlite broker, approval + reminder + admin socket. Owns the sqlite broker, approval + reminder
schedule queues, the meta flake, lifecycle (`nixos-container` queues, the meta flake, lifecycle (`nixos-container`
shellouts), gateway / forge / matrix wiring (agent forge and matrix shellouts), gateway / forge / matrix wiring (agent forge and matrix
accounts are `swarm-controller`'s), per-container stats, and the axum accounts are `swarm-controller`'s), per-container stats, and the axum
operator dashboard (`dashboard/`). Largest crate. operator dashboard (`dashboard/`). Largest crate.

2
Cargo.lock generated
View file

@ -1769,7 +1769,6 @@ dependencies = [
"clap_complete", "clap_complete",
"forgejo-api", "forgejo-api",
"futures-util", "futures-util",
"hive-agent-sock",
"hive-core-agent-sock", "hive-core-agent-sock",
"hive-host-sock", "hive-host-sock",
"hive-jobq", "hive-jobq",
@ -1777,7 +1776,6 @@ dependencies = [
"hive-log", "hive-log",
"hive-priv-sock", "hive-priv-sock",
"hive-sh4re", "hive-sh4re",
"hive-sock-client",
"hive-types", "hive-types",
"hmac 0.13.0", "hmac 0.13.0",
"indicatif", "indicatif",

View file

@ -79,9 +79,9 @@ sudo inside the cage) and a durable, self-compacting session. Out of the
box it gets: box it gets:
- **messaging** — `send` / `recv` to other agents and the operator, - **messaging** — `send` / `recv` to other agents and the operator,
`remind` and recurring schedules to wake itself, and a todo list that `remind` to wake itself, scheduled prompts the operator sets for it at
forge notifications and other producers feed. → swarm level, and a todo list that forge notifications and other
[scheduling](docs/tools/scheduling.md) producers feed. → [schedules](docs/swarm/schedules.md)
- **background bash** — long-running commands as tracked tasks - **background bash** — long-running commands as tracked tasks
(`run` / `status` / `kill`), so a build doesn't block the turn. → (`run` / `status` / `kill`), so a build doesn't block the turn. →
[bash](docs/tools/bash.md) [bash](docs/tools/bash.md)

View file

@ -21,7 +21,7 @@ declarations.
go deeper into implementation. go deeper into implementation.
- **What tools does an agent (or the operator) have available?** → - **What tools does an agent (or the operator) have available?** →
[`tools/`](tools/README.md) — `hivectl` (yours) plus every agent's MCP [`tools/`](tools/README.md) — `hivectl` (yours) plus every agent's MCP
tool surface (bash, forge, lifecycle, matrix, scheduling). tool surface (bash, forge, lifecycle, matrix).
## Agent lifecycle ## Agent lifecycle

View file

@ -94,12 +94,8 @@ apply.
approval step — every other agent is created at swarm level approval step — every other agent is created at swarm level
(`swarmctl agent create`). (`swarmctl agent create`).
Roster-wise, `ruth` is just another entry. Roster-wise, `ruth` is just another entry.
- **Wire-protocol** — the `*(privileged)*` `Request` variants in - **Wire-protocol** — `hive-core-agent-sock`'s unified `Request` enum
`hive-core-agent-sock`'s unified enum are the scheduling ops is the same on both socket flavours. Container lifecycle
(`RequestSchedulePrompt`, `CancelSchedule`, `FireScheduleNow`,
`EditSchedule`), reachable only from the manager's socket flavour,
matching the `scheduling` tool group
([`docs/turn-loop/mcp.md`](../turn-loop/mcp.md)). Container lifecycle
ops (kill/start/restart/rebuild) go through the separate host-admin ops (kill/start/restart/rebuild) go through the separate host-admin
socket `hivectl` speaks. `Wake` (inject a `from: <X>` message straight socket `hivectl` speaks. `Wake` (inject a `from: <X>` message straight
into the caller's own inbox) is on this socket too, unprivileged on into the caller's own inbox) is on this socket too, unprivileged on

View file

@ -1,7 +1,8 @@
# Approvals + helper events # Approvals + helper events
The approval queue is where an agent asks the operator for something it The approval queue is where an agent asks the operator for something it
can't do itself: a scheduled prompt, or a meta-input bump. Config can't do itself. Nothing submits a new approval today: the one kind
left, a meta-input bump, only reads back from existing rows. Config
changes don't go through this queue: they're pull requests changes don't go through this queue: they're pull requests
on the agent's config repo, which an operator merges on the forge, and on the agent's config repo, which an operator merges on the forge, and
that merge deploys them. The submitting agent — any agent with the that merge deploys them. The submitting agent — any agent with the
@ -16,13 +17,6 @@ it stays informed about what happens after a decision lands.
config (or its own, via a sub-manager) as a pull request on config (or its own, via a sub-manager) as a pull request on
`agent-configs/<agent>`. Review it on the forge like any other PR, and `agent-configs/<agent>`. Review it on the forge like any other PR, and
merge it there to deploy it. See [Config changes](#config-changes). merge it there to deploy it. See [Config changes](#config-changes).
- **Scheduled prompt** (`SchedulePrompt`) — an agent asked to schedule
a message to one or more inboxes at a future time. It lands on your
dashboard's Y3R C4LL tab (or `hivectl approvals pending` /
`approve <id>` from the CLI). You can also add schedules yourself
directly from the SCH3DUL3S tab, which skips this approval step
entirely — the gate here is specifically for an *agent* asking to
schedule something, not for you doing it.
- **Meta/flake update** (`UpdateMetaInputs`) — an existing row reads - **Meta/flake update** (`UpdateMetaInputs`) — an existing row reads
back and you can approve it. Approving runs the update and commits back and you can approve it. Approving runs the update and commits
the lock change; it doesn't rebuild anything by itself. the lock change; it doesn't rebuild anything by itself.
@ -113,9 +107,8 @@ merged by an operator on the forge (see [Config changes](#config-changes)).
### Approval kinds (wire shapes) ### Approval kinds (wire shapes)
`ApprovalKind` carries two variants; each maps to a different `ApprovalKind` carries one variant, and it overloads `commit_ref` as
`commit_ref` encoding because `ApprovalKind` overloads that field as its payload carrier.
the kind-specific payload carrier.
<!-- vale write-good.Passive = NO --> <!-- vale write-good.Passive = NO -->
- `UpdateMetaInputs` — `commit_ref` stores the JSON-encoded inputs - `UpdateMetaInputs` — `commit_ref` stores the JSON-encoded inputs
@ -123,64 +116,8 @@ the kind-specific payload carrier.
etc.). hive-c0re sets the `agent` field to the requesting root agent. etc.). hive-c0re sets the `agent` field to the requesting root agent.
On approve hive-c0re runs `nix flake update [inputs...]` on the On approve hive-c0re runs `nix flake update [inputs...]` on the
meta flake and commits the resulting lock changes. meta flake and commits the resulting lock changes.
- `SchedulePrompt` — `commit_ref` stores the JSON-encoded
`SchedulePromptPayload` (target list, body, schedule) so the
approval row carries the full submission verbatim. On approve
hive-c0re inserts a row into `scheduled_prompts` with
`source = approval:<id>`; the worker fans the body out as
inbox messages to each target at the scheduled time, recurring
when `interval_seconds` is set.
<!-- vale write-good.Passive = YES --> <!-- vale write-good.Passive = YES -->
### Scheduled prompts (submit paths)
Two ways a row lands in `scheduled_prompts`:
- **Operator-direct** (`source = "operator"`): the operator adds a schedule through the dashboard form. Lands in the table immediately, no approval gate — operator action is already the trust boundary.
- **Agent-requested** (`source = "approval:<id>"`): an agent submits a `RequestSchedulePrompt` through its MCP socket (the `request_schedule_prompt` tool, `scheduling` group). hive-c0re queues an `ApprovalKind::SchedulePrompt` row; on approve, it inserts the schedule row with `source = approval:<id>` so the audit trail points back at the operator decision (above).
No self-target shortcut: even agent-self schedules need approval. The existing `remind` MCP tool stays the quick self-wake path (no approval, lands directly in the agent's own inbox); this module is the bigger, multi-recipient, operator-visible thing.
### Scheduled prompt worker (catch-up clamp)
When hive-c0re comes back from being down, the worker sees rows whose `next_fire_at_unix` is well in the past. For recurring rows that would mean firing N delayed pulses in a row — spammy and useless. Instead the worker fires **once** per row and bumps `next_fire_at_unix` to the next interval slot ≥ `now`, recording how many cycles it skipped in `last_result` (per-target). Operators see "fired late, caught up from 17 skipped" instead of 17 wake-up storms.
The worker fires one-shot rows once (if past due, on the next worker pass) and deletes them; recurring rows survive until cancelled.
`targets` is its own table (`scheduled_prompt_targets`) so partial cancellation flips a single row and the dashboard can show last-fired / last-result per recipient. Cancelling every target reaps the parent row on the next worker pass.
### Scheduled prompt delivery: todo, not a broker message
An agent target's delivery is `push_todo` (`Coordinator::push_todo`,
`docs/scheduler/coordinator.md` covers the mechanism generally), not a broker
`Message` — a scheduled prompt wakes its target with a todo instead of
driving an immediate turn, by design. `key = "schedule:<id>"` per
target drives `push_todo`'s own upsert-by-key dedup: a re-fire of the
*same schedule* against a target that hasn't reviewed the last one
collapses into that one todo instead of stacking up.
### Missing-target failure
When a target name doesn't resolve to a known agent (container
destroyed, typo, etc.) the worker:
1. Records `last_result = "no such agent: <name>"` on the
per-target row.
2. Sends a single advisory `Message` from `system` to `operator`
naming the schedule, target, and reason. This one stays a `Message`
regardless of target type — it's a to-operator advisory about a
broken schedule, not the schedule's own delivery.
3. Continues fanning out to the other live targets.
Transient broker errors (sqlite lock contention, etc.) get the same
`last_result` annotation plus a `tracing::warn`, and then:
- **Recurring rows** re-arm to the next interval slot — the retry
self-heals on the next worker pass.
- **One-shot rows**: the worker deletes them unconditionally after their single
fan-out pass; a broker error on a one-shot isn't retried (the
operator advisory and `last_result` are the only audit trail).
### Reminder delivery: file-path semantics ### Reminder delivery: file-path semantics
A reminder may carry a `file_path` (the agent-visible path inside its A reminder may carry a `file_path` (the agent-visible path inside its
@ -324,7 +261,6 @@ by the approval handler rather than run inline:
| `ApprovalKind` | DAG submitted | source | | `ApprovalKind` | DAG submitted | source |
|---|---|---| |---|---|---|
| `UpdateMetaInputs` | `meta_update` (`MetaLock` + rebuild fan-out) | `approval` | | `UpdateMetaInputs` | `meta_update` (`MetaLock` + rebuild fan-out) | `approval` |
| `SchedulePrompt` | — runs inline (single sqlite insert) | — |
The DAG carries the originating `approval_id`. **Every** queued kind The DAG carries the originating `approval_id`. **Every** queued kind
resolves through `actions::resolve_approval_dag` when its DAG settles resolves through `actions::resolve_approval_dag` when its DAG settles
@ -508,10 +444,8 @@ the `submitter` column on the `approvals` table). The harness delivers it
as a regular `system` inbox message so it drives a normal claude turn. as a regular `system` inbox message so it drives a normal claude turn.
`finish_approval` fires an `ApprovalResolved` HelperEvent this way for `finish_approval` fires an `ApprovalResolved` HelperEvent this way for
**every** approval kind's terminal state. A **every** approval kind's terminal state. A
"FYI, check when convenient" event doesn't need a message — those go "FYI, check when convenient" event doesn't need a message — hive-c0re publishes those as swarm
through `Coordinator::push_todo` instead, a direct live dial of the target lifecycle notices instead (`swarm_notices::notify`). Legacy
agent's in-container todo socket (same `UpsertTodo` request in-container
producers use). Legacy
approval rows that predate the submitter column fall back to the approval rows that predate the submitter column fall back to the
root agent. Variants (`hive_sh4re::manager::HelperEvent`): root agent. Variants (`hive_sh4re::manager::HelperEvent`):
@ -533,9 +467,8 @@ root agent. Variants (`hive_sh4re::manager::HelperEvent`):
The remaining lower-urgency lifecycle notices — `Rebuilt`, `Killed`, The remaining lower-urgency lifecycle notices — `Rebuilt`, `Killed`,
`Destroyed`, `NeedsLogin`, `LoggedIn` — are "FYI, check `Destroyed`, `NeedsLogin`, `LoggedIn` — are "FYI, check
when convenient" events with no reason to drive an immediate turn, so when convenient" events with no reason to drive an immediate turn, so
they deliver via `push_todo` (see above) instead they go out via `swarm_notices::notify` (see above) instead
of `HelperEvent`: an `agent_todo_socket` push instead of a broker of a `HelperEvent`: `subsystem = "core"`, `key = "<event>:<agent>"`,
message, `subsystem = "core"`, `key = "<event>:<agent>"` for dedup,
and a single free-text `summary` (`rebuilt_todo_summary` renders and a single free-text `summary` (`rebuilt_todo_summary` renders
`Rebuilt`'s `ok`/`note`/`sha`/`tag` fields into that string). `Rebuilt`'s `ok`/`note`/`sha`/`tag` fields into that string).
@ -543,7 +476,7 @@ To add a new lifecycle notice: if it needs to drive an immediate turn
(something genuinely urgent, like `ContainerCrash`), add a (something genuinely urgent, like `ContainerCrash`), add a
`HelperEvent` variant + call sites + update `prompts/system.md`'s `HelperEvent` variant + call sites + update `prompts/system.md`'s
message-event list. If it's "FYI, check when convenient," call message-event list. If it's "FYI, check when convenient," call
`push_todo` directly instead — no new wire type `swarm_notices::notify` instead — no new wire type
needed. needed.
## Autoupdate on startup ## Autoupdate on startup

View file

@ -40,8 +40,8 @@ schemas, file layouts, and internal migration mechanics.
### `/var/lib/hyperhive/db/broker.sqlite` (host) ### `/var/lib/hyperhive/db/broker.sqlite` (host)
Seven tables, all in one file — three queues, a small key/value Six tables, all in one file — two queues, a small key/value table,
table, the schedule header/targets split, and the per-agent the read-only schedule header/targets split, and the per-agent
power-intent registry: power-intent registry:
- `messages` — every inter-agent / operator-bound message. - `messages` — every inter-agent / operator-bound message.
@ -56,21 +56,19 @@ power-intent registry:
per-agent store — see [`/harness/` contents per-agent store — see [`/harness/` contents
below](#state-dirs-per-agent) for where reminders (and todos) below](#state-dirs-per-agent) for where reminders (and todos)
live. live.
- `approvals` — the queue. `agent / kind (update_meta_inputs | - `approvals` — the queue. `agent / kind (update_meta_inputs) /
schedule_prompt) /
commit_ref / requested_at / status / resolved_at / note`. commit_ref / requested_at / status / resolved_at / note`.
- `scheduled_prompts` — recurring + one-shot prompt queue. - `scheduled_prompts` — the scheduled prompts left from before they
moved to swarm level ([`swarm/schedules.md`](../swarm/schedules.md)).
`owner / body / interval_seconds (NULL = one-shot) / `owner / body / interval_seconds (NULL = one-shot) /
next_fire_at_unix / created_at_unix / source ("operator" or next_fire_at_unix / created_at_unix / source ("operator" or
"approval:<id>") / cancelled_at_unix / description`. `owner` "approval:<id>") / cancelled_at_unix / description / paused_at_unix`.
drives cancel-permission checks (operator vs the submitting Read-only: the dashboard's SCH3DUL3S tab lists them so the operator
agent). The worker tombstones and reaps cancelled rows can recreate them with `swarmctl schedule`; nothing writes or fires
on its next pass. them.
- `scheduled_prompt_targets` — per-target state for each schedule. - `scheduled_prompt_targets` — per-target state for each schedule.
`schedule_id / target / cancelled_at_unix / `schedule_id / target / cancelled_at_unix /
last_fired_at_unix / last_result`. `ON DELETE CASCADE` from last_fired_at_unix / last_result`.
`scheduled_prompts(id)` — requires `PRAGMA foreign_keys = ON`
per connection (set at open).
- `agent_power` — one row per agent: `agent PK / wanted (up | - `agent_power` — one row per agent: `agent PK / wanted (up |
offline) / updated_at`, owned by `hive-c0re/src/stores/power.rs`. offline) / updated_at`, owned by `hive-c0re/src/stores/power.rs`.
This is the durable power *intent* the job queue reconciles the This is the durable power *intent* the job queue reconciles the
@ -90,11 +88,7 @@ Retention:
an unacked row on a crash. an unacked row on a crash.
- hive-c0re keeps approvals indefinitely — an audit trail. `actions::destroy` - hive-c0re keeps approvals indefinitely — an audit trail. `actions::destroy`
rows stay visible to anything that queries by id. rows stay visible to anything that queries by id.
- Scheduled prompts: the worker deletes one-shot rows on fire; - Scheduled prompts: nothing deletes them; the rows stay as they are.
recurring rows live until the operator cancels them
(`cancel_schedule` MCP / dashboard ✗) which tombstones via
`cancelled_at_unix`, then `reap_cancelled` drops the row on
the next worker pass.
- `agent_power` rows live until the operator destroys the agent (one row per - `agent_power` rows live until the operator destroys the agent (one row per
agent — nothing to vacuum). agent — nothing to vacuum).

View file

@ -254,8 +254,8 @@ the wire structs in hive-sh4re) serialize as **RFC 3339 UTC strings**
fields as `i64` unix seconds internally, only the JSON representation fields as `i64` unix seconds internally, only the JSON representation
changes, and deserialization accepts both the string form and a bare changes, and deserialization accepts both the string form and a bare
integer (rolling-deploy skew, persisted blobs). integer (rolling-deploy skew, persisted blobs).
**Input-direction** fields agents compute as epoch (`first_fire_at_unix`, **Input-direction** fields agents compute as epoch (`remind`'s
schedule-edit `next_fire_at_unix`, `Wakeup::At`) stay integers. The `at_unix_timestamp`) stay integers. The
`*_unix` field *names* stay for now — renaming is the wire-types `*_unix` field *names* stay for now — renaming is the wire-types
refactor's concern. The dashboard frontend parses via refactor's concern. The dashboard frontend parses via
`util.js::epochSec` wherever it needs arithmetic and feeds the string `util.js::epochSec` wherever it needs arithmetic and feeds the string
@ -297,7 +297,6 @@ binary flavor.
| `execution` | vestigial — `mcp__bash__run` / `mcp__bash__status` are always available unconditionally via `extraMcpServers`; this group's entries expand to non-existent `mcp__hyperhive__run` / `mcp__hyperhive__status` and have no effect. See `docs/tools/bash.md`. | | `execution` | vestigial — `mcp__bash__run` / `mcp__bash__status` are always available unconditionally via `extraMcpServers`; this group's entries expand to non-existent `mcp__hyperhive__run` / `mcp__hyperhive__status` and have no effect. See `docs/tools/bash.md`. |
| `lifecycle` | none | | `lifecycle` | none |
| `approvals` | none — gates `cancel_loose_end`'s approval-cancel arm server-side. | | `approvals` | none — gates `cancel_loose_end`'s approval-cancel arm server-side. |
| `scheduling` | `request_schedule_prompt`, `fire_schedule_now`, `cancel_schedule`, `edit_schedule`, `list_schedules` *(privileged)* |
| `forge` | none | | `forge` | none |
| `web_tools` | none (gates the Claude built-ins `WebFetch`/`WebSearch`, not an MCP tool) | | `web_tools` | none (gates the Claude built-ins `WebFetch`/`WebSearch`, not an MCP tool) |
@ -375,8 +374,7 @@ bypass the operator approval gate.
`hive-sh4re/src/permissions.rs` + an arm to `as_str`. Add it to `Capability::ALL` `hive-sh4re/src/permissions.rs` + an arm to `as_str`. Add it to `Capability::ALL`
(the source of truth for the permissions UI columns). Implement the access (the source of truth for the permissions UI columns). Implement the access
check in the relevant handler (`hive-c0re/src/socket_server/mod.rs`, check in the relevant handler (`hive-c0re/src/socket_server/mod.rs`,
`hive-c0re/src/socket_server/schedules.rs`, `coordinator.rs`, or a `coordinator.rs`, or a handler under `hive-c0re/src/dashboard/`).
handler under `hive-c0re/src/dashboard/`).
## Async forms ## Async forms

View file

@ -521,7 +521,7 @@ which is almost never desired. Leave off on non-x86 hosts.
## See also ## See also
- `docs/agent-lifecycle/approvals.md` — approval flow + scheduled prompts - `docs/agent-lifecycle/approvals.md` — approval flow
- `docs/agent-lifecycle/persistence.md` — SQLite schema, state-dir layout - `docs/agent-lifecycle/persistence.md` — SQLite schema, state-dir layout
- `docs/process/conventions.md` — wire protocol, recipient sentinels - `docs/process/conventions.md` — wire protocol, recipient sentinels
- `docs/agent-lifecycle/agent-roster.md` — the agent roster and privilege - `docs/agent-lifecycle/agent-roster.md` — the agent roster and privilege

View file

@ -64,11 +64,11 @@ works them out from the prompt itself, so:
watches the paused prompt; if the agent was down for the whole pause, it watches the paused prompt; if the agent was down for the whole pause, it
fires the latest time it missed once, as above. fires the latest time it missed once, as above.
A fire reaches the agent as a `schedule` loose end with the prompt's text, A fire reaches the agent as a `schedule` loose end with the prompt's text.
the same as a hive schedule's. The agent's turn loop wakes for it like any The agent's turn loop wakes for it like any other loose end. If the agent
other loose end. If the agent hasn't dealt with the previous fire yet, the hasn't dealt with the previous fire yet, the new fire replaces it rather
new fire replaces it rather than adding a second one; once the agent than adding a second one; once the agent dismissed the previous one, the
dismissed the previous one, the next fire opens a new one. next fire opens a new one.
Each agent reads its prompts every 10 seconds, so a fire can land up to 10 Each agent reads its prompts every 10 seconds, so a fire can land up to 10
seconds after its time. A change reaches each agent within the same window, seconds after its time. A change reaches each agent within the same window,

View file

@ -43,5 +43,3 @@ debug agent behavior.
- **[matrix](matrix.md)** — the matrix MCP tool surface - **[matrix](matrix.md)** — the matrix MCP tool surface
(`mcp__matrix__*`) for agents with a matrix account, multiple (`mcp__matrix__*`) for agents with a matrix account, multiple
accounts per agent, and declaring extra MCP servers generally. accounts per agent, and declaring extra MCP servers generally.
- **[scheduling](scheduling.md)** — scheduled prompts (operator
approval required).

View file

@ -1,74 +0,0 @@
# Scheduling and diagnostics tools
## `scheduling` tool group
Scheduled prompts fan a message body out to one or more agent inboxes
at a future time, optionally recurring.
**Creating** one goes through the operator approval queue, even when it
targets only yourself — use `remind` for an unapproved self-wake. The
other four verbs need no approval: holding the `scheduling` tool group
is the whole gate.
Authorization is one rule for all four verbs: you reach every schedule in
the hive, whoever owns it. `list_schedules` shows the same set, so the
snapshot only ever shows schedules you could also cancel.
### `request_schedule_prompt(targets, body, first_fire_at_unix, interval_seconds?, description?)`
<!-- vale write-good.Passive = NO -->
Queue an operator-approval for a scheduled prompt. On approve,
hive-c0re fans `body` out to each agent in `targets` at
`first_fire_at_unix` (Unix timestamp). Recurring when `interval_seconds`
is set, one-shot otherwise.
<!-- vale write-good.Passive = YES -->
Catch-up clamp: if hive-c0re is down across multiple intervals, only
ONE delayed fire happens on resume (per recurring schedule). The
skipped-cycle count surfaces in the per-target `last_result` for
audit.
### `edit_schedule(id, body?, description?, interval_seconds?, next_fire_at_unix?, targets_add?, targets_remove?)`
Partial-update a schedule. Pass only the fields to change; absent
fields stay untouched. `targets_add` / `targets_remove` mutate the
recipient list in the same transaction — re-adding a previously
cancelled target drops its tombstone and starts fresh.
`interval_seconds` accepts positive values only via this tool (omit
to keep the existing cadence; pass a new positive value to change
it). Toggling recurring → one-shot (clearing the interval) is
operator-only via the dashboard PATCH endpoint. Refuses cancelled
rows (terminal state).
### `cancel_schedule(id, targets?)`
<!-- vale write-good.Passive = NO -->
Cancel a schedule. Omit `targets` / pass empty to cancel the whole
schedule; pass a list to cancel just those recipients (the schedule
autocancels once every target is gone).
<!-- vale write-good.Passive = YES -->
### `fire_schedule_now(id)`
Fire a scheduled prompt out of band immediately. Recurring schedules
keep their cadence — the manual fire is additive. The manual fire
consumes one-shot schedules and cancels them afterwards.
### `list_schedules()`
Snapshot every schedule in the hive (active, and cancelled but not yet
reaped) — same read scope as the rest of this group. Returns id, owner,
body, per-target `last_fired_at` and `last_result`,
`next_fire_at_unix`, `interval_seconds`. Use to look up an id before
cancelling, or to audit upcoming wake-ups across the hive.
## See also
- `remind` (no-approval self-wake path) — documented in
[`docs/turn-loop/`](../turn-loop/README.md).
- [`docs/agent-lifecycle/approvals.md`](../agent-lifecycle/approvals.md) — approval flow for
`request_schedule_prompt`.

View file

@ -131,10 +131,10 @@ checkpoints**, not about sandboxing the agent from its own tools:
settings nor merge its own PR. It's **not** set up for external VCS (GitHub settings nor merge its own PR. It's **not** set up for external VCS (GitHub
etc.), though — there, operator-merge is process + accepted risk, not a etc.), though — there, operator-merge is process + accepted risk, not a
technical control. technical control.
- **Approvals** — schedule additions and other blast-radius-y operations - **Approvals** — config changes are config PRs an operator merges on the
route through the operator approval queue; config changes are config PRs forge (see [`approvals.md`](../agent-lifecycle/approvals.md)), and
an operator merges on the forge scheduled prompts are operator-only, set at swarm level
(see [`approvals.md`](../agent-lifecycle/approvals.md)). (see [`schedules.md`](../swarm/schedules.md)).
### Capability = accepted risk ### Capability = accepted risk
@ -327,6 +327,6 @@ writes to only the paths `hive-priv` legitimately needs.
| Component | Runs as | Privilege needed for | | Component | Runs as | Privilege needed for |
| ---------------------------- | -------------- | ------------------------------------------------------------ | | ---------------------------- | -------------- | ------------------------------------------------------------ |
| `hive-c0re` | `hive-core` | broker, HTTP dashboard, scheduling, approvals | | `hive-c0re` | `hive-core` | broker, HTTP dashboard, approvals |
| `hive-priv` | `root` | container lifecycle, journal reads, bind mounts, cred writes | | `hive-priv` | `root` | container lifecycle, journal reads, bind mounts, cred writes |
| `hive-ag3nt` (per-container) | per-agent user | turn execution, MCP serving | | `hive-ag3nt` (per-container) | per-agent user | turn execution, MCP serving |

View file

@ -24,7 +24,7 @@ for what reaches a subagent's own `--mcp-config` and what doesn't.
Tool groups (`HIVE_TOOL_GROUPS`) gate tool access. The default Tool groups (`HIVE_TOOL_GROUPS`) gate tool access. The default
preset (`AGENT_DEFAULT`) includes `messaging`, `meta`, `inbox`, and preset (`AGENT_DEFAULT`) includes `messaging`, `meta`, `inbox`, and
`execution`. Privileged groups (`lifecycle`, `approvals`, `scheduling`) `execution`. Privileged groups (`lifecycle`, `approvals`)
are opt-in via the P3RM1SS10NS tab. are opt-in via the P3RM1SS10NS tab.
## Core tools (always available) ## Core tools (always available)
@ -149,8 +149,6 @@ hive_name?, swarm_name?, matrix_accounts? }`. `matrix_accounts` is a
`lifecycle` gates nothing. `lifecycle` gates nothing.
Config changes go through a forge PR on `agent-configs/<name>` — see Config changes go through a forge PR on `agent-configs/<name>` — see
[`docs/agent-lifecycle/approvals.md`](../agent-lifecycle/approvals.md). [`docs/agent-lifecycle/approvals.md`](../agent-lifecycle/approvals.md).
- **Scheduling** (`scheduling`) — scheduled prompts. See
[`docs/tools/scheduling.md`](../tools/scheduling.md).
- **Forge repos** (`forge`) — gates nothing. - **Forge repos** (`forge`) — gates nothing.
- **Web egress** (`web_tools`) — enables Claude's built-in `WebFetch` - **Web egress** (`web_tools`) — enables Claude's built-in `WebFetch`
and `WebSearch` tools (not MCP tools; added directly to the and `WebSearch` tools (not MCP tools; added directly to the

View file

@ -23,7 +23,8 @@ of your time. It's a single page with exactly four tabs:
- **Y3R C4LL** — anything waiting on _you_: pending approvals. If an - **Y3R C4LL** — anything waiting on _you_: pending approvals. If an
agent needs a decision from you, it's here. agent needs a decision from you, it's here.
- **P3RM1SS10NS** — what tools and system-level access each agent has. - **P3RM1SS10NS** — what tools and system-level access each agent has.
- **SCH3DUL3S** — scheduled prompts and agent self-reminders. - **SCH3DUL3S** — a read-only list of the scheduled prompts left in the
hive, due for removal; operators set schedules at swarm level now.
Everything else lives on its own page instead, all reachable from the Everything else lives on its own page instead, all reachable from the
H0M3 hub: **Flow** (`/flow.html`, the raw live message stream across H0M3 hub: **Flow** (`/flow.html`, the raw live message stream across
@ -48,9 +49,8 @@ and quick links (stats, screen, forge profile). Select the name to open
its terminal and watch it work in real time. its terminal and watch it work in real time.
**Approve something an agent is waiting on.** Y3R C4LL is the one tab **Approve something an agent is waiting on.** Y3R C4LL is the one tab
worth checking regularly — it's everything that needs _you_: an agent's worth checking regularly — it's everything that needs _you_. The tab's
request to schedule a prompt. The tab's count pill tells you at a glance if count pill tells you at a glance if anything's pending.
anything's pending.
**Review a config change.** Agent config changes (new packages, env **Review a config change.** Agent config changes (new packages, env
vars, MCP servers) are pull requests on the agent's `agent-configs` vars, MCP servers) are pull requests on the agent's `agent-configs`
@ -76,9 +76,10 @@ save queues a rebuild for whichever agents actually changed.
journald view for any agent + service; the per-agent `⋮` menu's journald view for any agent + service; the per-agent `⋮` menu's
**journal logs →** link jumps straight there, pre-filtered. **journal logs →** link jumps straight there, pre-filtered.
**Set up a schedule or check on a reminder.** SCH3DUL3S covers both — **Set up a schedule.** Scheduled prompts are swarm-level: set them with
recurring or one-shot prompts you schedule for one or more agents, and `swarmctl schedule` ([`swarm/schedules.md`](../swarm/schedules.md)).
reminders agents have set for themselves. SCH3DUL3S only lists the ones still in the hive, so you can copy them
over.
## More depth ## More depth

View file

@ -25,7 +25,7 @@ from the dashboard tab strip.
live on their own pages too, all reachable from the live on their own pages too, all reachable from the
H0M3 hub (not the tab strip). Count pills on SW4RM H0M3 hub (not the tab strip). Count pills on SW4RM
(container count), Y3R C4LL (pending approvals + unread (container count), Y3R C4LL (pending approvals + unread
operator messages), and SCH3DUL3S (active schedules). operator messages), and SCH3DUL3S (active schedules left to copy).
- **Banner-thin** (`░▒▓█▓▒░ HYPERHIVE / HIVE-C0RE / WE ARE THE WIRED ░▒▓█▓▒░`) - **Banner-thin** (`░▒▓█▓▒░ HYPERHIVE / HIVE-C0RE / WE ARE THE WIRED ░▒▓█▓▒░`)
— sits below the tab strip. — sits below the tab strip.
- **Server-warnings banner** — a generic, sticky top-of-page strip shown - **Server-warnings banner** — a generic, sticky top-of-page strip shown
@ -50,7 +50,7 @@ from the dashboard tab strip.
page prepends a `(N)` prefix — `(3) pr1ma // h1ve-c0re` — so the page prepends a `(N)` prefix — `(3) pr1ma // h1ve-c0re` — so the
operator can see the call count in an unfocused browser tab without operator can see the call count in an unfocused browser tab without
opening the dashboard. The page sets the prefix on the initial opening the dashboard. The page sets the prefix on the initial
`/api/state` cold-load and updates it live via `approval_added` / `/api/state` cold-load and updates it live via
`approval_resolved` SSE events; it's preserved when `hive_name` / `approval_resolved` SSE events; it's preserved when `hive_name` /
`swarm_name` later replace the raw title. `swarm_name` later replace the raw title.
@ -332,7 +332,7 @@ named buckets of MCP tools; each agent starts with a role default
(sub-agents: `messaging`, `meta`, `inbox`, `execution` — (sub-agents: `messaging`, `meta`, `inbox`, `execution` —
`ToolGroup::AGENT_DEFAULT`; hive-c0re seeds the root agent to `ToolGroup::AGENT_DEFAULT`; hive-c0re seeds the root agent to
`ToolGroup::MANAGER_DEFAULT` — `messaging`, `meta`, `inbox`, `ToolGroup::MANAGER_DEFAULT` — `messaging`, `meta`, `inbox`,
`lifecycle`, `approvals`, `scheduling`, `execution`, `lifecycle`, `approvals`, `execution`,
that is, every group except `forge` and `web_tools`). Checking / that is, every group except `forge` and `web_tools`). Checking /
unchecking stages which groups are active for the agent; the unchecking stages which groups are active for the agent; the
page-level **save all** button (below) commits it. Columns come from page-level **save all** button (below) commits it. Columns come from
@ -341,7 +341,7 @@ takes effect.
<!-- vale write-good.Passive = YES --> <!-- vale write-good.Passive = YES -->
The current tool groups are: `messaging`, `meta`, `inbox`, `lifecycle`, The current tool groups are: `messaging`, `meta`, `inbox`, `lifecycle`,
`approvals`, `scheduling`, `forge`, `execution`, `approvals`, `forge`, `execution`,
`web_tools`. All listed in `ToolGroup::ALL` in `hive-sh4re`. The `web_tools`. All listed in `ToolGroup::ALL` in `hive-sh4re`. The
`web_tools` group is special: it carries no MCP tools; instead it adds `web_tools` group is special: it carries no MCP tools; instead it adds
Claude's built-in `WebFetch` and `WebSearch` to `--tools` / Claude's built-in `WebFetch` and `WebSearch` to `--tools` /
@ -374,83 +374,31 @@ isn't clobbered.
## SCH3DUL3S tab ## SCH3DUL3S tab
Anything that fires at a future time. The operator creates schedules A read-only list of the scheduled prompts still in the hive's store.
inline in the table (last row); agent self-paced The hive no longer fires them: scheduled prompts live at swarm level
reminders surface at the bottom as a sibling list — they ([`swarm/schedules.md`](../swarm/schedules.md)), and a warning banner at
share enough conceptual ground to live together. the top of the tab says the page is due for removal and points at
`swarmctl schedule`. The list is there so the operator can recreate each
row with `swarmctl schedule add`.
**N3W SCH3DUL3 / QU3U3D SCH3DUL3S** — operator-managed **Single-table layout**: each schedule is one `<tr>`; columns are
scheduled prompts. **Single-table layout**: each schedule is `# | src | next | every | owner | body | …agents…`. Agent columns are
one `<tr>`; columns are dynamic — every live container plus any extra name that appears as a
`# | src | next | every | owner | body | …agents… | actions`. target on some schedule but isn't a current container (struck through
Agent columns are dynamic — `operator` + `root` + every as gone). Column headers render vertically so each column reads as a
live container + any extra name that appears as a target on narrow ~28px strip; per-agent cells show `✓` for an active target, a
some schedule but isn't a current container (same muted `✕` for a cancelled one, and nothing when the agent isn't a
`buildTargetChips` membership rule the new/edit forms use, target. Hovering a target cell shows its last fire and result.
so table and forms agree on what's addressable). Column
headers tilt –45° via CSS so each column reads as a narrow
~28px strip; per-agent cells render as:
- **active target** → `<button>✓</button>` that cancels just The `next` column shows the stored next fire time as an absolute UTC
that one target on selection timestamp, the value to pass as `--at`; paused and cancelled rows say
- **cancelled target** → muted `✕` glyph (no button — so instead, with the time in the cell's tooltip. The body cell
re-adding goes through the edit form's targets multi-select) truncates; hovering shows the full body and description.
- **not a target** → empty cell
Per-schedule action column: The tab has no controls: nothing creates, edits, pauses, fires or
cancels a row. The tab pill shows the count of active schedules (at least one
- **`↯ fire now`** — sends an out-of-band manual pulse to every live target not yet cancelled). Fetched from `GET /api/schedules` on
active target (recurring schedules keep their cadence; the manual tab activation and on each dashboard state load.
fire consumes one-shots). Disabled for paused schedules.
- **`⏸` / `▶` pause/resume toggle** — only on recurring schedules
(the operator may also pause one-shots to delay a one-time fire, so
the button always appears). Paused rows show a `⏸ paused` badge in
the `next` column; the table sorts them to the middle bucket (after
active, before cancelled). A paused schedule doesn't fire regardless of
`next_fire_at`; resuming (`▶`) clears the pause without adjusting
the next-fire time. POSTs to `/api/schedules/{id}/pause` or
`/api/schedules/{id}/resume`.
- **`✎ edit`** — expands an inline edit form as a colspan'd row
directly under the schedule's row (body / description / interval /
next-fire / targets all editable; targets are a multi-select diff'd
against the original active set so unchecked-was-active =
`targets_remove`, checked-not-originally-active = `targets_add`;
submit PATCHes `/api/schedules/{id}`).
- **`✕`** — cancels the whole schedule
(`POST /api/schedules/{id}/cancel`).
The `next` column cell (`.sched-due`) carries a `data-due-at`
Unix timestamp attribute; a shared 1s ticker rewrites it
in-place showing `fmtDuration` while in the future and
`overdue X ago` once the fire time has passed — same
zero-re-render pattern as the reminder due-at labels.
The table's last row is a permanent inline creation row:
inputs live directly in table cells (targets as checkboxes,
body textarea that expands on focus, datetime-local pre-filled
to 5 minutes from now, mini d/h/m/s number inputs (blank or
all-zero = one-shot), description). Select `+` to POST to
`/api/schedules` as JSON (or `⌫` to clear the half-filled row);
carry-state preserves partially typed inputs across re-renders. The tab pill shows the count of active
schedules (at least one live target not yet cancelled).
Refreshed on tab activation and after each submit/cancel. Backed by
`GET /api/schedules`. No hive-c0re changes for the table layout
— it renders entirely from existing `schedulesState` +
`containersState`.
**QU3U3D R3M1ND3RS** — reminders agents have scheduled for
themselves (via the `remind` tool) but not yet delivered.
Each row shows the owner, due time, and message; a `CANC3L`
button hard-deletes (`POST /api/cancel-reminder/{id}`) and a
`R3TRY` button re-arms one whose delivery failed
(`POST /api/retry-reminder/{id}`). Backed by `GET /api/reminders`.
Lives in the SCH3DUL3S tab alongside operator schedules so the
operator has one place for everything time-fired. The due-time
label (`.reminder-due`) carries a `data-due-at` Unix timestamp
attribute; a shared 1s ticker rewrites it in-place — showing
`in Xm Ys` while the reminder is in the future and
`overdue X ago` once the deadline passes — without triggering a
full re-render of the list.
## ST4TS page (`/stats.html`) ## ST4TS page (`/stats.html`)
@ -880,7 +828,6 @@ renderApprovals`) with three stacked sections:
| kind | glyph | chip | | kind | glyph | chip |
|---|---|---| |---|---|---|
| `update_meta_inputs` | `↻` | `meta-update` | | `update_meta_inputs` | `↻` | `meta-update` |
| `schedule_prompt` | `⏱` | `schedule` |
<!-- vale write-good.Passive = NO --> <!-- vale write-good.Passive = NO -->
The chip ticks live every second via a `data-requested-at` The chip ticks live every second via a `data-requested-at`
@ -889,8 +836,7 @@ renderApprovals`) with three stacked sections:
the `.stale` class flips precisely at the 3600s boundary rather the `.stale` class flips precisely at the 3600s boundary rather
than at the next `renderApprovals` call. than at the next `renderApprovals` call.
- **what-changed body** — the submitting agent's description, then the - **what-changed body** — the submitting agent's description, then the
kind's payload: the inputs to bump (`update_meta_inputs`) or the prompt to kind's payload: the inputs to bump (`update_meta_inputs`).
schedule (`schedule_prompt`).
- **decision actions** — `◆ APPR0VE` and `DENY`. Deny pops a - **decision actions** — `◆ APPR0VE` and `DENY`. Deny pops a
`prompt()` for an optional reason carried to the submitting agent as `prompt()` for an optional reason carried to the submitting agent as
`HelperEvent::ApprovalResolved.note`. `HelperEvent::ApprovalResolved.note`.
@ -1075,42 +1021,9 @@ below — some endpoints aren't in it yet.
rows with both fields omitted rather than erroring on them. rows with both fields omitted rather than erroring on them.
Returns `200 "ok"` on Returns `200 "ok"` on
success. success.
- `GET /api/schedules` — list all schedules (active and - `GET /api/schedules` — list every schedule row left in the hive's
recently cancelled) for the SCH3DUL3S scheduled-prompts panel. store (cancelled ones included) for the read-only SCH3DUL3S tab.
- `POST /api/schedules` — operator-direct schedule create: Targets naming an agent that no longer exists are left out.
`{ targets, body, first_fire_at_unix, interval_seconds?, description? }`.
Agent-initiated schedules go through the approval queue instead
(via the `request_schedule_prompt` MCP tool).
- `PATCH /api/schedules/{id}` — partial edit. JSON body
`{ body?, description?, interval_seconds?, next_fire_at_unix?,
targets_add?, targets_remove? }`.
Missing key = "leave alone"; explicit `null` on
`description` / `interval_seconds` clears the field (so a
recurring schedule flips to one-shot when the caller sends
`interval_seconds` as `null`). `targets_add` is replace-on-conflict:
re-adding a previously cancelled target drops the tombstone
and the target starts fresh (operator intent on re-add =
"this target is active again"). `targets_remove` delegates
to the same path as `cancel_targets` — tombstones preserve
audit, parent schedule autocancels when no active targets
remain. Refuses cancelled rows; returns the updated
`WireSchedule` on success.
- `POST /api/schedules/{id}/cancel` — cancel a schedule. Body
`{ targets?: ["name", …] }` cancels just those recipients;
absent or empty body cancels the whole schedule.
- `POST /api/schedules/{id}/fire-now` — out-of-band manual
pulse. Fires the schedule body once immediately to
every active target. Recurring schedules: `next_fire_at_unix`
is untouched; the regular cadence continues. One-shots: the
manual fan-out consumes the schedule (cancels it).
The endpoint annotates per-target `last_result` as a manual fire so
the audit trail distinguishes scheduled fires from operator-
triggered ones.
- `POST /api/schedules/{id}/pause` — pause a recurring or
one-shot schedule. A paused schedule doesn't fire regardless
of `next_fire_at`; the row gains a `⏸ paused` badge.
- `POST /api/schedules/{id}/resume` — resume a paused schedule.
Clears the pause flag without adjusting `next_fire_at`.
- `POST /api/meta-update` — `nix flake update` the selected - `POST /api/meta-update` — `nix flake update` the selected
`meta/flake.lock` inputs, then rebuild the affected agents. `meta/flake.lock` inputs, then rebuild the affected agents.
- `GET /api/dashboard/stream` — unified live event channel: - `GET /api/dashboard/stream` — unified live event channel:
@ -1133,10 +1046,9 @@ payload):
rows with a `↳ reply` tag that scroll-highlights the parent rows with a `↳ reply` tag that scroll-highlights the parent
row on selection. Used by the message-flow terminal renderer and row on selection. Used by the message-flow terminal renderer and
the operator-inbox derived state. the operator-inbox derived state.
- `approval_added` (id, agent, approval_kind, sha_short, diff, - `approval_resolved` (id, agent, approval_kind, status,
description) / `approval_resolved` (id, agent, approval_kind, resolved_at, note, description) — moves a row from the pending
sha_short, status, resolved_at, note, description) — pending queue to history. Client mutates a derived store and
queue + history mutations. Client mutates a derived store and
re-renders only the approvals section. re-renders only the approvals section.
- `transient_set` (name, transient_kind, since_unix) / - `transient_set` (name, transient_kind, since_unix) /
`transient_cleared` (name) — lifecycle action spinners. The `transient_cleared` (name) — lifecycle action spinners. The
@ -1158,16 +1070,6 @@ payload):
terminal-history trim). Unlike the snapshot events below it ships terminal-history trim). Unlike the snapshot events below it ships
no state at all: the client re-fetches `GET /api/jobq/graph`, which no state at all: the client re-fetches `GET /api/jobq/graph`, which
is where it reads the queue from cold too. is where it reads the queue from cold too.
- `schedules_changed` (seq, schedules: `Vec<WireSchedule>`) —
full snapshot of all scheduled prompts. Emitted after every
operator mutation via the `/api/schedules` surface (new /
edit / cancel / fire-now) and after the worker fires or
rearms a row. Same snapshot-shape rationale as
`tombstones_changed` / `meta_inputs_changed` (small list, no
add/remove races). The SCH3DUL3S tab subscribes and
re-renders `schedulesState` on receipt; tab activation still
re-fetches as a safety net for approval-path inserts and
disconnect windows.
- `reminders_changed` (seq, reminders: `Vec<PendingReminder>`) — - `reminders_changed` (seq, reminders: `Vec<PendingReminder>`) —
full snapshot of all pending reminders. Emitted after every full snapshot of all pending reminders. Emitted after every
reminder mutation: agent `remind` calls (`agent_server`), reminder mutation: agent `remind` calls (`agent_server`),

View file

@ -50,7 +50,7 @@
(per-agent) → `text/event-stream` SSE for live updates. The (per-agent) → `text/event-stream` SSE for live updates. The
dashboard stream carries broker `Sent` / `Delivered` (mirrored dashboard stream carries broker `Sent` / `Delivered` (mirrored
by a forwarder task from the broker's intra-process channel) by a forwarder task from the broker's intra-process channel)
plus mutation events (`approval_added` / `approval_resolved`, plus mutation events (`approval_resolved`,
`transient_set` / `transient_cleared`). Each frame carries a `seq`. The `transient_set` / `transient_cleared`). Each frame carries a `seq`. The
matching backfill endpoint is `GET /api/dashboard/history` (last matching backfill endpoint is `GET /api/dashboard/history` (last
~200 broker messages wrapped in `{ seq, events }`) on the ~200 broker messages wrapped in `{ seq, events }`) on the

View file

@ -1,7 +1,7 @@
# hive-c0re # hive-c0re
The unprivileged host daemon (runs as `hive-core`). Owns the sqlite The unprivileged host daemon (runs as `hive-core`). Owns the sqlite
broker, the approval/reminder/schedule queues, the generic job-DAG broker, the approval/reminder queues, the generic job-DAG
queue, container lifecycle, gateway vhost wiring, forge/matrix config queue, container lifecycle, gateway vhost wiring, forge/matrix config
reconciliation, per-container stats, and the axum operator dashboard. reconciliation, per-container stats, and the axum operator dashboard.
Largest crate in the workspace — bin-only, no separate lib. Largest crate in the workspace — bin-only, no separate lib.
@ -32,7 +32,7 @@ to track it:
- **`lifecycle/`** — `nixos-container` lifecycle + per-agent config - **`lifecycle/`** — `nixos-container` lifecycle + per-agent config
flake generation. flake generation.
- **`stores/`** — sqlite-backed stores (broker, queues, audit, power). - **`stores/`** — sqlite-backed stores (broker, queues, audit, power).
- **`workers/`** — background sweeps (crash watch, scheduled prompts, - **`workers/`** — background sweeps (crash watch,
autoupdate, knowledge sync). autoupdate, knowledge sync).
- **`agent_config/`** — per-agent registries (tool groups, - **`agent_config/`** — per-agent registries (tool groups,
capabilities, resource limits, topology). capabilities, resource limits, topology).