refactor(hive-agent): split the forge notification poller into its own crate

The poller was a `tokio::spawn` inside the `hive-agent` serve loop. It
never needed anything from that loop except a socket path, so being
in-process bought nothing and cost two things: a harness restart took
forge notifications down with it, and the whole forge/HTTP dependency
tree was linked into the serve-loop binary.

It is now `hive-forge-notify`, a per-agent daemon with its own systemd
unit, a sibling of `hive-bash-daemon` and `hive-matrix-daemon`. Same
contract as those two: it reaches the harness only by upserting todos on
the in-agent socket, and nowhere else.

The module moves verbatim (`notify.rs`) — the formatters, the activation
gates, the dedupe map and all 33 tests are unchanged. Only the socket
call sites are rewritten, onto a small local `todo_client` rather than
the harness's. That mirrors what both sibling daemons already do, and
the etiquette differs on purpose: the harness's client carries a 60s
backoff schedule sized to ride out a hive-c0re restart, which its
callers need because they have no retry of their own. This poller's two
call sites both sit inside the 30s poll loop and both treat a failure as
"leave the thread unread, try next tick", so the poll interval already
is the retry; a second backoff would only stack sleeps and delay the
rest of the batch.

The unit is `Restart=on-failure`, not `always`. An agent with no forge
account is a supported configuration and the poller reports it by
logging why and exiting 0 — under `always` that clean exit would be a
restart loop on every forge-less agent.

`forgejo-api`, `url` and `time` drop out of `hive-agent`'s dependencies
with the module.

Also corrects docs that outlived the code they described: the persisted
`forge_cursor` field is long gone (forge's own read-state is the durable
record of what has been delivered), but `docs/persistence.md` and the
`harness_state` module docs still documented it as live.
This commit is contained in:
atlas 2026-07-26 20:44:54 +02:00 committed by mara
commit 246c9471b1
18 changed files with 301 additions and 48 deletions

View file

@ -57,6 +57,41 @@ in
config.hyperhive.packages.hive-forge
];
# Forge notification poller — a long-running sibling of
# `hive-bash-daemon` / `hive-matrix-daemon`. Polls the agent's unread
# notification list and upserts each thread as a todo on the harness's
# in-agent socket; it needs nothing else from the harness, which is why
# it is its own process rather than a task inside the serve loop.
systemd.services.hive-forge-notify = {
description = "Forgejo notification poller for this agent";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
environment = {
# In-agent todo socket the harness serves (loose-ends v2). Must
# match the harness's HIVE_AGENT_SOCKET (agent-service.nix) — the
# poller upserts one todo per forge thread here rather than firing
# a hive-c0re wake.
HIVE_AGENT_SOCKET = "/run/hive-agent/${userName}/agent.sock";
RUST_LOG = "info";
# HIVE_FORGE_URL and HYPERHIVE_STATE_DIR come from
# systemd.globalEnvironment (forwarded into every container by the
# meta flake) — the poller reads the forge base URL from the first
# and the agent's `forge-token` from under the second.
};
serviceConfig = {
ExecStart = "${config.hyperhive.packages.hive-forge-notify}/bin/hive-forge-notify";
SyslogIdentifier = "hive-forge-notify";
# `on-failure`, NOT `always`: an agent with no forge account is a
# supported configuration, and the poller reports that by logging
# why and exiting 0. Under `always` that clean exit would become a
# restart loop on every forge-less agent. A crash still restarts.
Restart = "on-failure";
RestartSec = 5;
User = userName;
Group = userName;
};
};
# One-shot: tea config.yml from the seeded forge token. Shape
# contract (always exit 0, no set -e, skip-silently, re-runnable):
# docs/conventions.md::Best-effort oneshot services.

View file

@ -12,7 +12,7 @@
hyperhive package outputs consumed by the harness modules: the
per-binary daemon/CLI packages (`hive-agent`, `hive-agent-mcp`,
`hive-bash-daemon`,
`hive-forge`, `hive-matrix-daemon`,
`hive-forge`, `hive-forge-notify`, `hive-matrix-daemon`,
`hive-metric`, `hive-screen-mcp`) plus the `assets`, `frontend` and
`reference-docs` trees. Wired by the flake's agent-base/ruth
nixosModules to `hyperhive.packages.<system>.*`; override an