Compare commits
7 changed files with 92 additions and 254 deletions
|
|
@ -546,15 +546,9 @@ the body + marker check at that point.
|
|||
process per agent. Serves its MCP tools directly over
|
||||
streamable-http (`services.hyperhive.agent.mcp.matrixHttpPort`, no stdio bridge —
|
||||
same shape as `hive-bash-daemon`), emits hyperhive wake signals
|
||||
on incoming room events via `/run/hive/mcp.sock`. Conditional on the
|
||||
agent having at least one `services.hyperhive.agent.matrixAccounts`
|
||||
entry — there is no separate enable switch, and the same condition gates
|
||||
the daemon, its path watcher AND the autoinjected
|
||||
`extraMcpServers.matrix` entry. The module declares the hive-internal
|
||||
`main` entry whenever `services.hyperhive.agent.matrix.url` is non-null,
|
||||
so on a real hive (where hive-c0re renders that URL per agent) every
|
||||
agent has one; a `null` URL with no operator-declared account is the
|
||||
"this agent has no matrix" state.
|
||||
on incoming room events via `/run/hive/mcp.sock`. Conditional on
|
||||
`services.hyperhive.agent.matrix.enable` (which both the daemon AND the
|
||||
autoinjected `extraMcpServers.matrix` entry read).
|
||||
|
||||
**First-boot ordering**: hive-c0re provisions the matrix token AFTER
|
||||
agent containers come up. Without the path-trigger sibling
|
||||
|
|
|
|||
|
|
@ -2,14 +2,10 @@
|
|||
|
||||
## Built-in matrix MCP (`mcp__matrix__*`)
|
||||
|
||||
When the agent has at least one `services.hyperhive.agent.matrixAccounts`
|
||||
entry, the harness autoinjects `hive-matrix-daemon`'s
|
||||
When `services.hyperhive.agent.matrix.enable = true` and the host-level matrix
|
||||
tuwunel is configured, the harness autoinjects `hive-matrix-daemon`'s
|
||||
streamable-http endpoint as a second MCP server (no stdio bridge —
|
||||
see Architecture below). No `matrix.enable` switch exists: the account
|
||||
set *is* the switch, and the module declares the hive-internal `main`
|
||||
account for you as soon as `services.hyperhive.agent.matrix.url` is
|
||||
non-null — which hive-c0re fills in per agent whenever the host-level
|
||||
matrix tuwunel is configured. Tools land as `mcp__matrix__<name>`:
|
||||
see Architecture below). Tools land as `mcp__matrix__<name>`:
|
||||
|
||||
### Messaging
|
||||
|
||||
|
|
@ -75,8 +71,7 @@ room you haven't read yet.
|
|||
|
||||
`services.hyperhive.agent.matrixAccounts` (declared in `agent.nix`) is
|
||||
the agent's full set of matrix identities — for example an
|
||||
external-facing account alongside the internal one — **and, being
|
||||
non-empty, is what turns matrix on for the agent at all.** Each entry is
|
||||
external-facing account alongside the internal one. Each entry is
|
||||
keyed by account name and specifies `tokenFile` (bearer token,
|
||||
provisioned out-of-band; basename must start with `matrix-token`),
|
||||
`sessionDir` (per-account matrix-sdk sqlite state — crypto keys +
|
||||
|
|
@ -88,17 +83,8 @@ declares it for you as an ordinary entry of this map, from
|
|||
here are the *further* accounts. Its `tokenFile` is pinned to
|
||||
`<state>/matrix-token` (hive-c0re provisions it there), and the
|
||||
dashboard's link-account route refuses to create an account by that
|
||||
name.
|
||||
|
||||
`main` is present exactly when that URL is non-null, which is the whole
|
||||
mechanism behind "accounts are the enable signal": an agent with no
|
||||
homeserver and no account of its own has an empty set, so it gets no
|
||||
daemon, no path watcher and no injected MCP entry. That's how you give
|
||||
an agent no matrix tools — it replaces the removed
|
||||
`services.hyperhive.agent.matrix.enable = false`, which now fails
|
||||
evaluation with a message saying so. Declaring an external account with
|
||||
its own `homeserver` is enough on its own; a hive homeserver isn't
|
||||
required.
|
||||
name. Declaring extras requires
|
||||
`services.hyperhive.agent.matrix.enable = true`.
|
||||
|
||||
Every matrix tool above takes an optional `account` parameter (a name
|
||||
from this map) to act as that identity instead of the primary one.
|
||||
|
|
|
|||
|
|
@ -18,25 +18,6 @@
|
|||
flakeInputs ? { },
|
||||
...
|
||||
}:
|
||||
let
|
||||
# What an `agent.nix` still setting the dropped per-agent matrix switch is
|
||||
# told. Shared by the two `mkRemovedOptionModule` entries below, one per
|
||||
# spelling of the path.
|
||||
matrixEnableRemoved = ''
|
||||
The per-agent matrix switch is gone. Matrix is enabled for an agent exactly
|
||||
when it has at least one services.hyperhive.agent.matrixAccounts entry, and
|
||||
the hive-internal `main` entry is declared for you whenever
|
||||
services.hyperhive.agent.matrix.url is non-null.
|
||||
|
||||
- `enable = true` was the default: drop the line, nothing else to do.
|
||||
- `enable = false`: set services.hyperhive.agent.matrix.url = null instead
|
||||
(and declare no accounts of your own). That is what leaves the account set
|
||||
empty, which is now what suppresses the daemon, its path watcher and the
|
||||
injected extraMcpServers.matrix entry.
|
||||
|
||||
See docs/tools/matrix.md.
|
||||
'';
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./agent-service.nix
|
||||
|
|
@ -66,20 +47,6 @@ in
|
|||
The built-in Bash tool is fully disabled; agents use
|
||||
mcp__bash__run instead. Remove the setting from your agent.nix.
|
||||
'')
|
||||
# The dropped per-agent matrix switch. A removal, not a silent delete,
|
||||
# because the definition that *changes meaning* is `false`: left undeclared
|
||||
# it would be ignored and the agent would quietly gain the matrix tools its
|
||||
# operator turned off. Failing the eval with the replacement spelling is the
|
||||
# only outcome that cannot do that.
|
||||
#
|
||||
# Both spellings, for the same reason ../host-modules/deploy.nix lists both
|
||||
# of `swarm|deploy.matrix.registrationTokenFile`: an existing agent.nix may
|
||||
# carry either the pre-rename `hyperhive.*` path or the current one, and only
|
||||
# the one it actually carries will fire. The rename shim that used to bridge
|
||||
# them is gone from ./renamed-options.nix — a rename pointing at an
|
||||
# undeclared option breaks evaluation for everyone, removed or not.
|
||||
(lib.mkRemovedOptionModule [ "hyperhive" "matrix" "enable" ] matrixEnableRemoved)
|
||||
(lib.mkRemovedOptionModule [ "services" "hyperhive" "agent" "matrix" "enable" ] matrixEnableRemoved)
|
||||
];
|
||||
|
||||
options.services.hyperhive.agent.icon = lib.mkOption {
|
||||
|
|
|
|||
|
|
@ -3,9 +3,6 @@
|
|||
# hive-matrix-daemon (serves its MCP tools directly over
|
||||
# streamable-http), its token-arrival path trigger, and the
|
||||
# auto-injected extraMcpServers entry.
|
||||
#
|
||||
# There is no `matrix.enable`. An agent has matrix exactly when it has an
|
||||
# account to serve — see `matrixEnabled` below.
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
|
|
@ -19,21 +16,6 @@ let
|
|||
# its matrix-sdk store. Shared by the `main` account entry below and
|
||||
# the path-watcher glob at the bottom of this file.
|
||||
stateDir = "/agents/${userName}/state";
|
||||
accounts = config.services.hyperhive.agent.matrixAccounts;
|
||||
# **The enable signal.** Matrix is on for this agent exactly when it has at
|
||||
# least one account, because an account is the only thing the daemon has to
|
||||
# do: no account, no Client, no sync, no tool surface worth injecting.
|
||||
#
|
||||
# This is not trivially true even though the module declares `main` itself:
|
||||
# that definition is gated on `matrix.url != null` (see below), which is the
|
||||
# per-agent "does this agent have a homeserver to reach" fact. So an agent the
|
||||
# hive handed no homeserver URL, whose operator declared no external account
|
||||
# either, has an empty set here and gets none of the units — the case the
|
||||
# deleted `matrix.enable = false` used to express.
|
||||
#
|
||||
# No cycle: `matrixAccounts`'s own definition reads `matrix.url`, never this
|
||||
# binding, so the `mkIf`s below may read the merged option value freely.
|
||||
matrixEnabled = accounts != { };
|
||||
# Rasterize the operator-set agent icon (`services.hyperhive.agent.icon`, an SVG) to a
|
||||
# 512x512 PNG so the matrix daemon can upload it as each account's avatar
|
||||
# over the live authenticated Client (see hive-matrix-mcp::client::sync_avatar).
|
||||
|
|
@ -45,6 +27,40 @@ let
|
|||
'';
|
||||
in
|
||||
{
|
||||
options.services.hyperhive.agent.matrix.enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Enable per-agent matrix integration via `hive-matrix-daemon`.
|
||||
When true (the default), the harness:
|
||||
|
||||
- runs `hive-matrix-daemon` as a systemd unit that holds a
|
||||
matrix-sdk Client + sync against the homeserver named by
|
||||
`HIVE_MATRIX_URL` (see `services.hyperhive.agent.matrix.url` — there is no
|
||||
default, since an agent's own netns makes a loopback guess
|
||||
wrong). The daemon auto-skips when that URL or
|
||||
`<state>/matrix-token` is missing, and a `systemd.paths`
|
||||
watcher restarts it the moment hive-c0re provisions the token
|
||||
(same path-trigger shape as `forge-avatar-sync`).
|
||||
- exposes the matrix tool surface (send_message, send_dm,
|
||||
send_reaction, send_reply, mark_read, list_rooms,
|
||||
list_room_members, read_room) to claude via an auto-injected
|
||||
`extraMcpServers.matrix` entry pointed at the daemon's own
|
||||
streamable-http listener (`services.hyperhive.agent.mcp.matrixHttpPort`) — no
|
||||
stdio bridge, no per-turn respawn, same shape as the built-in
|
||||
hyperhive surface and `hive-bash-daemon`.
|
||||
- wakes the agent on incoming room events via a short teaser
|
||||
Wake signal (`[matrix] <sender> in <room>: <first 100c>…`)
|
||||
to the hyperhive control socket; the full event stays
|
||||
unread server-side until `read_room` consumes it.
|
||||
|
||||
Set to `false` for agents that should NOT have matrix tools at
|
||||
all (e.g. agents on a host without `services.hyperhive.agent.matrix.enable` on
|
||||
the meta side). When token file is absent the daemon and MCP
|
||||
both no-op cleanly anyway, so `false` is rarely necessary.
|
||||
'';
|
||||
};
|
||||
|
||||
options.services.hyperhive.agent.matrix.url = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
|
|
@ -64,18 +80,10 @@ in
|
|||
different host from the agents, and inside an agent's network
|
||||
namespace `localhost` reaches the agent rather than the
|
||||
homeserver, so a default would be a value that builds fine and
|
||||
then talks to the wrong machine. An absent integration, never a
|
||||
then talks to the wrong machine. When this is `null` the daemon
|
||||
is left without a homeserver and no-ops, exactly as it does when
|
||||
the token file is absent --- an absent integration, never a
|
||||
misdirected one.
|
||||
|
||||
`null` is also this agent's **matrix off switch**, and the
|
||||
replacement for the `services.hyperhive.agent.matrix.enable`
|
||||
boolean that used to exist: the hive-internal `main` account in
|
||||
`services.hyperhive.agent.matrixAccounts` is declared from this
|
||||
URL, so `null` leaves that set empty and the whole integration —
|
||||
daemon unit, path watcher, injected MCP entry — is not generated
|
||||
at all. Declaring an external account with its own `homeserver`
|
||||
turns matrix back on without a hive homeserver, which is the
|
||||
honest reading of that config.
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
@ -135,41 +143,12 @@ in
|
|||
each account (unique by construction) and is the handle the matrix
|
||||
MCP tools target via their `account` argument.
|
||||
|
||||
**This set is also the enable signal for per-agent matrix** —
|
||||
there is no separate boolean. A non-empty set means the harness:
|
||||
|
||||
- runs `hive-matrix-daemon` as a systemd unit that holds a
|
||||
matrix-sdk Client + sync per account against that account's
|
||||
homeserver (`homeserver`, else
|
||||
`services.hyperhive.agent.matrix.url`). The daemon auto-skips an
|
||||
account whose URL or token file is missing, and a `systemd.paths`
|
||||
watcher restarts it the moment hive-c0re provisions the token
|
||||
(same path-trigger shape as `forge-avatar-sync`).
|
||||
- exposes the matrix tool surface (send_message, send_dm,
|
||||
send_reaction, send_reply, mark_read, list_rooms,
|
||||
list_room_members, read_room) to claude via an auto-injected
|
||||
`extraMcpServers.matrix` entry pointed at the daemon's own
|
||||
streamable-http listener (`services.hyperhive.agent.mcp.matrixHttpPort`) — no
|
||||
stdio bridge, no per-turn respawn, same shape as the built-in
|
||||
hyperhive surface and `hive-bash-daemon`.
|
||||
- wakes the agent on incoming room events via a short teaser
|
||||
Wake signal (`[matrix] <sender> in <room>: <first 100c>…`)
|
||||
to the hyperhive control socket; the full event stays
|
||||
unread server-side until `read_room` consumes it.
|
||||
|
||||
An **empty** set is an agent with no matrix at all: none of those
|
||||
three exist. That is the state an agent reaches by having no
|
||||
homeserver (`services.hyperhive.agent.matrix.url = null`) and no
|
||||
account of its own, and it replaces the removed
|
||||
`services.hyperhive.agent.matrix.enable = false`.
|
||||
|
||||
The **hive-internal account is the primary whenever it exists**: it
|
||||
is named `main`, and this module declares it for you from
|
||||
The **hive-internal account is always present and is the primary**:
|
||||
it is named `main`, and this module declares it for you (when
|
||||
`services.hyperhive.agent.matrix.enable` is set) from
|
||||
`services.hyperhive.agent.matrix.url` + `<state>/matrix-token` +
|
||||
`<state>/matrix-sdk-state` — so it is present exactly when that URL
|
||||
is non-null, which on a real hive is always (hive-c0re renders it
|
||||
per agent). It is the account a tool call acts as when it omits
|
||||
`account`. It is an ordinary entry of this option
|
||||
`<state>/matrix-sdk-state`. It is the account a tool call acts as
|
||||
when it omits `account`. It is an ordinary entry of this option
|
||||
like any other, so it shows up in the account list --- what you
|
||||
add here are the *further* accounts (e.g. an external
|
||||
public-matrix account). Its `tokenFile` stays pinned to
|
||||
|
|
@ -181,7 +160,8 @@ in
|
|||
Leave it alone (the default) for the common single-account case:
|
||||
the agent then has only `main`. The whole set is serialized to the
|
||||
daemon's `HIVE_MATRIX_ACCOUNTS` environment variable, `main`
|
||||
first.
|
||||
first. Declaring extras requires
|
||||
`services.hyperhive.agent.matrix.enable` (there is no `main` to extend otherwise).
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
@ -205,26 +185,29 @@ in
|
|||
|
||||
config = {
|
||||
assertions = [
|
||||
# The "extras require `matrix.enable`" assertion that used to head this
|
||||
# list is gone with the option: a non-empty account set is now what
|
||||
# enables matrix, so the condition it checked has become the definition
|
||||
# of the thing it was checking. An operator declaring only an external
|
||||
# account, with no hive homeserver, is a config this module now renders
|
||||
# rather than rejects — matrix on, no `main`.
|
||||
#
|
||||
# `main` is not a forbidden key --- this module declares it
|
||||
# Extra matrix accounts only make sense alongside the hive-internal
|
||||
# `main` account they extend, which exists only when matrix is
|
||||
# enabled.
|
||||
{
|
||||
assertion =
|
||||
config.services.hyperhive.agent.matrixAccounts == { }
|
||||
|| config.services.hyperhive.agent.matrix.enable;
|
||||
message =
|
||||
"services.hyperhive.agent.matrixAccounts requires services.hyperhive.agent.matrix.enable = true "
|
||||
+ "(the extras extend the hive-internal `main` account, which only "
|
||||
+ "exists when matrix is enabled).";
|
||||
}
|
||||
# `main` is no longer a forbidden key --- this module declares it
|
||||
# itself (see the `matrixAccounts.main` definition below), so the
|
||||
# name must be allowed. What stays rejected is retargeting *its
|
||||
# token file*: hive-c0re writes the hive-internal account's token
|
||||
# to `<state>/matrix-token` and nowhere else, so an override there
|
||||
# is an account that evaluates fine and then never restores. The
|
||||
# other two fields are free to override (a `mkDefault` each).
|
||||
#
|
||||
# Guarded on `? main` rather than on an enable flag: `main` is absent
|
||||
# whenever `matrix.url` is null, and an unguarded `.main.tokenFile`
|
||||
# would throw on exactly those agents instead of passing vacuously.
|
||||
{
|
||||
assertion = !(accounts ? main) || accounts.main.tokenFile == "${stateDir}/matrix-token";
|
||||
assertion =
|
||||
!config.services.hyperhive.agent.matrix.enable
|
||||
|| config.services.hyperhive.agent.matrixAccounts.main.tokenFile == "${stateDir}/matrix-token";
|
||||
message =
|
||||
"services.hyperhive.agent.matrixAccounts.main.tokenFile must stay "
|
||||
+ "\"${stateDir}/matrix-token\" --- that is where hive-c0re provisions the "
|
||||
|
|
@ -239,7 +222,7 @@ in
|
|||
# `matrix-catgirl-token`) is caught at build time, not silently.
|
||||
{
|
||||
assertion = lib.all (a: lib.hasPrefix "matrix-token" (baseNameOf a.tokenFile)) (
|
||||
lib.attrValues accounts
|
||||
lib.attrValues config.services.hyperhive.agent.matrixAccounts
|
||||
);
|
||||
message =
|
||||
"every services.hyperhive.agent.matrixAccounts.<name>.tokenFile basename must start with "
|
||||
|
|
@ -247,7 +230,9 @@ in
|
|||
+ "(matrix-token* in the agent's state dir) picks it up live. Offending: "
|
||||
+ lib.concatStringsSep ", " (
|
||||
lib.mapAttrsToList (n: a: "${n}=${baseNameOf a.tokenFile}") (
|
||||
lib.filterAttrs (_n: a: !lib.hasPrefix "matrix-token" (baseNameOf a.tokenFile)) accounts
|
||||
lib.filterAttrs (
|
||||
_n: a: !lib.hasPrefix "matrix-token" (baseNameOf a.tokenFile)
|
||||
) config.services.hyperhive.agent.matrixAccounts
|
||||
)
|
||||
)
|
||||
+ ".";
|
||||
|
|
@ -261,29 +246,19 @@ in
|
|||
# operator can retarget e.g. the homeserver without a
|
||||
# conflicting-definition error (the token file is pinned by an
|
||||
# assertion above, since hive-c0re owns that path).
|
||||
#
|
||||
# ⚠️ Gated on the homeserver URL, and that gate is what keeps `matrixEnabled`
|
||||
# from being trivially true for every agent in the hive. A `main` with no
|
||||
# homeserver is an account the daemon can never log in as, so declaring one
|
||||
# unconditionally would enable matrix everywhere and inject a tool surface
|
||||
# backed by a permanently no-opping daemon. On a real hive hive-c0re renders
|
||||
# this URL per agent (meta.rs's `FORWARDED_VAR_OPTIONS`), so the common case
|
||||
# is still "every agent has `main`".
|
||||
services.hyperhive.agent.matrixAccounts =
|
||||
lib.mkIf (config.services.hyperhive.agent.matrix.url != null)
|
||||
{
|
||||
main = {
|
||||
tokenFile = lib.mkDefault "${stateDir}/matrix-token";
|
||||
sessionDir = lib.mkDefault "${stateDir}/matrix-sdk-state";
|
||||
homeserver = lib.mkDefault config.services.hyperhive.agent.matrix.url;
|
||||
};
|
||||
};
|
||||
services.hyperhive.agent.matrixAccounts = lib.mkIf config.services.hyperhive.agent.matrix.enable {
|
||||
main = {
|
||||
tokenFile = lib.mkDefault "${stateDir}/matrix-token";
|
||||
sessionDir = lib.mkDefault "${stateDir}/matrix-sdk-state";
|
||||
homeserver = lib.mkDefault config.services.hyperhive.agent.matrix.url;
|
||||
};
|
||||
};
|
||||
|
||||
# Auto-inject the matrix MCP entry alongside the bash entry from
|
||||
# ./mcp.nix. `lib.mkDefault` so the operator's own agent.nix can
|
||||
# override it. Points at the daemon's own persistent
|
||||
# streamable-http listener — no stdio bridge, no per-turn spawn.
|
||||
services.hyperhive.agent.extraMcpServers = lib.mkIf matrixEnabled {
|
||||
services.hyperhive.agent.extraMcpServers = lib.mkIf config.services.hyperhive.agent.matrix.enable {
|
||||
matrix = lib.mkDefault {
|
||||
type = "http";
|
||||
url = "http://127.0.0.1:${toString config.services.hyperhive.agent.mcp.matrixHttpPort}/mcp";
|
||||
|
|
@ -296,7 +271,7 @@ in
|
|||
# signals on incoming room events via `/run/hive/mcp.sock`. See
|
||||
# `docs/agent-lifecycle/persistence.md::Matrix per-agent daemon + token-arrival
|
||||
# trigger` for the first-boot-ordering rationale.
|
||||
systemd.services.hive-matrix-daemon = lib.mkIf matrixEnabled {
|
||||
systemd.services.hive-matrix-daemon = lib.mkIf config.services.hyperhive.agent.matrix.enable {
|
||||
description = "long-running matrix-sdk Client + MCP daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
before = [ "hive-agent.service" ];
|
||||
|
|
@ -329,10 +304,7 @@ in
|
|||
# synthesizing it from the per-agent paths only when this JSON
|
||||
# carries no `main` --- which is how an agent whose harness
|
||||
# predates this entry keeps working.
|
||||
#
|
||||
# Unconditional, not `optionalAttrs (accounts != {})`: a non-empty set is
|
||||
# what generated this unit at all, so the guard could only ever be true.
|
||||
// {
|
||||
// lib.optionalAttrs (config.services.hyperhive.agent.matrixAccounts != { }) {
|
||||
HIVE_MATRIX_ACCOUNTS = builtins.toJSON (
|
||||
lib.mapAttrsToList (
|
||||
name: a:
|
||||
|
|
@ -342,7 +314,7 @@ in
|
|||
state_dir = a.sessionDir;
|
||||
}
|
||||
// lib.optionalAttrs (a.homeserver != null) { inherit (a) homeserver; }
|
||||
) accounts
|
||||
) config.services.hyperhive.agent.matrixAccounts
|
||||
);
|
||||
}
|
||||
# Rasterized agent icon path for the daemon's avatar sync. Only set
|
||||
|
|
@ -375,7 +347,7 @@ in
|
|||
# the daemon would exit 0 silently on first boot and the MCP
|
||||
# would have no backend until next restart. See
|
||||
# `docs/agent-lifecycle/persistence.md` (same section as above).
|
||||
systemd.paths.hive-matrix-daemon = lib.mkIf matrixEnabled {
|
||||
systemd.paths.hive-matrix-daemon = lib.mkIf config.services.hyperhive.agent.matrix.enable {
|
||||
description = "trigger hive-matrix-daemon when a matrix token appears";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
# `matrix-token*` (not just `matrix-token`) so a secondary
|
||||
|
|
|
|||
|
|
@ -273,8 +273,7 @@ in
|
|||
# Auto-inject the built-in bash MCP server — always present, every
|
||||
# agent needs bash tools. `lib.mkDefault` so the operator's own
|
||||
# agent.nix can override the entry. (The matrix sibling lives in
|
||||
# ./matrix.nix, gated on that agent having a
|
||||
# services.hyperhive.agent.matrixAccounts entry at all.) `hive-bash-daemon`
|
||||
# ./matrix.nix, gated on services.hyperhive.agent.matrix.enable.) `hive-bash-daemon`
|
||||
# serves its MCP tools directly over streamable-http (no stdio bridge,
|
||||
# no round-trip socket) — see the `hive-bash-daemon` service below.
|
||||
services.hyperhive.agent.extraMcpServers.bash = lib.mkDefault {
|
||||
|
|
@ -285,9 +284,8 @@ in
|
|||
|
||||
# Auto-inject the subagent MCP server — default-on for every agent for
|
||||
# now (operator's call: "default on for now, should be a capability
|
||||
# later" — unconditional, where `matrix.nix`'s sibling entry appears only
|
||||
# for an agent that has a matrix account to serve).
|
||||
# `lib.mkDefault` so an agent.nix can still override/disable
|
||||
# later" — not gated behind an enable option yet, unlike `matrix.nix`'s
|
||||
# pattern). `lib.mkDefault` so an agent.nix can still override/disable
|
||||
# the entry in the meantime.
|
||||
services.hyperhive.agent.extraMcpServers.subagent = lib.mkDefault {
|
||||
type = "http";
|
||||
|
|
|
|||
|
|
@ -119,10 +119,10 @@
|
|||
[ "hyperhive" "logs" "queryUrl" ]
|
||||
[ "services" "hyperhive" "agent" "logs" "queryUrl" ]
|
||||
)
|
||||
# `matrix.enable` is deliberately absent from this list: the option it
|
||||
# renamed to no longer exists, and a rename pointing at an undeclared
|
||||
# option breaks evaluation for everyone. Both of its spellings are
|
||||
# `mkRemovedOptionModule` in ./default.nix instead.
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "hyperhive" "matrix" "enable" ]
|
||||
[ "services" "hyperhive" "agent" "matrix" "enable" ]
|
||||
)
|
||||
(lib.mkRenamedOptionModule
|
||||
[ "hyperhive" "matrix" "url" ]
|
||||
[ "services" "hyperhive" "agent" "matrix" "url" ]
|
||||
|
|
|
|||
|
|
@ -611,29 +611,6 @@ let
|
|||
# RAM percentage and so hands the module no byte count to size against.
|
||||
agentCapped = agent { claudeMemoryMaxBytes = 8589934592; };
|
||||
agentUncapped = agent { };
|
||||
|
||||
# Matrix's enable signal, which is the account set itself — there is no
|
||||
# `matrix.enable` option left to read. Three arms, because the property has
|
||||
# three distinct shapes and only one of them is the common case:
|
||||
#
|
||||
# - a homeserver URL, which is what the module turns into a `main` account;
|
||||
# - neither URL nor operator account, the state that replaced
|
||||
# `matrix.enable = false`. ⚠️ **This is the arm that matters.** `main` is
|
||||
# declared by the module itself, so "any account declared" would be
|
||||
# trivially true — and matrix would render for every agent in every hive —
|
||||
# the moment that declaration stops being gated on the URL. Nothing else in
|
||||
# this suite would notice;
|
||||
# - an operator account carrying its own homeserver and no hive one, which is
|
||||
# matrix on with no `main` at all.
|
||||
agentMatrix = agent { matrix.url = "https://chat.t.local"; };
|
||||
agentNoMatrix = agent { };
|
||||
agentMatrixExternalOnly = agent {
|
||||
matrixAccounts.ccc = {
|
||||
tokenFile = "/agents/a1/state/matrix-token-ccc";
|
||||
sessionDir = "/agents/a1/state/matrix-sdk-state-ccc";
|
||||
homeserver = "https://matrix.example.invalid";
|
||||
};
|
||||
};
|
||||
agentHarness = machine: machine.systemd.services.hive-agent;
|
||||
agentSubagentDaemon = machine: machine.systemd.services.hive-subagent-daemon;
|
||||
agentSettings = machine: machine.services.opentelemetry-collector.settings;
|
||||
|
|
@ -1816,62 +1793,6 @@ let
|
|||
name = "one subagent losing the OOM draw does not stop the daemon";
|
||||
ok = (agentSubagentDaemon agentUncapped).serviceConfig.OOMPolicy == "continue";
|
||||
}
|
||||
{
|
||||
# A homeserver URL is the whole input: from it the module derives the
|
||||
# hive-internal `main` account, and from a non-empty account set the three
|
||||
# things that used to hang off `matrix.enable`.
|
||||
name = "an agent with a homeserver gets a main account and the matrix units";
|
||||
ok =
|
||||
let
|
||||
a = agentMatrix.services.hyperhive.agent.matrixAccounts;
|
||||
in
|
||||
lib.attrNames a == [ "main" ]
|
||||
&& a.main.tokenFile == "/agents/a1/state/matrix-token"
|
||||
&& a.main.homeserver == "https://chat.t.local"
|
||||
&& agentMatrix.systemd.services ? hive-matrix-daemon
|
||||
&& agentMatrix.systemd.paths ? hive-matrix-daemon
|
||||
&& agentMatrix.services.hyperhive.agent.extraMcpServers ? matrix;
|
||||
}
|
||||
{
|
||||
# The absence arm, and the reason the enable signal is not vacuous. An
|
||||
# agent the hive gave no homeserver, whose operator declared nothing, must
|
||||
# come out with an EMPTY account set — not a `main` that can never log in
|
||||
# — and therefore with none of the three. Assert the emptiness itself and
|
||||
# not just the units: it is the account set that is load-bearing now, and
|
||||
# a `main` sneaking back in is the regression this case exists to name.
|
||||
name = "an agent with no homeserver and no declared account gets no matrix at all";
|
||||
ok =
|
||||
agentNoMatrix.services.hyperhive.agent.matrixAccounts == { }
|
||||
&& !(agentNoMatrix.systemd.services ? hive-matrix-daemon)
|
||||
&& !(agentNoMatrix.systemd.paths ? hive-matrix-daemon)
|
||||
&& !(agentNoMatrix.services.hyperhive.agent.extraMcpServers ? matrix);
|
||||
}
|
||||
{
|
||||
# Matrix without a hive homeserver: one operator account, its own
|
||||
# homeserver, no `main`. Under the deleted `matrix.enable` this config was
|
||||
# an assertion failure ("extras require enable") even though every account
|
||||
# in it was complete; the account set being the signal is what makes it
|
||||
# expressible, and the serialized env var is where that has to show up.
|
||||
name = "an external-only account enables matrix with no main entry";
|
||||
ok =
|
||||
let
|
||||
accts = agentMatrixExternalOnly.services.hyperhive.agent.matrixAccounts;
|
||||
env = agentMatrixExternalOnly.systemd.services.hive-matrix-daemon.environment;
|
||||
in
|
||||
lib.attrNames accts == [ "ccc" ]
|
||||
&& !(accts ? main)
|
||||
&&
|
||||
builtins.fromJSON env.HIVE_MATRIX_ACCOUNTS == [
|
||||
{
|
||||
name = "ccc";
|
||||
token_file = "/agents/a1/state/matrix-token-ccc";
|
||||
state_dir = "/agents/a1/state/matrix-sdk-state-ccc";
|
||||
homeserver = "https://matrix.example.invalid";
|
||||
}
|
||||
]
|
||||
# No hive homeserver, so nothing may claim one.
|
||||
&& !(env ? HIVE_MATRIX_URL);
|
||||
}
|
||||
{
|
||||
# The doctrine three glue files state, as a property a rewrite has to
|
||||
# keep: a client is defined by holding a certificate the store accepts,
|
||||
|
|
|
|||
Loading…
Reference in a new issue