start the forge and github pollers when their token lands late
Both pollers wait about ten minutes for the agent's token, then exit 0. Under Restart=on-failure that exit is final, and nothing started them again, so a token minted or placed after the window left the agent without notifications until its container restarted. Add a path unit per poller with PathChanged on the token file(s) the poller reads, the shape forge-avatar-sync already uses. A path unit starts its service only when it is not running, so a poller that found its token is untouched. The github path unit is gated on github.enable like its service. Container-side: it ships with the agent's next rebuild, and an agent on an older rev keeps the old behaviour. Closes #6179
This commit is contained in:
parent
0c78ccbf84
commit
fae9398295
6 changed files with 70 additions and 1 deletions
|
|
@ -133,6 +133,12 @@ unread-only filter), formats each notification as a broker
|
||||||
`Wake { from: "forge" }` message, and delivers it to the agent's own
|
`Wake { from: "forge" }` message, and delivers it to the agent's own
|
||||||
inbox so claude's normal turn loop picks it up.
|
inbox so claude's normal turn loop picks it up.
|
||||||
|
|
||||||
|
The daemon waits about ten minutes for the agent's forge token, then exits 0,
|
||||||
|
which its `Restart=on-failure` doesn't restart. A path unit
|
||||||
|
(`hive-forge-notify.path`) starts it again when the fetched token file or the
|
||||||
|
state-dir `forge-token` changes, so a token minted later still starts the
|
||||||
|
polling.
|
||||||
|
|
||||||
The crate builds a second, independent binary for a different host — see
|
The crate builds a second, independent binary for a different host — see
|
||||||
[github.md](github.md#notifications).
|
[github.md](github.md#notifications).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,10 @@ crate its build cache.
|
||||||
At runtime the poller needs the PAT above. No PAT, no polling: the unit
|
At runtime the poller needs the PAT above. No PAT, no polling: the unit
|
||||||
logs why and exits 0, which is why it's `Restart = on-failure` and
|
logs why and exits 0, which is why it's `Restart = on-failure` and
|
||||||
never `always` — a clean exit on a PAT-less agent must not become a
|
never `always` — a clean exit on a PAT-less agent must not become a
|
||||||
restart loop. Once polling, it re-reads `<state>/github-token` on every
|
restart loop. The poller waits about ten minutes for the PAT to appear
|
||||||
|
before it gives up. A path unit (`hive-github-notify.path`) starts the poller
|
||||||
|
again when `<state>/github-token` changes, so a PAT that lands later still
|
||||||
|
enables polling. Once polling, it re-reads the file on every
|
||||||
tick, so it picks up a rotated PAT on the next tick without a restart.
|
tick, so it picks up a rotated PAT on the next tick without a restart.
|
||||||
|
|
||||||
Forgejo models its notifications API on GitHub's, so one tolerant
|
Forgejo models its notifications API on GitHub's, so one tolerant
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,21 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Starts the poller when a token appears after it gave up waiting for one
|
||||||
|
# (the poller waits about ten minutes, then exits 0, which `on-failure`
|
||||||
|
# does not restart). A path unit starts its service only when it is not
|
||||||
|
# running, so it costs nothing while the poller runs. Both files the poller
|
||||||
|
# reads are watched. `PathChanged=` for the reason given on
|
||||||
|
# `forge-avatar-sync` below.
|
||||||
|
systemd.paths.hive-forge-notify = {
|
||||||
|
description = "start hive-forge-notify when the forge token appears";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
pathConfig.PathChanged = [
|
||||||
|
fetchedTokenFile
|
||||||
|
stateTokenFile
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
# Path-trigger sibling: re-fires forge-avatar-sync when the fetched
|
# Path-trigger sibling: re-fires forge-avatar-sync when the fetched
|
||||||
# forge token (./forge-token.nix) appears or is replaced. On first
|
# forge token (./forge-token.nix) appears or is replaced. On first
|
||||||
# agent deployment the container can boot before the swarm has minted
|
# agent deployment the container can boot before the swarm has minted
|
||||||
|
|
|
||||||
|
|
@ -127,5 +127,18 @@ in
|
||||||
Group = userName;
|
Group = userName;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Starts the poller when the token appears after it gave up waiting for one
|
||||||
|
# (about ten minutes, then exit 0, which `on-failure` does not restart).
|
||||||
|
# The operator places the PAT through the swarm after the agent runs, so
|
||||||
|
# that is the usual order. A path unit starts its service only when it is
|
||||||
|
# not running. `PathChanged=` because the fetch renames the file into place,
|
||||||
|
# and a `PathExists=` that already holds would re-fire on every re-arm.
|
||||||
|
# Gated on the service it triggers.
|
||||||
|
systemd.paths.hive-github-notify = lib.mkIf config.services.hyperhive.agent.github.enable {
|
||||||
|
description = "start hive-github-notify when the GitHub token appears";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
pathConfig.PathChanged = "/agents/${userName}/state/github-token";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,23 @@ let
|
||||||
agentForgeBao.systemd.paths.forge-avatar-sync.pathConfig.PathChanged == tokenFile agentForgeBao
|
agentForgeBao.systemd.paths.forge-avatar-sync.pathConfig.PathChanged == tokenFile agentForgeBao
|
||||||
&& builtins.elem "hive-agent-forge-token.service" agentForgeBao.systemd.services.forge-avatar-sync.after;
|
&& builtins.elem "hive-agent-forge-token.service" agentForgeBao.systemd.services.forge-avatar-sync.after;
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
# The poller waits for the token about ten minutes and then exits 0, which
|
||||||
|
# `on-failure` does not restart; the path unit starts it when either file
|
||||||
|
# it reads appears. Control: the avatar watcher beside it, in the same set.
|
||||||
|
name = "a token that lands after the forge poller gave up starts it again";
|
||||||
|
ok =
|
||||||
|
let
|
||||||
|
path = agentForgeBao.systemd.paths.hive-forge-notify;
|
||||||
|
in
|
||||||
|
path.pathConfig.PathChanged == [
|
||||||
|
(tokenFile agentForgeBao)
|
||||||
|
"/agents/${agentForgeBao.services.hyperhive.agent.user.name}/state/forge-token"
|
||||||
|
]
|
||||||
|
&& path.wantedBy == [ "multi-user.target" ]
|
||||||
|
&& agentForgeBao.systemd.services ? hive-forge-notify
|
||||||
|
&& agentForgeBao.systemd.paths ? forge-avatar-sync;
|
||||||
|
}
|
||||||
{
|
{
|
||||||
# Both readers take the fetched token first and fall back to the state
|
# Both readers take the fetched token first and fall back to the state
|
||||||
# file, which is still the only copy for an agent with no store identity.
|
# file, which is still the only copy for an agent with no store identity.
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,21 @@ let
|
||||||
name = "the fetch runs before the github poller starts";
|
name = "the fetch runs before the github poller starts";
|
||||||
ok = builtins.elem "hive-github-notify.service" (fetchUnit agentGithubBao).before;
|
ok = builtins.elem "hive-github-notify.service" (fetchUnit agentGithubBao).before;
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
# The poller waits for the token about ten minutes and then exits 0, which
|
||||||
|
# `on-failure` does not restart; the path unit starts it when the token lands.
|
||||||
|
name = "a token that lands after the github poller gave up starts it again";
|
||||||
|
ok =
|
||||||
|
let
|
||||||
|
name = agentGithubBao.services.hyperhive.agent.user.name;
|
||||||
|
path = agentGithubBao.systemd.paths.hive-github-notify;
|
||||||
|
in
|
||||||
|
path.pathConfig.PathChanged == "/agents/${name}/state/github-token"
|
||||||
|
&& path.wantedBy == [ "multi-user.target" ]
|
||||||
|
&& agentGithubBao.systemd.services ? hive-github-notify
|
||||||
|
&& !(agentGithubOff.systemd.paths ? hive-github-notify)
|
||||||
|
&& !(agentGithubOff.systemd.services ? hive-github-notify);
|
||||||
|
}
|
||||||
{
|
{
|
||||||
name = "the fetch re-runs every two minutes";
|
name = "the fetch re-runs every two minutes";
|
||||||
ok = agentGithubBao.systemd.timers.hive-agent-github-token.timerConfig.OnUnitInactiveSec == "2min";
|
ok = agentGithubBao.systemd.timers.hive-agent-github-token.timerConfig.OnUnitInactiveSec == "2min";
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue