Commit graph hyperhive/hive-c0re/src/agent_server.rs
Author SHA1 Message Date
atlas
a053d33184 refactor(#1865): rename agent_server module to socket_server
Pure rename ahead of the agent+manager server consolidation: the
per-agent socket dispatcher already hosts the shared dispatch and all
lifecycle handlers, and will absorb the manager-only handlers next, so
`agent_server` becomes a misnomer. No logic change — git mv plus a
mechanical `agent_server` -> `socket_server` rename across refs.
2026-06-22 13:58:52 +02:00
atlas
53f49615fa refactor(#1834): derive cancel-loose-end privilege from the socket, not the MANAGER_AGENT name
The cancel-loose-end guards (cancel approval / question / reminder)
inferred manager-level privilege by string-matching the canceller
against the hardcoded `hive_sh4re::MANAGER_AGENT` ("ruth"). That laundered
privilege through a name: a request arrives on the privileged manager
socket, gets stamped with the bootstrap agent's name, and the guards
re-derive privilege from that name. Renaming or replacing the bootstrap
agent would then silently move privilege.

Privilege is a property of the SOCKET the request arrived on (the manager
socket is the trust boundary), so thread an explicit `privileged: bool`
through `dispatch_shared` → `handle_cancel_loose_end` → the three guards:

- `Broker::cancel_reminder_as` and `OperatorQuestions::cancel`: the
  `== MANAGER_AGENT` leg becomes `privileged` (owner/asker + operator name
  legs unchanged).
- `check_approval_canceller_is_manager(canceller)` →
  `check_can_cancel_approval(privileged)` (manager-socket-only); unit tests
  updated to assert on the flag.

The manager socket passes `privileged = true`; the agent socket passes
`false`. `MANAGER_AGENT` is still passed as the actor NAME for legitimate
attribution/routing (notifications, schedule ownership, bootstrap
destroy-protection) — those are not privilege checks and are left intact.
Scope is the privilege guards only.
2026-06-22 02:07:14 +02:00
atlas
27ac0153c4 forge authz: scrub tracker tags from comments
Replace the #-number tracker references in code comments with prose
(tracker-tag lint; hive convention is prose in source). No behaviour
change. Branch-protection / collaborator / team / repo API field names
were verified against the live Forgejo swagger.
2026-06-21 22:36:45 +02:00
atlas
f1d54ce12c agent surface: create_repo through hive-c0re (#1787)
Closes the #1787 loop — the sanctioned create path now that agents
can't create repos directly. Adds:

- wire: Request::CreateRepo{repo} + Response::RepoCreated{full_name,
  clone_url} (hive-sh4re).
- agent_server: dispatch_shared arm + handle_create_repo — validates the
  repo name, then forge::create_agent_repo (org-owned repo, agent=write
  collaborator, operator-team branch protection). Returns the full name
  + clone url so the agent can git clone immediately.
- MCP: create_repo tool + CreateRepoArgs in the harness.
- a new opt-in ToolGroup::Forge (=[create_repo]) so the operator
  controls which agents can spin up repos (least privilege).

Workspace clippy -D warnings, cargo test, nix fmt all green.
2026-06-21 22:36:45 +02:00
damocles
b3d002e4a7 hive-c0re: let request_init_config spawn a brand-new sub-agent under its requester 2026-06-21 22:36:35 +02:00
atlas
cd025b3790 hive-sh4re: type infra containers as an InfraContainer enum
Replace the stringly-typed infra-control path with an InfraContainer enum
(Ci/Forge/Gateway/Matrix). The variants are the allowlist: serde rejects any
unknown or unsafe name (hive-c0re has no variant) at the wire boundary, so
hive-priv no longer needs a root-side SIBLING_CONTAINERS.contains() check on
ControlInfraContainer — the type enforces it, and 'the daemon can't stop
itself' is a compile-time guarantee.

- priv_proto: InfraContainer enum; manual Serialize/Deserialize + FromStr +
  unit_name() all key off one mapping, so the wire form ('hive-ci', …) is
  unchanged and there's no drift. ControlInfraContainer.container: String ->
  InfraContainer.
- hive-priv / priv_client / server.rs: thread the enum; scoped_infra returns
  Vec<InfraContainer>; the control handler uses unit_name().
- agent_server: the infra_admin restart gate parses the name via FromStr
  instead of a slice .contains().
- SIBLING_CONTAINERS stays (validate_container_name/_system_name still use it
  for journals / general container validation); a test keeps the enum and the
  slice in lockstep.
2026-06-21 21:05:52 +02:00
damocles
03ea5d601b hive-c0re: graceful agent stop — quiesce harness, flush state, then stop 2026-06-19 11:50:59 +02:00
atlas
0df9e40940 hivectl: collapse infra allowlist + restart/control ops onto SIBLING_CONTAINERS
Per review: RESTARTABLE_INFRA_CONTAINERS and the new CONTROLLABLE_INFRA_CONTAINERS
were near-identical subsets of SIBLING_CONTAINERS. Drop both and validate infra
lifecycle ops against SIBLING_CONTAINERS directly (all four infra containers;
hive-c0re is never in it, so it can't stop itself). This also makes hive-matrix
restartable, including via an infra_admin agent's restart tool.

Collapse the two priv ops too: RestartInfraContainer is gone; ControlInfraContainer
{ action } is the single op (restart = action: Restart). priv_client's
restart_infra_container is now a thin wrapper over control_infra_container.
2026-06-19 08:16:18 +02:00
atlas
629f08a113 feat(audit): live-append event for the dashboard audit view
Follow-up to the audit-log backend + surface. Emits a DashboardEvent on
each agent-initiated privileged action so the audit view live-appends off
/dashboard/stream instead of polling.

- new DashboardEvent::AuditEntryAdded { seq, <flattened AuditEntry> } —
  serde tag `audit_entry_added`; the AuditEntry fields flatten to the top
  level so the wire shape matches one /api/audit-log `entries` row exactly.
- Coordinator::emit_audit_entry helper (stamps seq like the others).
- audit_log::record now returns the canonical inserted AuditEntry (id + ts
  assigned) so the streamed event is the same row that was stored — no
  drift. Best-effort unchanged (None on a sqlite blip).
- handle_restart_infra records + emits for every attempt (ok/err/denied),
  threading the coordinator through.

Tests: kind_tag round-trip now covers the new variant; added a flatten
test pinning the top-level wire shape (kind/seq/id/…/detail, no nesting).

Pairs with iris's audit view (the /dashboard/stream listener half).
2026-06-13 17:17:00 +02:00
atlas
a452a92fb1 feat(audit): persistent audit log of agent-initiated privileged actions
Adds a durable, operator-visible audit trail of privileged operations
hive-c0re performs on behalf of an agent — the ones that cross the
agent/operator trust boundary. First entry: infra-container restarts via
the infra_admin-gated `restart` tool, which until now were recorded only
as a hive-priv journal trace.

Backend:
- new `audit_log` module: sqlite-backed store (audit_log.sqlite, same dir
  as build_logs) with schema (ts/agent/action/target/outcome/detail),
  best-effort `record`, `list_recent` (clamped 500), 90-day `vacuum`, and
  a process-singleton handle mirroring build_logs.
- Coordinator opens + installs the handle; main spawns the hourly vacuum.
- agent_server::handle_restart_infra records every attempt (ok, error, and
  capability-denied) via the global handle — best-effort, never fails the
  underlying action.
- dashboard: `GET /api/audit-log` returns recent entries as JSON.

Scope is deliberately agent-initiated privileged actions only (not every
PrivRequest — token writes + nspawn edits are constant lifecycle noise).
Extensible: future agent-initiated priv ops record via the same handle.

Unit tests cover record/list ordering, the 500 clamp, and retention vacuum.

The dashboard *surface* (an AUDIT view consuming /api/audit-log) is a
frontend follow-up coordinated with iris.
2026-06-13 13:45:02 +02:00
damocles
f05031ebe3 feat(#1636): infra_admin capability — restart hive-ci/gateway/forge via restart tool 2026-06-13 12:19:56 +02:00
atlas
ab1b07acce ci(#1555): prose-ify legacy tracker tags; add lint:allow escape hatch
Clean the legacy backlog so the tracker-tag lint can become a required
gate (mara's warn-during-cleanup -> full-tree-enforcement path). Rewrite
the ~33 real `closes/see #NNN` provenance refs in doc-comments to prose
across hive-forge, hive-c0re, hive-ag3nt, hive-matrix-mcp, hive-sh4re,
and add a `lint:allow` line marker to check-issue-refs.sh for genuine
non-tracker `#<digits>` (a hash-digit heading-detection test input).
Tree is now lint-clean; tracker-tag lint ready to promote to required.
2026-06-10 13:14:42 +02:00
damocles
5804e986ce refactor(#1474): extract remaining dispatch_shared + hive-priv arms, drop their too_many_lines allows 2026-06-09 12:22:57 +02:00
damocles
c6d9f59c4d refactor(#1474): extract dispatch arm logic into helpers (server + agent_server) 2026-06-09 12:19:48 +02:00
damocles
3130e56cfb refactor(#1474): replace too_many_arguments allows on pub fns with param structs 2026-06-09 11:25:38 +02:00
damocles
7c9954ceec chore(#1474): add reason= to remaining bare clippy allows outside dashboard 2026-06-09 11:25:38 +02:00
damocles
f751c4495f refactor(#1474): group journalctl read args into a journalquery struct 2026-06-08 21:58:08 +02:00
damocles
fb726197ea fix(#1375): clean up pedantic warnings and re-enable -D warnings without pedantic bypass 2026-06-05 16:55:09 +02:00
iris
a1e46e2b3d feat: live SSE updates for the SYST3M reminders section
Add RemindersChanged SSE event so the pending-reminders list in the
SYST3M tab updates live without polling.

Backend emission sites (every path that mutates the reminders table):
- agent_server: store_remind (remind MCP call)
- dashboard.rs: post_cancel_reminder, post_retry_reminder
- questions.rs: cancel_loose_end Reminder kind
- reminder_scheduler: after each delivery batch (any_delivered)

Coordinator gets emit_reminders_snapshot() mirroring the existing
emit_schedules_snapshot() pattern: lists PendingReminder rows from the
broker and emits DashboardEvent::RemindersChanged.

Frontend: applyRemindersChanged(ev) calls renderReminders(ev.reminders)
and is registered as reminders_changed in MUTATION_HANDLERS.

Docs: dashboard.md reminders_changed entry; CLAUDE.md file map updated.
2026-06-05 12:06:32 +02:00
damocles
41eb3f806c refactor: remove hyperhive.role option — there is only one role: agent 2026-06-04 14:31:44 +02:00
damocles
c634dab2a6 refactor: unify AgentServer + ManagerServer into HiveServer {socket, flavor} 2026-06-04 00:20:17 +02:00
damocles
94c2d651c0 feat(#1178): add list_containers tool to AgentServer (topology-scoped to descendants) 2026-06-03 23:08:31 +02:00
damocles
3fa31a414f feat(#791): add request_init_config + request_apply_commit to AgentServer (topology-scoped) 2026-06-03 22:52:52 +02:00
damocles
2722e1548c feat(#1178): add kill + update tools to AgentServer (topology-scoped) 2026-06-03 22:18:03 +02:00
damocles
049ae47191 fix(#1164,#1171): harness writes status file, c0re just rescans 2026-06-03 18:33:07 +02:00
damocles
452766b35f fix(#1165): validate send recipient before queuing message 2026-06-03 18:32:38 +02:00
damocles
5a9ede10df feat(#792): agent restart tool — topology-checked restart of direct children 2026-06-03 18:23:28 +02:00
damocles
a1c6736ba5 feat(#1106): transient wake for bash tasks — bypass broker sqlite 2026-06-03 12:30:30 +02:00
müde
9e12012a95 fix(#702): route container journal reads through hive-priv
The privsep drop to the hive-core user left four journalctl -M <container>
call sites shelling out directly. -M enters the container namespace via the
machine bus, which needs root, so all container-journal reads failed with
Permission denied. Add a ReadContainerJournal verb to hive-priv and route
dashboard get_journal, manager get_logs, the rebuild-failure journal tail,
and the agent host-journal -M path through it. Host-journal reads (no -M)
stay direct via systemd-journal group membership.
2026-06-02 23:43:02 +02:00
damocles
89665b94de feat(#1014): rename manager agent root→ruth across all crates + frontend 2026-06-02 18:18:06 +02:00
atlas
4bff450343 feat(gateway): hivectl gateway user management + fix htpasswdFile assertion
Add `hivectl gateway {create-user,delete-user,list-users}` subcommands for
managing htpasswd files used by gateway Basic auth. Pure Rust bcrypt
(cost 12, $2y$ prefix nginx accepts). No external htpasswd binary required.

Also fix the NixOS module assertion: `cfg.auth ? htpasswdFile` is always
true in the module system (declared options always exist as keys); switch
to `nullOr path; default = null` + `!= null` check so the assertion
actually fires with a useful error when enable=true but no file is set.
Guard bind-mount and nginx config against null to prevent eval errors.

Update docs/gateway.md to show hivectl commands instead of raw htpasswd.
2026-06-01 23:25:28 +02:00
atlas
5c5ca38fe8 fix(#999): resolve all clippy warnings across the workspace
All crates now pass `cargo clippy --workspace -- -D warnings` cleanly.

Fixes span six crates (hive-sh4re, hive-ag3nt, hive-c0re, hive-forge,
hive-priv, hive-matrix-mcp was already clean):

- doc_markdown: wrap snake_case, type names, constants in backticks
- collapsible_if / collapsible_match: fold nested ifs into let-chains
- duration_suboptimal_units: Duration::from_secs(N) → from_mins/from_hours
- implicit_hasher: allow on HashMap-param fns where generalization is risky
- items_after_statements: hoist use to function tops
- map(f).unwrap_or(x) → map_or(x, f); map(f).unwrap_or_else(g) → map_or_else
- is_ok_and / is_none_or in place of map().unwrap_or(bool)
- needless_continue: {} instead of continue in loop match arms
- match_same_arms: Ok(None) | Err(_) merged
- format_push_str: write!() instead of push_str(&format!())
- while let replaces loop { let Some(..) = x else { break } }
- struct_excessive_bools / dead_code: allow on purpose-built structs
- too_many_lines / too_many_arguments: allow where refactor not worth it
- unused_async: remove async from poll_once in bash_runner
- needless_borrow: fix &repo deref in hive-forge comments verb
- cast_possible_truncation: allow u64→usize in fetch_tail

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 22:31:06 +02:00
damocles
d35b7ab9b4 fix(#1021): error on unauthorized target (not silent ignore); allow child targeting without cap 2026-06-01 21:26:42 +02:00
damocles
4834ca413c feat(#1021): query_agent_state capability for agent socket GetLooseEnds/CountPendingReminders/ReminderRollup 2026-06-01 21:21:25 +02:00
damocles
9a1014f195 fix(#1019): add allow(too_many_lines) to dispatch_shared 2026-06-01 21:09:40 +02:00
damocles
68f488d81f refactor(#1019): unify agent + manager server dispatch via dispatch_shared 2026-06-01 21:09:40 +02:00
damocles
b9ecacaafe fix(#1004,#1006): get_host_journal - JournalPriority enum, grep/since/until, default 30/max 100, verbatim container, fix doc comment 2026-06-01 20:57:36 +02:00
damocles
dc8a4e2baf feat(#1004,#1006): capability system + read_host_journal / get_host_journal MCP tool 2026-06-01 20:57:36 +02:00
damocles
15617cef9a refactor: unify AgentRequest/Response + ManagerRequest/Response into Request/Response (#691) 2026-06-01 11:20:10 +02:00
damocles
36c683138d feat(#704): add <children> recipient sentinel — fan-out to direct descendants 2026-05-31 20:59:20 +02:00
damocles
d2f1a9d291 hive-c0re: extract agent-socket-plumbing prose (#715 batch 5) 2026-05-31 17:32:21 +02:00
damocles
a91cf4493f get_agent_meta: include hive_name + swarm_name in response (#710) 2026-05-31 12:50:19 +02:00
damocles
6c2cd078f1 set_status: reject multi-line and over-200-char text (#720) 2026-05-31 11:54:08 +02:00
damocles
7142e95c8f broker: resolve <parent> sentinel to topology parent at send time (#692) 2026-05-31 11:39:27 +02:00
müde
8571e3243a c0re: chmod 0666 mcp.sock after bind so in-container agent user can connect (#658 fixup) 2026-05-30 23:15:16 +02:00
iris
7b4917b256 container_view: clear live-only fields when stopped (#432)
per mara's review on #433, move the gating from the dashboard into
the host so a stopped container's stale on-disk state (rate_limited
sentinel, hyperhive-needs-login, last-turn-stats row, status blob)
never reaches the wire in the first place. when build_all sees
is_running == false:

  - needs_login → false
  - ctx_tokens / context_window_tokens → None
  - rate_limited → false
  - status_text / status_set_at → None

static / declared fields (extra_links, deployed_sha,
pending_reminders, needs_update, parent) stay populated regardless
of run state.

extend AgentMeta (both AgentResponse + ManagerResponse) with a
`running: bool` field so get_agent_meta callers can tell whether
the target is up — answers the second half of #432 ("agent meta
should probably show the info that it is not running as well").
read_agent_status_live wraps the existing read_agent_status with
the same is_running gate so the manager/agent socket handlers don't
have to know about sentinel semantics.

format_agent_meta now prints a `running: yes|no` line so claude
sees the run state in plain text alongside hyperhive_rev.

frontend follow-up in the same commit: drop the redundant
`c.running &&` guards on ctx_tokens / status_text in
renderContainers — the backend now guarantees those fields are
absent when the container is stopped, so the existing
truthy-check is sufficient. the `■ not running` badge + icon /
links fetch short-circuits stay (those are pure presentation /
network-noise wins the backend can't address).
2026-05-25 23:35:03 +02:00
damocles
73871f18c3 set_status: consolidate whoami into get_agent_meta with optional name 2026-05-23 11:32:33 +02:00
damocles
77fdaf0d1e set_status: add get_agent_meta tool for inter-agent status queries 2026-05-23 11:32:33 +02:00
damocles
8e8e8a771f set_status: add status_set_at timestamp (mtime of status file) 2026-05-23 11:32:33 +02:00
damocles
fe2933b213 feat: add set_status MCP tool and status field to whoami/dashboard (closes #325) 2026-05-23 11:32:33 +02:00