parent
f6eb0442c4
commit
99752f81d9
13 changed files with 40 additions and 79 deletions
|
|
@ -101,7 +101,7 @@ Per-hive (host + in-container):
|
||||||
Wire types + socket plumbing:
|
Wire types + socket plumbing:
|
||||||
|
|
||||||
- **`hive-types/`** — leaf newtypes, chiefly the validated `Ident`.
|
- **`hive-types/`** — leaf newtypes, chiefly the validated `Ident`.
|
||||||
- **`hive-sh4re/`** — shared payload types (`Message`, `HelperEvent`, …).
|
- **`hive-sh4re/`** — shared payload types (`Message`, …).
|
||||||
- **`hive-host-sock/`** (hivectl ↔ c0re), **`hive-priv-sock/`** (c0re ↔
|
- **`hive-host-sock/`** (hivectl ↔ c0re), **`hive-priv-sock/`** (c0re ↔
|
||||||
priv), **`hive-core-agent-sock/`** (harness ↔ c0re, `/run/hive/mcp.sock`)
|
priv), **`hive-core-agent-sock/`** (harness ↔ c0re, `/run/hive/mcp.sock`)
|
||||||
— per-socket wire types, split out so each client links only its protocol.
|
— per-socket wire types, split out so each client links only its protocol.
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ declarations.
|
||||||
|
|
||||||
- **How do config changes flow from manager to operator to container?** →
|
- **How do config changes flow from manager to operator to container?** →
|
||||||
[`agent-lifecycle/config-changes.md`](agent-lifecycle/config-changes.md)
|
[`agent-lifecycle/config-changes.md`](agent-lifecycle/config-changes.md)
|
||||||
(config PRs, the meta flake, helper events).
|
(config PRs, the meta flake, system messages).
|
||||||
- **What state survives destroy / purge / restart?** →
|
- **What state survives destroy / purge / restart?** →
|
||||||
[`agent-lifecycle/persistence.md`](agent-lifecycle/persistence.md).
|
[`agent-lifecycle/persistence.md`](agent-lifecycle/persistence.md).
|
||||||
- **Who can do what to whom — the agent roster and privilege?** →
|
- **Who can do what to whom — the agent roster and privilege?** →
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# Config changes + helper events
|
# Config changes + system messages
|
||||||
|
|
||||||
Config changes are pull requests on the agent's config repo, which an
|
Config changes are pull requests on the agent's config repo, which an
|
||||||
operator merges on the forge; that merge deploys them. Helper events are
|
operator merges on the forge; that merge deploys them. The "System messages"
|
||||||
how agents stay informed about lifecycle changes that need a reaction.
|
section covers the plain-text `system` messages hive-c0re sends agents.
|
||||||
|
|
||||||
## For operators
|
## For operators
|
||||||
|
|
||||||
|
|
@ -322,24 +322,17 @@ for the root agent. The root agent's role-specific
|
||||||
behaviour comes from its AGENTS.md / agent-specific instructions, not
|
behaviour comes from its AGENTS.md / agent-specific instructions, not
|
||||||
the system prompt template.
|
the system prompt template.
|
||||||
|
|
||||||
## Helper events
|
## System messages
|
||||||
|
|
||||||
`hive_sh4re::manager::HelperEvent` is the wire type for an event urgent
|
Hive-c0re has no typed event that drives an immediate claude turn. A
|
||||||
enough to drive an immediate claude turn (delivered as a regular
|
message from sender `system` is plain inbox text, and the harness logs it
|
||||||
`system` inbox message, same `recv` path as any other message). It
|
(`log_system_event` in `hive-agent/src/main.rs`) without parsing the body.
|
||||||
currently has one variant, `NeedsUpdate { agent }` (sub-agent's
|
Today hive-c0re sends two: the `/knowledge updated` broadcast
|
||||||
recorded flake rev is stale, operator rebuilds from the dashboard) —
|
(`workers/knowledge.rs`) and the wake message for a graceful stop
|
||||||
and nothing in the tree constructs it; this removal dropped the push path
|
(`Coordinator::kick_agent`).
|
||||||
(`Coordinator::notify_agent`) once its last caller
|
|
||||||
(`notify_manager`, used only for the crash event below) went away.
|
|
||||||
`HelperEvent` stays as the type for whatever comes along that
|
|
||||||
genuinely needs to interrupt an agent's turn loop, not because
|
|
||||||
anything live uses it today.
|
|
||||||
|
|
||||||
**A previously running container crashing** used to be exactly that
|
**A previously running container crashing** drives no agent turn.
|
||||||
case (`ContainerCrash`, routed to the root agent via
|
`crash_watch` only `tracing::warn!`s `"container crash
|
||||||
`Coordinator::notify_manager`) — it no longer drives any agent turn at
|
|
||||||
all. `crash_watch` now only `tracing::warn!`s `"container crash
|
|
||||||
detected"`; a swarm-level grafana alert matches that log line directly
|
detected"`; a swarm-level grafana alert matches that log line directly
|
||||||
and pages the operator, with no agent (and no per-hive dashboard
|
and pages the operator, with no agent (and no per-hive dashboard
|
||||||
banner) in the loop. See
|
banner) in the loop. See
|
||||||
|
|
@ -348,18 +341,16 @@ banner) in the loop. See
|
||||||
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 go out via `swarm_notices::notify` instead
|
they go out via `swarm_notices::notify` instead: `subsystem = "core"`,
|
||||||
of a `HelperEvent`: `subsystem = "core"`, `key = "<event>:<agent>"`,
|
`key = "<event>:<agent>"`, and a single free-text `summary`
|
||||||
and a single free-text `summary` (`rebuilt_todo_summary` renders
|
(`rebuilt_todo_summary` renders `Rebuilt`'s `ok`/`note`/`sha`/`tag`
|
||||||
`Rebuilt`'s `ok`/`note`/`sha`/`tag` fields into that string).
|
fields into that string).
|
||||||
|
|
||||||
To add a new lifecycle notice: if it's "FYI, check when convenient,"
|
To add a new lifecycle notice: if it's "FYI, check when convenient,"
|
||||||
call `swarm_notices::notify` — no new wire type needed. If it needs to
|
call `swarm_notices::notify` — no new wire type needed. If it needs to
|
||||||
drive an immediate turn, decide first whether it actually needs an
|
drive an immediate turn, decide first whether it actually needs an
|
||||||
*agent* in the loop at all (the crash case above didn't, once a
|
*agent* in the loop at all (the crash case above didn't, once a
|
||||||
swarm-level alert could carry the same signal) — only reach for a new
|
swarm-level alert could carry the same signal).
|
||||||
`HelperEvent` variant + `notify_agent`-equivalent call site +
|
|
||||||
`prompts/system.md` update when the answer is yes.
|
|
||||||
|
|
||||||
## Autoupdate on startup
|
## Autoupdate on startup
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -519,7 +519,7 @@ config — all nspawn containers live in `machine.slice`.
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
- `docs/agent-lifecycle/config-changes.md` — config PRs, meta flake, helper events
|
- `docs/agent-lifecycle/config-changes.md` — config PRs, meta flake, system messages
|
||||||
- `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
|
||||||
|
|
|
||||||
|
|
@ -68,13 +68,9 @@ runs on the change.
|
||||||
`ack_until(N)` to prevent re-pop. Acked rows never redeliver.
|
`ack_until(N)` to prevent re-pop. Acked rows never redeliver.
|
||||||
Transient pings (sentinel id 0) have nothing to ack and show no marker.
|
Transient pings (sentinel id 0) have nothing to ack and show no marker.
|
||||||
|
|
||||||
**System messages** (from sender `system`): the broker delivers
|
**System messages** (from sender `system`): the broker delivers them as regular inbox
|
||||||
`hive_sh4re::manager::HelperEvent` (one variant, `NeedsUpdate` —
|
messages (same `recv` path). A container crash never reaches an
|
||||||
declared, but no call site constructs it) as regular inbox messages
|
agent — it only produces a log line + swarm-level grafana
|
||||||
(same `recv` path; body is a JSON object with an `event` discriminant
|
|
||||||
field). A container crash used to be the other variant
|
|
||||||
(`ContainerCrash`, routed to `ruth`) but this tree no longer has that
|
|
||||||
path — a crash now only produces a log line + swarm-level grafana
|
|
||||||
alert, no agent turn at all (`docs/scheduler/observability.md`). Lifecycle
|
alert, no agent turn at all (`docs/scheduler/observability.md`). Lifecycle
|
||||||
transitions the job-queue scheduler or crash watcher drive directly —
|
transitions the job-queue scheduler or crash watcher drive directly —
|
||||||
stop/kill, destroy, a flake-rev login or logout state change — reach
|
stop/kill, destroy, a flake-rev login or logout state change — reach
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,7 @@ You are built from the `{archetype}` archetype and have no config repo of your o
|
||||||
|
|
||||||
Operator boundary: starting, stopping and rebuilding containers is the operator's — ask them when any agent needs one. _Creating_ a new agent is not something you can do from here — ask the operator, who creates agents at swarm level. The operator only signs off on changes; you run the day-to-day.
|
Operator boundary: starting, stopping and rebuilding containers is the operator's — ask them when any agent needs one. _Creating_ a new agent is not something you can do from here — ask the operator, who creates agents at swarm level. The operator only signs off on changes; you run the day-to-day.
|
||||||
|
|
||||||
Messages from sender `system` are hyperhive helper events (JSON body, `event` field discriminates): `container_crash`, `needs_update`. Use these to react to lifecycle changes:
|
Messages from sender `system` are plain-text notices from hive-c0re, not peer messages. Two kinds today: a `/knowledge updated` broadcast (read the changed files in `/knowledge`), and the wake message for a graceful stop (check `/agents/<you>/state/` for your notes). A container crash sends you nothing; the operator is alerted separately.
|
||||||
|
|
||||||
- `needs_update` — agent's flake rev is stale. Ask the operator to rebuild it.
|
|
||||||
- `container_crash` — ask the operator to start it again; if it keeps crashing, say so with what you saw.
|
|
||||||
|
|
||||||
Lifecycle notices that don't need an immediate turn — a new agent spawned, a container rebuilt/killed/destroyed, or its login state changing — surface as todos instead of messages now. Call `get_loose_ends` to see them.
|
Lifecycle notices that don't need an immediate turn — a new agent spawned, a container rebuilt/killed/destroyed, or its login state changing — surface as todos instead of messages now. Call `get_loose_ends` to see them.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ use crate::turn_stats::TurnStats;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use hive_core_agent_sock::{Request, Response};
|
use hive_core_agent_sock::{Request, Response};
|
||||||
use hive_sh4re::manager::{HelperEvent, SYSTEM_SENDER};
|
use hive_sh4re::manager::SYSTEM_SENDER;
|
||||||
use hive_sock_client::Retry;
|
use hive_sock_client::Retry;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
|
|
@ -134,21 +134,12 @@ async fn main() -> Result<()> {
|
||||||
// ---------- shared turn helpers ----------
|
// ---------- shared turn helpers ----------
|
||||||
|
|
||||||
/// Surface a `SYSTEM_SENDER` message in the live event bus + tracing
|
/// Surface a `SYSTEM_SENDER` message in the live event bus + tracing
|
||||||
/// log. Both agents and the manager receive `HelperEvent` notifications
|
/// log. Quiet no-op when `from` isn't `SYSTEM_SENDER`.
|
||||||
/// (just `NeedsUpdate`, now that a container crash is a log line instead
|
|
||||||
/// of a `ContainerCrash` event); the parse and log path is
|
|
||||||
/// identical. Quiet no-op when `from` isn't
|
|
||||||
/// `SYSTEM_SENDER`.
|
|
||||||
fn log_system_event(bus: &Bus, from: &str, body: &str) {
|
fn log_system_event(bus: &Bus, from: &str, body: &str) {
|
||||||
if from != SYSTEM_SENDER {
|
if from != SYSTEM_SENDER {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let parsed = serde_json::from_str::<HelperEvent>(body).ok();
|
tracing::info!(%from, %body, "system message");
|
||||||
if let Some(event) = &parsed {
|
|
||||||
tracing::info!(?event, "helper event");
|
|
||||||
} else {
|
|
||||||
tracing::info!(%from, %body, "system message");
|
|
||||||
}
|
|
||||||
bus.emit(LiveEvent::Note {
|
bus.emit(LiveEvent::Note {
|
||||||
text: format!("[system] {body}"),
|
text: format!("[system] {body}"),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -936,10 +936,9 @@ impl Coordinator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `push_todo` summary text for a rebuild outcome, for
|
/// `push_todo` summary text for a rebuild outcome, for
|
||||||
/// `job_queue::exec::run_emit_rebuilt`. Pure + independently testable, unlike
|
/// `job_queue::exec::run_emit_rebuilt`. Pure + independently testable; the
|
||||||
/// the old `HelperEvent::Rebuilt`'s separate `sha`/`tag`
|
/// `sha`/`tag` fields are part of the summary text, since a todo carries one
|
||||||
/// fields — those become part of the summary text itself now, since a todo
|
/// string, not a structured payload.
|
||||||
/// carries one string, not a structured payload.
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn rebuilt_todo_summary(
|
pub fn rebuilt_todo_summary(
|
||||||
agent: &str,
|
agent: &str,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
//! that line directly; see the module doc on [`emit_crash_transitions`].
|
//! that line directly; see the module doc on [`emit_crash_transitions`].
|
||||||
//! Login-state transitions (`needs_login`/`logged_in`) publish onto
|
//! Login-state transitions (`needs_login`/`logged_in`) publish onto
|
||||||
//! `swarm_notices` instead. The `RECENT_TRANSIENT_GRACE` window:
|
//! `swarm_notices` instead. The `RECENT_TRANSIENT_GRACE` window:
|
||||||
//! `docs/agent-lifecycle/config-changes.md::Helper events`.
|
//! `docs/agent-lifecycle/config-changes.md::System messages`.
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# hive-sh4re
|
# hive-sh4re
|
||||||
|
|
||||||
The **shared payload vocabulary** between `hive-c0re` and the in-container
|
The **shared payload vocabulary** between `hive-c0re` and the in-container
|
||||||
harness — the common types (`Message`, `LooseEnd`, `HelperEvent`, …)
|
harness — the common types (`Message`, `LooseEnd`, …)
|
||||||
that the per-socket wire protocols are built from. The request/response
|
that the per-socket wire protocols are built from. The request/response
|
||||||
_envelopes_ themselves now live in the per-socket crates (below); this crate
|
_envelopes_ themselves now live in the per-socket crates (below); this crate
|
||||||
holds the payloads they carry.
|
holds the payloads they carry.
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
//! Reserved recipient names/senders and the out-of-band `HelperEvent`
|
//! Reserved recipient names and senders.
|
||||||
//! payload hive-c0re pushes into the manager's inbox.
|
|
||||||
|
|
||||||
use hive_types::Ident;
|
use hive_types::Ident;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// Logical name of the human operator: the sender of operator-authored
|
/// Logical name of the human operator: the sender of operator-authored
|
||||||
/// messages. Not a recipient — `send` to it fails with
|
/// messages. Not a recipient — `send` to it fails with
|
||||||
|
|
@ -18,8 +16,7 @@ pub const OPERATOR_SEND_REFUSAL: &str = "send failed: `operator` is not a send r
|
||||||
Without them there is no direct channel to the operator: they read your `set_status` \
|
Without them there is no direct channel to the operator: they read your `set_status` \
|
||||||
line and turn output on the dashboard, and your logs.";
|
line and turn output on the dashboard, and your logs.";
|
||||||
|
|
||||||
/// Sender hive-c0re uses for events it pushes into the manager's inbox.
|
/// Sender hive-c0re uses for messages it pushes into an agent's inbox.
|
||||||
/// Manager harness recognises this and parses the body as a `HelperEvent`.
|
|
||||||
pub const SYSTEM_SENDER: &str = "system";
|
pub const SYSTEM_SENDER: &str = "system";
|
||||||
|
|
||||||
/// Parse `s` as a [`Ident`] for use as `Message.from`, falling back to
|
/// Parse `s` as a [`Ident`] for use as `Message.from`, falling back to
|
||||||
|
|
@ -41,17 +38,6 @@ pub fn trusted_sender(s: &str) -> Ident {
|
||||||
.unwrap_or_else(|_| Ident::parse(SYSTEM_SENDER).expect("SYSTEM_SENDER is a valid Ident"))
|
.unwrap_or_else(|_| Ident::parse(SYSTEM_SENDER).expect("SYSTEM_SENDER is a valid Ident"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Out-of-band events the host-side daemon pushes to the manager's inbox.
|
|
||||||
/// Serialised as JSON in `Message::body` (sender = `SYSTEM_SENDER`).
|
|
||||||
/// Per-variant triggers + the optional `sha`/`tag` semantics live in
|
|
||||||
/// `docs/agent-lifecycle/config-changes.md::Helper events`.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
#[serde(tag = "event", rename_all = "snake_case")]
|
|
||||||
pub enum HelperEvent {
|
|
||||||
/// A sub-agent's recorded flake rev is stale relative to hyperhive.
|
|
||||||
NeedsUpdate { agent: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod reserved_name_tests {
|
mod reserved_name_tests {
|
||||||
use super::{OPERATOR_RECIPIENT, SYSTEM_SENDER};
|
use super::{OPERATOR_RECIPIENT, SYSTEM_SENDER};
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,9 @@
|
||||||
# (`{from: "operator", ...}`) and the fallback attribution for an answered
|
# (`{from: "operator", ...}`) and the fallback attribution for an answered
|
||||||
# question.
|
# question.
|
||||||
"operator"
|
"operator"
|
||||||
# Helper events (`container_crash`, `needs_update`) — the sender an
|
# The sender of plain-text notices from hive-c0re (knowledge updates,
|
||||||
# agent is told to treat as hyperhive itself rather than as a peer.
|
# the graceful-stop wake) — an agent is told to treat it as hyperhive
|
||||||
|
# itself rather than as a peer.
|
||||||
# `hive_sh4re::manager::SYSTEM_SENDER`.
|
# `hive_sh4re::manager::SYSTEM_SENDER`.
|
||||||
"system"
|
"system"
|
||||||
# A due self-scheduled reminder arrives as its own sender, so a wake I asked
|
# A due self-scheduled reminder arrives as its own sender, so a wake I asked
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ pub struct User {
|
||||||
/// rest are message *senders*, and they matter for a reason that is not
|
/// rest are message *senders*, and they matter for a reason that is not
|
||||||
/// cosmetic: the harness switches on the sender name, so an agent called
|
/// cosmetic: the harness switches on the sender name, so an agent called
|
||||||
/// `todo` would have its messages interpreted as todo-wakes and one called
|
/// `todo` would have its messages interpreted as todo-wakes and one called
|
||||||
/// `system` as helper events. That is a parse, not a display quirk.
|
/// `system` as hive-c0re notices. That is a parse, not a display quirk.
|
||||||
///
|
///
|
||||||
/// Sorted so a reader can scan it; the lookup is a linear walk over eight
|
/// Sorted so a reader can scan it; the lookup is a linear walk over eight
|
||||||
/// short strings on a path that already reads a file off disk.
|
/// short strings on a path that already reads a file off disk.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue