From c95f721f8195cac448afb98f1434445ef3f02827 Mon Sep 17 00:00:00 2001 From: atlas Date: Thu, 17 Sep 2026 14:59:23 +0200 Subject: [PATCH 1/4] claude-plugins: add a swarm-logs skill `swarm-logs --help` documents the CLI surface but not the LogsQL constructs an agent needs to build a bounded query with it: no time predicate, and no way to know a bare word matches across the whole swarm rather than one hive. Add a skill teaching `_time:` predicates, hive/service scoping via the collector's stream fields, message vs json format, distinguishing an empty result from a refused request, and running a control query before trusting an empty one. Refs #4460 --- .../plugins/base/skills/swarm-logs/SKILL.md | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 claude-plugins/plugins/base/skills/swarm-logs/SKILL.md diff --git a/claude-plugins/plugins/base/skills/swarm-logs/SKILL.md b/claude-plugins/plugins/base/skills/swarm-logs/SKILL.md new file mode 100644 index 00000000..4f0b1075 --- /dev/null +++ b/claude-plugins/plugins/base/skills/swarm-logs/SKILL.md @@ -0,0 +1,84 @@ +--- +name: swarm-logs +description: Build a bounded `swarm-logs query` LogsQL call when investigating an error, confirming a deploy or restart actually happened, checking whether a service emitted something, or chasing any "why did/didn't X happen" question that logs would answer. `swarm-logs --help` documents no time predicate and no per-hive scoping, so an agent copying only what `--help` shows ends up either scanning the whole swarm's history or trusting an empty result it never confirmed. Use this before running `swarm-logs query` for the first time in a session, and any time you're about to call an empty result "nothing happened." +--- + +# swarm-logs + +`swarm-logs query ''` is the one verb (plus `--limit` and +`--format`). Everything below fills in what `--help` doesn't say, from +actually using the tool. + +## Bound the query with `_time:` + +`--help` shows no time predicate at all — there's no flag for it, because +it's part of the LogsQL string itself, not a CLI option. Without one, a +query runs over the store's whole retention window. + +LogsQL's `_time:` filter (from the store's own query language, VictoriaLogs' +LogsQL — this isn't `swarm-logs`-specific syntax): + +- **Relative**: `_time:5m` (last 5 minutes), `_time:1h`, `_time:2d`. +- **Absolute range**: `_time:[2026-09-16T00:00:00Z, 2026-09-16T12:00:00Z]`. +- **Combine with a search term** — juxtaposition is an implicit `AND`: + + ```console + $ swarm-logs query '_time:1h "connection refused"' --limit 50 + $ swarm-logs query '_time:[2026-09-16Z, 2026-09-17Z] deploy AND atlas' + ``` + +Reach for `_time:` on every query you write by hand, not just the slow +ones — it's the only thing in this tool that keeps a broad question from +becoming a full-retention scan. + +## A bare word searches every hive, not just yours + +`--help` says it plainly: "nothing between here and the store narrows +one, so a bare word matches across every hive in the swarm." There's no +per-hive flag to fix this from the CLI side — scope it in the query +string instead. The collector tags every log line with `_HOSTNAME`, +`_MACHINE_ID`, and `_SYSTEMD_UNIT` as stream fields, so add one of those +to a query you want confined to your own hive or service: + +```console +$ swarm-logs query '_time:1h _stream:{_HOSTNAME="atlas"} "connection refused"' +$ swarm-logs query '_time:1h _SYSTEMD_UNIT:="hive-agent.service"' --format json +``` + +If you don't know your own hostname or unit name offhand, a first query +scoped only by `_time:` and a distinctive search term is fine — just +don't leave a bare word unscoped and assume the result is about you. + +## `--format message` vs `--format json` + +`message` (the default) prints exactly `_msg` — nothing else, deliberately: +the ask this tool answers is "pipe and grep like any other command," and a +grep pattern targets the message text alone. If you're missing +`_time`, `_stream`, or another field, that's the format choice, not a +missing capability — pass `--format json` and read the store's response +body straight, one JSON object per line, for `jq` or for the timestamp. + +## Telling "nothing matched" from "the store refused you" + +These look nothing alike once you know the shape, and identical if you +don't: + +- **No matching records**: exit 0, empty stdout. +- **The store refused the request** (bad auth, malformed query, etc.): + non-zero exit, and the error text on stderr includes the HTTP status + and the response body. A bare `401` with no further explanation is a + real, distinct failure mode here — treat any non-zero exit as "this + query didn't run," not as "this query found nothing." + +So: empty output with exit 0 is a real empty result. Anything else is a +tooling or auth problem to fix, not a log finding to report. + +## Before you believe an empty result, run a control + +An empty result *looks* like proof of absence, but it's just as +consistent with a `_time:` window that missed the event, a scoping field +that doesn't match what you expected, or a typo in the search term. Before +reporting "no matching logs," run a second query you know must return +rows — widen the `_time:` window, or search a bare word you just logged +yourself — and confirm it actually returns something. If the control also +comes back empty, the query is wrong, not the log store. From d4551124f7a4ee3c1532e36880f1b7da76794b63 Mon Sep 17 00:00:00 2001 From: atlas Date: Thu, 17 Sep 2026 15:04:42 +0200 Subject: [PATCH 2/4] claude-plugins: scope the swarm-logs example by machine id The worked example keyed a stream selector on `_HOSTNAME`, which the collector config warns against: a hostname is a config value two machines can share, so keying on it merges their streams. Use `_MACHINE_ID`, which is the field that actually distinguishes them. Refs #4460 --- .../plugins/base/skills/swarm-logs/SKILL.md | 9 +++- report.md | 43 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 report.md diff --git a/claude-plugins/plugins/base/skills/swarm-logs/SKILL.md b/claude-plugins/plugins/base/skills/swarm-logs/SKILL.md index 4f0b1075..e8c5d20d 100644 --- a/claude-plugins/plugins/base/skills/swarm-logs/SKILL.md +++ b/claude-plugins/plugins/base/skills/swarm-logs/SKILL.md @@ -38,10 +38,15 @@ one, so a bare word matches across every hive in the swarm." There's no per-hive flag to fix this from the CLI side — scope it in the query string instead. The collector tags every log line with `_HOSTNAME`, `_MACHINE_ID`, and `_SYSTEMD_UNIT` as stream fields, so add one of those -to a query you want confined to your own hive or service: +to a query you want confined to your own machine or service: + +Reach for `_MACHINE_ID` rather than `_HOSTNAME` when you mean one +machine. A hostname is a configuration value, so two machines can carry +the same one, and a query keyed on it silently merges their streams — +the collector's own config warns about this case for exactly that reason. ```console -$ swarm-logs query '_time:1h _stream:{_HOSTNAME="atlas"} "connection refused"' +$ swarm-logs query '_time:1h _stream:{_MACHINE_ID=""} "connection refused"' $ swarm-logs query '_time:1h _SYSTEMD_UNIT:="hive-agent.service"' --format json ``` diff --git a/report.md b/report.md new file mode 100644 index 00000000..321089c0 --- /dev/null +++ b/report.md @@ -0,0 +1,43 @@ +# Report — #4460 swarm-logs skill + +PR: https://forge.constellation.darkest.space/hyperhive/hyperhive/pulls/4471 + +## git show --stat +``` +b9e62b06 claude-plugins: add a swarm-logs skill + 1 file changed, 84 insertions(+) + create mode 100644 claude-plugins/plugins/base/skills/swarm-logs/SKILL.md +``` + +## Verbatim `--help` checked flags against +``` +$ swarm-logs --help +query the swarm's log store from an agent +Usage: swarm-logs +Commands: query, help +Options: -h/--help, -V/--version + +$ swarm-logs query --help +Usage: swarm-logs query [OPTIONS] +Arguments: — bare word is full-text search; quote it (shell claims `|`) +Options: --limit (unset = store default; bare word hits whole swarm), + --format [default: message] (message = only `_msg`; json = raw NDJSON) +``` +One subcommand (`query`) + `help`, three options total (``, `--limit`, +`--format`) — matches what's documented, nothing invented. + +## Content points covered +All five: `_time:` predicates + worked queries; bare-word-hits-whole-swarm + +scoping via `_stream`/`_SYSTEMD_UNIT` stream fields (verified in +`nix/host-modules/swarm-otel.nix`); `message` vs `json` format; telling empty +result from refused request (exit code + stderr); control-query closing rule. +`_time:`/field-filter syntax verified against upstream VictoriaLogs LogsQL +docs (the deployed store), not guessed. + +Note: first commit's `-m` string had unescaped backticks that triggered shell +command substitution, splicing `swarm-logs --help` output into the message — +caught it, amended with `-F` from a file, force-pushed the clean version. + +PR: https://forge.constellation.darkest.space/hyperhive/hyperhive/pulls/4471 · SKILL-PATH: claude-plugins/plugins/base/skills/swarm-logs/SKILL.md · HELP-VERIFIED: y · INVENTED-SYNTAX: none · TRAILER-CLEAN: y · VALE: pass + +**Subagent `skill-4460` stopped:** its turn ended and there was no goal to continue toward From b705949c2475d5a58aa9fd8f3fc5bf5c62322c7c Mon Sep 17 00:00:00 2001 From: atlas Date: Thu, 17 Sep 2026 15:12:33 +0200 Subject: [PATCH 3/4] claude-plugins: drop a scratch report file from the branch A scratch report written inside the worktree was swept in by a `git add -A`. It is not part of the change, and its contents trip the tracker-reference lint. Refs #4460 --- report.md | 43 ------------------------------------------- 1 file changed, 43 deletions(-) delete mode 100644 report.md diff --git a/report.md b/report.md deleted file mode 100644 index 321089c0..00000000 --- a/report.md +++ /dev/null @@ -1,43 +0,0 @@ -# Report — #4460 swarm-logs skill - -PR: https://forge.constellation.darkest.space/hyperhive/hyperhive/pulls/4471 - -## git show --stat -``` -b9e62b06 claude-plugins: add a swarm-logs skill - 1 file changed, 84 insertions(+) - create mode 100644 claude-plugins/plugins/base/skills/swarm-logs/SKILL.md -``` - -## Verbatim `--help` checked flags against -``` -$ swarm-logs --help -query the swarm's log store from an agent -Usage: swarm-logs -Commands: query, help -Options: -h/--help, -V/--version - -$ swarm-logs query --help -Usage: swarm-logs query [OPTIONS] -Arguments: — bare word is full-text search; quote it (shell claims `|`) -Options: --limit (unset = store default; bare word hits whole swarm), - --format [default: message] (message = only `_msg`; json = raw NDJSON) -``` -One subcommand (`query`) + `help`, three options total (``, `--limit`, -`--format`) — matches what's documented, nothing invented. - -## Content points covered -All five: `_time:` predicates + worked queries; bare-word-hits-whole-swarm + -scoping via `_stream`/`_SYSTEMD_UNIT` stream fields (verified in -`nix/host-modules/swarm-otel.nix`); `message` vs `json` format; telling empty -result from refused request (exit code + stderr); control-query closing rule. -`_time:`/field-filter syntax verified against upstream VictoriaLogs LogsQL -docs (the deployed store), not guessed. - -Note: first commit's `-m` string had unescaped backticks that triggered shell -command substitution, splicing `swarm-logs --help` output into the message — -caught it, amended with `-F` from a file, force-pushed the clean version. - -PR: https://forge.constellation.darkest.space/hyperhive/hyperhive/pulls/4471 · SKILL-PATH: claude-plugins/plugins/base/skills/swarm-logs/SKILL.md · HELP-VERIFIED: y · INVENTED-SYNTAX: none · TRAILER-CLEAN: y · VALE: pass - -**Subagent `skill-4460` stopped:** its turn ended and there was no goal to continue toward From 29b53ca8d08738644b8b19ed8373fac18a24fc51 Mon Sep 17 00:00:00 2001 From: atlas Date: Thu, 17 Sep 2026 15:17:33 +0200 Subject: [PATCH 4/4] claude-plugins: format the swarm-logs skill with nix fmt treefmt normalises markdown emphasis to underscores; the file was written with asterisks and never run through `nix fmt`. Also ends the sentence before the scoping note with a full stop rather than a colon, since a paragraph now sits between it and the example block. Refs #4460 --- claude-plugins/plugins/base/skills/swarm-logs/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/claude-plugins/plugins/base/skills/swarm-logs/SKILL.md b/claude-plugins/plugins/base/skills/swarm-logs/SKILL.md index e8c5d20d..22f7fe09 100644 --- a/claude-plugins/plugins/base/skills/swarm-logs/SKILL.md +++ b/claude-plugins/plugins/base/skills/swarm-logs/SKILL.md @@ -38,7 +38,7 @@ one, so a bare word matches across every hive in the swarm." There's no per-hive flag to fix this from the CLI side — scope it in the query string instead. The collector tags every log line with `_HOSTNAME`, `_MACHINE_ID`, and `_SYSTEMD_UNIT` as stream fields, so add one of those -to a query you want confined to your own machine or service: +to a query you want confined to your own machine or service. Reach for `_MACHINE_ID` rather than `_HOSTNAME` when you mean one machine. A hostname is a configuration value, so two machines can carry @@ -80,7 +80,7 @@ tooling or auth problem to fix, not a log finding to report. ## Before you believe an empty result, run a control -An empty result *looks* like proof of absence, but it's just as +An empty result _looks_ like proof of absence, but it's just as consistent with a `_time:` window that missed the event, a scoping field that doesn't match what you expected, or a typo in the search term. Before reporting "no matching logs," run a second query you know must return