docs: move the agent tool docs to 4-implementation/agent-tools
This commit is contained in:
parent
df8a82bc33
commit
aa227a95c1
8 changed files with 94 additions and 83 deletions
|
|
@ -1,11 +1,8 @@
|
|||
# Tools
|
||||
|
||||
`hivectl` is _your_ tool — the operator's own host CLI. Everything
|
||||
else here documents the tool surface your **agents** get inside their
|
||||
containers (the MCP tools an agent's own claude session can call).
|
||||
You never call these directly, but they're the reference for what an
|
||||
agent can actually do — useful when you're trying to understand or
|
||||
debug agent behavior.
|
||||
`hivectl` and `swarmctl` are _your_ tools — the operator's own CLIs. The
|
||||
tool surface your agents get inside their containers lives in
|
||||
[`4-implementation/agent-tools/`](../../4-implementation/agent-tools/README.md).
|
||||
|
||||
## For the operator
|
||||
|
||||
|
|
@ -25,27 +22,3 @@ debug agent behavior.
|
|||
(authelia's subject store, edited in place) and `agent create`
|
||||
(queues the swarm-controller's creation job graph). No curated guide
|
||||
yet; add one here if/when that grows.
|
||||
- **[swarm-logs-cli](swarm-logs-cli.md)** — the exhaustive, autogenerated
|
||||
flag-by-flag reference for `swarm-logs`, kept in lockstep with the
|
||||
binary by CI. No curated guide.
|
||||
- **[swarm-metrics-cli](swarm-metrics-cli.md)** — the exhaustive,
|
||||
autogenerated flag-by-flag reference for `swarm-metrics`, kept in
|
||||
lockstep with the binary by CI. No curated guide.
|
||||
|
||||
## What your agents can do
|
||||
|
||||
- **[bash](bash.md)** — background shell execution (`mcp__bash__*`),
|
||||
available on every agent unconditionally.
|
||||
- **[subagent](subagent.md)** — spawn nested headless claude sessions
|
||||
(`mcp__subagent__{start,continue,status,interrupt}`), shipped
|
||||
default-on for every agent today alongside `bash` (expected to become a
|
||||
real opt-in capability later).
|
||||
- **[forge](forge.md)** — the `hive-forge` Forgejo CLI every agent has
|
||||
for issues, PRs, and comments. Not an MCP tool — a binary agents
|
||||
shell out to instead of ad-hoc curl.
|
||||
- **[forge-cli](forge-cli.md)** — the exhaustive, autogenerated
|
||||
flag-by-flag reference for `hive-forge`, kept in lockstep with the
|
||||
binary by CI the same way `hivectl-cli.md` is.
|
||||
- **[matrix](matrix.md)** — the matrix MCP tool surface
|
||||
(`mcp__matrix__*`) for agents with a matrix account, multiple
|
||||
accounts per agent, and declaring extra MCP servers generally.
|
||||
|
|
|
|||
|
|
@ -1,91 +0,0 @@
|
|||
# Bash execution tools
|
||||
|
||||
For the operator: this page covers the daemon as deployed and what the tools do
|
||||
around the edges. How an agent calls them is in the tools' own MCP descriptions.
|
||||
|
||||
Background shell execution via `hive-bash-daemon`. Tools land as
|
||||
`mcp__bash__<tool>` (the MCP server name is `bash`, not `hyperhive`).
|
||||
`nix/agent-modules/mcp.nix` registers bash into `services.hyperhive.agent.extraMcpServers`
|
||||
by default (`lib.mkDefault`, with `allowedTools = ["*"]`) — an `agent.nix`
|
||||
can override or drop the entry, so this is a default, not an
|
||||
unconditional grant. Even where the entry is present, the server is only
|
||||
actually rendered into an agent's claude config when the agent also
|
||||
holds the **`execution`** permission: `extra_server_required_permission` in
|
||||
`hive-agent/src/mcp_config.rs` suppresses it entirely without that
|
||||
permission, so an agent lacking `execution` gets no `mcp__bash__*` tools at
|
||||
all, not just an unused grant. The harness rewrites the config each turn,
|
||||
so a change applies at the next one. See "Relationship to the
|
||||
`execution` permission" below.
|
||||
|
||||
## Tools
|
||||
|
||||
Each tool's parameters and return values are in its own MCP description, which
|
||||
is what an agent reads at call time. This section says what the tools do around
|
||||
the edges.
|
||||
|
||||
- **`run`** submits a shell command (through `bash`) as a background task. Its
|
||||
stdout and stderr stream to `harness/bash-tasks/<id>.{out,err}`. When the
|
||||
task finishes, times out or its process errors, it surfaces as a todo in the
|
||||
agent's loose-ends, with the exit code and a pointer to the captured output.
|
||||
`run` waits up to 3 seconds (30 at most) for a result before it returns; a
|
||||
result delivered that way makes no todo. A caller-chosen `name` replaces the
|
||||
generated id. The daemon accepts the name again once its task has finished,
|
||||
and rejects it while the task is still pending or running.
|
||||
- **`status`** polls a task: its state (`pending`, `running`, `done`,
|
||||
`timed_out`, `interrupted` or `killed`), the exit code, the run time and the last
|
||||
4 KiB of each stream. A call that sees a finished task clears its completion
|
||||
todo, since the agent already has the result; in a narrow race both can still
|
||||
appear. A task marked `interrupted` had its process killed by a harness
|
||||
restart, and a best-effort todo is still made so the agent isn't blocked
|
||||
silently.
|
||||
- **`kill`** stops a running or pending task and returns without waiting. The
|
||||
completion surfaces as a todo like any other. It signals the task's whole
|
||||
process group, so children the shell started stop too: SIGINT by default,
|
||||
SIGKILL with `force`. The daemon cancels a task still pending before it
|
||||
starts, and the task ends as `killed`.
|
||||
|
||||
## Namespace note
|
||||
|
||||
`run` and `status` live in the `bash` MCP server, not `hyperhive`. The
|
||||
tool names in claude are `mcp__bash__run` and `mcp__bash__status`.
|
||||
hyperhive blocks the `Bash` built-in tool — all shell execution goes through
|
||||
this structured path so tasks get task-id tracking and structured output.
|
||||
|
||||
## Architecture
|
||||
|
||||
`hive-bash-daemon` is a single long-running process (one per agent
|
||||
container, systemd service in `nix/agent-modules/mcp.nix`) — no stdio
|
||||
bridge. It owns subprocess management, output file writing, todo delivery
|
||||
on the harness's in-agent socket, **and** serves the `run`/`status`/`kill`
|
||||
MCP tools directly over streamable-http on `services.hyperhive.agent.mcp.bashHttpPort`
|
||||
(declared in `services.hyperhive.agent.extraMcpServers.bash` as `{ type = "http"; url =
|
||||
...; }`). Same shape as the built-in `hyperhive` surface (`hive-mcp-http`)
|
||||
— claude reconnects to the stable URL every turn instead of respawning a
|
||||
stdio child, so there's no per-turn MCP re-registration race and no
|
||||
round-trip socket hop for tool calls.
|
||||
|
||||
### Completion as a todo (loose-ends v2)
|
||||
|
||||
When a bash task changes state, `hive-bash-daemon` upserts a single keyed
|
||||
todo (`key = task id`) on the harness's in-agent socket (`HIVE_AGENT_SOCKET`)
|
||||
— "running" at start, then the completion summary when it finishes. The
|
||||
summary change signals the harness turn loop directly (in-process, no broker
|
||||
round-trip), so the harness drives a turn for the agent to handle it via
|
||||
`get_loose_ends`, then clears the todo with `cancel_loose_end(kind: "todo", id: N)` (dials the
|
||||
in-container socket directly — no bash task involved, so clearing doesn't
|
||||
spawn another todo). Same mechanism the matrix daemon uses for
|
||||
unread rooms. An inline `wait_seconds` / `status` observation that already
|
||||
delivered the result instead clears the keyed todo, so no redundant
|
||||
loose-end follows.
|
||||
|
||||
## Relationship to the `execution` permission
|
||||
|
||||
`Permission::Execution` (in `Permission::DEFAULT`) is the real gate on bash:
|
||||
`extra_server_required_permission` (in `hive-agent/src/mcp_config.rs`)
|
||||
controls whether the `bash` MCP server reaches the agent's claude
|
||||
config at all, suppressing every `mcp__bash__*` tool
|
||||
when absent — not just leaving them unused. Its own `tools()` returns
|
||||
`&[]`: `mcp__bash__run`/`status`/`kill` live on the separate `bash` MCP
|
||||
server that `extra_server_required_permission` gates, not on the
|
||||
`mcp__hyperhive__*` allowlist `tools()` builds. Removing `execution`
|
||||
from an agent's permissions **does** remove bash availability.
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,376 +0,0 @@
|
|||
# hive-forge CLI
|
||||
|
||||
`hive-forge` is the Forgejo API wrapper available in every agent
|
||||
container (installed via `nix/agent-modules/forge.nix`, on `PATH` as a
|
||||
proper Rust binary). Use it instead of ad-hoc curl pipelines.
|
||||
|
||||
This page is the curated guide. For the exhaustive flag-by-flag reference
|
||||
autogenerated from the binary's own command tree, see
|
||||
[`forge-cli.md`](forge-cli.md) — emitted by the hidden `hive-forge
|
||||
markdown-docs` subcommand and kept in lockstep with the code by the
|
||||
`hive-forge-docs` flake check (CI fails if the committed copy drifts).
|
||||
Regenerate with `nix build .#hive-forge && ./result/bin/hive-forge
|
||||
markdown-docs > docs/2-handbook/tools/forge-cli.md`.
|
||||
|
||||
## Credentials and repo defaults
|
||||
|
||||
- Credentials: `$HIVE_FORGE_TOKEN_FILE` (the token the agent fetched from
|
||||
the swarm secret store), else `$HYPERHIVE_STATE_DIR/forge-token`
|
||||
- Active repo resolves, highest priority first: global `-r/--repo` flag
|
||||
> the `origin` remote of the cwd's git checkout > `$HIVE_FORGE_REPO`
|
||||
(last-resort override, unset by default) > a hard error.
|
||||
`client::Client::from_env` assumes no single repo by default.
|
||||
- Deadline: one invocation runs for at most `$HIVE_FORGE_DEADLINE` seconds
|
||||
(default 900, five minutes above the largest per-request budget). Past
|
||||
it, `hive-forge` prints `hive-forge: FAILED: no result after <n>s …` and
|
||||
exits with code 75, so a forge that accepts a connection and then stalls
|
||||
can't block the caller forever. The command stops wherever it was: a write verb
|
||||
may already have reached the forge, so check its result before retrying.
|
||||
|
||||
## Verbs
|
||||
|
||||
**Kind-namespaced commands (preferred):** hive-forge groups issue/PR operations
|
||||
under `issue` and `pr` parent commands — `hive-forge pr close 42`,
|
||||
`hive-forge issue create --title …`, `hive-forge pr status 42`. The
|
||||
`pr <verb>` / `issue <verb>` forms validate the number's kind (for example, `pr close`
|
||||
refuses an issue number). Run
|
||||
`hive-forge pr --help` / `hive-forge issue --help` for the full subcommand
|
||||
list (show/create/edit/status/merge/reviews/commits/diff/view/comment/
|
||||
comments/close/reopen/labels/assign/dependency/reaction/timeline as applicable).
|
||||
|
||||
```bash
|
||||
hive-forge pr close 42 # close a PR (kind-validated)
|
||||
hive-forge pr reopen 42 # reopen a closed PR (kind-validated)
|
||||
hive-forge issue close 42 # close an issue (kind-validated)
|
||||
hive-forge issue reopen 42 # reopen a closed issue (kind-validated)
|
||||
hive-forge pr status 42 # PR health (mergeable / CI / reviews)
|
||||
hive-forge issue create --title "..." --body "..."
|
||||
# --- flat aliases below remain valid (hidden) ---
|
||||
hive-forge view 42 # title + body + comments (marks thread read)
|
||||
hive-forge comments 42 # list all comments (human-readable; marks thread read); for PRs, review bodies merged in
|
||||
hive-forge comments 42 --tail 10 # last 10 comments (fetches the whole thread, shows the tail)
|
||||
hive-forge --json comments 42 # same as above, JSON array (global flag)
|
||||
hive-forge comment 42 --body "..." # post comment (inline body)
|
||||
hive-forge comment 42 --body-file - <<EOF # ...or pipe a HEREDOC
|
||||
multi-line body
|
||||
EOF
|
||||
hive-forge comment 42 --body "..." --force # post even if thread has unread activity
|
||||
hive-forge comment-show 18042 # fetch one comment by id
|
||||
hive-forge comment-edit 18042 --body "..." # edit a comment
|
||||
hive-forge assign 42 damocles
|
||||
hive-forge close 42
|
||||
hive-forge labels 42 add feature
|
||||
hive-forge issue dependency 42 # list issue 42's dependencies (issues it's blocked by)
|
||||
hive-forge issue dependency 42 add 40 41 # issue 42 is now blocked by 40 and 41
|
||||
hive-forge issue dependency 42 remove 40 # drop the dependency on 40
|
||||
hive-forge pr dependency 55 add 42 # same verb, PR-scoped (Forgejo shares the issue/PR index)
|
||||
hive-forge issue reaction 42 # list issue 42's reactions
|
||||
hive-forge issue reaction 42 add heart # react to issue 42 with :heart:
|
||||
hive-forge issue reaction 42 --comment 18042 add +1 # react to a specific comment instead
|
||||
hive-forge issue reaction 42 remove heart # remove your own :heart: reaction
|
||||
hive-forge issue reaction 1 --list-allowed # this instance's actual configured shortcodes
|
||||
hive-forge issue create --title "..." --body "..."
|
||||
hive-forge issue create --title "..." --body "..." --label area/ops --label type/bug # repeatable
|
||||
hive-forge issue edit 42 --title "new title"
|
||||
hive-forge pr edit 42 --title "new title" # same edit, PR-kind-validated
|
||||
hive-forge pr edit 42 --base main # retarget a stacked PR onto main
|
||||
hive-forge pr 42 # PR metadata as JSON
|
||||
hive-forge pr create --title "..." --head my-branch --push # also `git push forge my-branch`
|
||||
hive-forge pr create --title "..." --head my-branch --label area/ops # repeatable, same as issue create
|
||||
hive-forge pr reviews 42 # list reviews; each inline comment as `[#id path:line] text`, tagged (by X) when its poster is not the reviewer / (resolved by X) / (no position)
|
||||
hive-forge pr reviews 42 --only unresolved # every inline comment nobody has resolved yet
|
||||
hive-forge pr timeline 42 # review rows list their line comments (id, path:line, state) too
|
||||
hive-forge pr reviews 42 --approve # submit APPROVED review
|
||||
hive-forge pr reviews 42 --request-changes -m "msg" # submit REQUEST_CHANGES review
|
||||
hive-forge pr reviews 42 --comment -m "msg" # submit COMMENT review
|
||||
hive-forge pr assign-reviewer 42 argus # set argus as a requested reviewer on the PR
|
||||
hive-forge pr assign-reviewer 42 argus --remove # withdraw the request; fails if argus is still listed afterwards (a reviewer who already reviewed stays listed)
|
||||
hive-forge pr assign-committer 42 damocles # add damocles to the PR's assignee list
|
||||
hive-forge pr diff 42 # unified diff (lockfile hunks collapsed by default)
|
||||
hive-forge pr diff 42 --full # include unfiltered lockfile hunks
|
||||
hive-forge list # open issues/PRs
|
||||
hive-forge list --kind pr --state all --page 2 # page 2 of all PRs (walk --page 1,2,… with --limit as page size for a repo-wide sweep)
|
||||
hive-forge list --search "trust bundle" --state all # full-text over title AND body — the duplicate check
|
||||
hive-forge list --milestone 11 --state all # what's left in a milestone (name or id, repeatable)
|
||||
hive-forge milestone # list milestones
|
||||
hive-forge branches deployed/ # filter branches by pattern
|
||||
hive-forge tree-sha main # git tree SHA for a ref
|
||||
hive-forge -r other-org/other-repo pr 7 # target a different repo
|
||||
hive-forge lint unassigned # open issues/PRs with no assignee
|
||||
hive-forge lint no-reviewer # PRs with zero formally requested reviewers
|
||||
hive-forge lint no-reviewer --reviewer argus # PRs where argus specifically isn't a requested reviewer
|
||||
hive-forge lint stale-branches --days 14 # branches with no recent activity, each with its merge outcome (PR #n merged / closed unmerged / no PR)
|
||||
hive-forge lint assignments # per-assignee open item count
|
||||
hive-forge lint unlabeled --scope type # open issues/PRs with no exclusive type/* label (any scope works, e.g. --scope area)
|
||||
hive-forge pr status 42 # PR health: mergeable, CI, reviews, last comment (exit 0 = ready)
|
||||
hive-forge pr status --sha <sha> # CI-only fast path for an explicit commit sha
|
||||
hive-forge pr merge 42 # merge (refuses unless mergeable + CI not red + no changes-requested); deletes head branch
|
||||
hive-forge pr merge 42 --method rebase # rebase-merge instead of a merge commit (no squash option)
|
||||
hive-forge pr merge 42 --keep-branch --force # keep the head branch; override the readiness gate
|
||||
hive-forge timeline 42 # audit trail: closes, label changes, assignments, commit refs
|
||||
hive-forge attach-issue 42 /path/to/file # upload a file attachment to an issue; prints download URL
|
||||
hive-forge attach-comment 18042 /path/to/file # upload a file attachment to a comment; prints download URL
|
||||
hive-forge attachment-get <uuid> # download an attachment; prints resolved path to stdout
|
||||
hive-forge artifact-get pr1ma-paper-pdf --run 51 # download a CI run's Actions artifact zip (run number from the run-page URL)
|
||||
hive-forge ci-log --run 51 # print a CI run's job step logs (run number from the run-page URL); --job i / --step i to narrow
|
||||
hive-forge ci-rerun --branch foo # re-run CI without an empty commit (dispatches a fresh run; --run n also works unless that run was PR-triggered; --pr refuses, see below)
|
||||
hive-forge subscription --watch # subscribe to repo notifications
|
||||
hive-forge subscription --unwatch # unsubscribe
|
||||
hive-forge subscription --list # list every repo you watch (audit the notification firehose)
|
||||
hive-forge -r internal/knowledge clone # clone with creds auto-injected
|
||||
hive-forge -r internal/knowledge pr create --agit --topic foo --title "..." # open PR via AGit (no fork)
|
||||
# --- repo management ---
|
||||
hive-forge repo-create my-repo # create under authenticated user; prints URL
|
||||
hive-forge repo-create my-repo --org myorg --private --auto-init # under an org, private, with initial commit
|
||||
hive-forge repo-add-collaborator alice # add write collaborator (default permission)
|
||||
hive-forge repo-add-collaborator alice --permission read # read-only collaborator
|
||||
hive-forge repo-labels # list every label defined on the repo
|
||||
hive-forge repo-labels bug # filter by name substring
|
||||
hive-forge repo-labels --json # full label objects (id, name, color, description)
|
||||
```
|
||||
|
||||
### Which read verb when
|
||||
|
||||
Multiple verbs read an issue/PR and look overlapping, but each has a
|
||||
distinct output shape — pick by what you need:
|
||||
|
||||
| Need | Verb | Shape |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| The full human view of one item — title, body, **and** every comment in order | `view <n>` | prose dump; **marks the thread read** (clears the read-before-comment guard). Top-level `view` autodetects issue-or-PR by number; scoped `issue view <n>` / `pr view <n>` do the same when you already know the kind |
|
||||
| Just the structured metadata of one item (state, labels, assignees, head sha…) | `issue <n>` / `pr <n>` | JSON object |
|
||||
| Only the comments (skip the body), for example to catch up a long thread; for PRs, review bodies (approve/request-changes/comment summaries) come along too | `comments <n>` [`--tail N`] | comment + review-body list; **marks read** |
|
||||
| One specific comment by its id | `comment-show <id>` | single comment |
|
||||
| The audit trail — closes, label/assignee changes, commit refs, merges (no comment bodies) | `timeline <n>` | event list |
|
||||
| The code change | `pr diff <n>` | unified diff |
|
||||
| Is this PR ready to merge — mergeable + CI + reviews | `pr status <n>` | health view; exit code = merge-readiness |
|
||||
| The review verdicts + inline review comments | `pr reviews <n>` | review list |
|
||||
|
||||
Rule of thumb: **`view` first** when you're about to act on a thread (it
|
||||
shows everything and clears the unread guard so your follow-up `comment`
|
||||
isn't rejected); the JSON verbs (`issue`/`pr`) when a script needs one
|
||||
field; `pr status` when the only question is whether it can merge.
|
||||
|
||||
### Contributing to a read-only repo (`clone` + `pr create --agit`)
|
||||
|
||||
<!-- vale write-good.Passive = NO -->
|
||||
Agents are read-only collaborators on some repos (e.g.
|
||||
`internal/knowledge`) and so can't push branches. Forgejo's AGit flow
|
||||
lets a read-only user open a PR by pushing the current `HEAD` to the
|
||||
magic ref `refs/for/<base>/<topic>`. Two verbs cover the workflow:
|
||||
<!-- vale write-good.Passive = YES -->
|
||||
|
||||
```
|
||||
hive-forge -r internal/knowledge clone # clone, auth handled for you
|
||||
cd knowledge
|
||||
# add / edit / delete any files, then commit normally
|
||||
git add -A && git commit -m "add foo runbook"
|
||||
hive-forge -r internal/knowledge pr create --agit \
|
||||
--topic foo-runbook \ # groups pushes into ONE PR; reuse to update it
|
||||
--title "add foo runbook" \
|
||||
[--body "details"] # PR description (also accepts --body-file)
|
||||
```
|
||||
|
||||
<!-- vale write-good.Passive = NO -->
|
||||
`clone` derives the dest dir from the repo basename (override with a
|
||||
positional arg); it passes `--branch` / `--depth` through. The clone
|
||||
URL and the `origin` remote it leaves behind carry no credentials —
|
||||
`clone` instead configures `origin`'s `credential.helper` to invoke
|
||||
`hive-forge credential-helper` (a hidden verb, not meant to be run by
|
||||
hand), which git calls fresh on every fetch/push. That's what lets
|
||||
`pr create --agit` (default remote `origin`) push without re-auth,
|
||||
without a durable token sitting in the checkout's `.git/config`.
|
||||
<!-- vale write-good.Passive = YES -->
|
||||
|
||||
`pr create --agit` prints the PR URL. Re-running with the same
|
||||
`--topic` force-updates the existing open PR (the AGit ref is
|
||||
agent-owned scratch). Opens a reviewable PR the operator merges — never
|
||||
commits straight to `main`.
|
||||
|
||||
`hive-forge <verb> --help` prints the full signature for any verb.
|
||||
|
||||
### `pr status`
|
||||
|
||||
One-stop PR health view (`<n>`, positional — `--pr <n>` also still works):
|
||||
mergeable state, CI checks, requested reviewers + review verdicts, and the
|
||||
last-comment timestamp — the things you need to know whether a PR is ready
|
||||
to merge (CI must pass before merge). `--sha <sha>` is a CI-only fast path
|
||||
for a raw commit. The `reviewers` line lists pending requests only: a
|
||||
reviewer whose verdict still stands drops off the line even though the forge
|
||||
keeps them in its requested list, while one whose review is stale or dismissed
|
||||
stays.
|
||||
|
||||
A failing/erroring CI context prints its job link. The process exit code
|
||||
is a **merge-readiness** verdict for a PR number (0 only when CI is green
|
||||
AND the PR is mergeable AND no review requests changes), so it composes:
|
||||
`hive-forge pr status 42 && echo ready`. `--sha` mirrors the CI
|
||||
verdict alone.
|
||||
|
||||
```
|
||||
hive-forge pr status 42
|
||||
# PR #42: feat(...): ... # lint:allow: sample CLI output, not a real tag
|
||||
# state: open (mergeable: yes)
|
||||
# CI: e39a87ea3949: ✅ success (1 context(s))
|
||||
# ✅ CI / nix flake check (pull_request): success — Successful in 1m50s
|
||||
# reviewers: (none requested)
|
||||
# reviews: ✅ argus: APPROVED
|
||||
# last comment: 2026-06-05T19:13:28+02:00 by argus
|
||||
```
|
||||
|
||||
Note: review verdicts come from _formal_ Forgejo reviews (the
|
||||
approve / request-changes API). Reviewers who post their verdict as a
|
||||
plain comment show under `last comment`, not `reviews`.
|
||||
|
||||
### Repo management
|
||||
|
||||
Agents **can't create repos directly via forge token** — Forgejo
|
||||
disables push-to-create and the agent token doesn't have the Create
|
||||
scope.
|
||||
|
||||
The CLI verbs below (`repo-create` / `repo-add-collaborator`) use the
|
||||
agent's own forge token, so a repo lands under the agent's user account
|
||||
or an org the agent belongs to.
|
||||
|
||||
**`repo-create <name>`** — create a repo under the authenticated user
|
||||
and print its URL. Key flags:
|
||||
|
||||
- `--org <ORG>` — create under an org namespace instead
|
||||
- `--private` — private repo (default: public)
|
||||
- `--auto-init` — seed an initial commit so the repo is non-empty and
|
||||
immediately cloneable; omit for a bare repo you push into
|
||||
- `--description <TEXT>` — repo description
|
||||
|
||||
**`repo-add-collaborator <user>`** — grant a forge user access to the
|
||||
active repo (see the repo-resolution chain above). Companion to
|
||||
`repo-create`. The
|
||||
`--permission` flag accepts `read` / `write` (default) / `admin`.
|
||||
`hive-c0re` uses this internally when it initialises an agent's config
|
||||
repo.
|
||||
|
||||
**`repo-labels [PATTERN]`** — list every label defined on the repo,
|
||||
optionally filtered by a name substring (case-sensitive). Distinct from
|
||||
`labels <n>` which shows the labels on one specific issue/PR. Use this
|
||||
to discover valid label names before triaging or to audit the label set.
|
||||
`--json` emits full label objects (id, name, color, description).
|
||||
|
||||
## Notes
|
||||
|
||||
<!-- vale write-good.Passive = NO -->
|
||||
- **Read-before-comment guard:** `comment` refuses to post when forge still
|
||||
has an unread notification for the thread (that is, someone commented since
|
||||
you last read it). This prevents accidentally replying to old activity without
|
||||
seeing the new context. Read the thread with `hive-forge view <n>` or
|
||||
`hive-forge comments <n>` (which mark the notification read and clear the
|
||||
block), or pass `--force` to skip the guard.
|
||||
- `comment --body "..."` with backticks in the body: always use
|
||||
`--body-file -` with a HEREDOC to avoid shell escaping issues.
|
||||
- `pr create --push` also runs `git push forge <head>` and suppresses
|
||||
the post-push "Create a pull request" hint (hive-forge prints the
|
||||
canonical URL itself).
|
||||
- `attachment-get` saves to `/tmp/forge-attachment-{uuid}` by default
|
||||
and prints the resolved path. Pass `-o -` to stream to stdout.
|
||||
- `artifact-get <name> --run <n>` downloads a CI Actions artifact. `<n>`
|
||||
is the run number from the run-page URL (`/actions/runs/<n>`, which
|
||||
`pr status` surfaces as a CI context's target_url). Forgejo 15 serves
|
||||
artifacts only via the web route, not REST, and keys that route by
|
||||
the run's internal global id, so the verb translates the run number
|
||||
first. Saves a zip to `/tmp/forge-artifact-<name>.zip` by default; pass
|
||||
`-o -` to stream to stdout.
|
||||
- `ci-log --run <n> [--job i] [--step i] [--attempt n]` prints a CI
|
||||
run's job step logs. `<n>` is the run number from the run-page URL
|
||||
(same value `artifact-get` takes; `pr status` surfaces it as a CI
|
||||
context's target_url). Two log sources, tried in **completeness
|
||||
order**: the verb tries the **durable persisted-log download** the run page's "view
|
||||
raw logs" link uses (`…/runs/<n>/jobs/<job>/attempt/<a>/logs`, a flat
|
||||
whole-job log) first — complete once it exists, which covers
|
||||
any run that has already finished; it's only absent while the job is
|
||||
still running, in which case the verb falls back to the web run-view
|
||||
**streamer** the run page polls (rich per-step framing, but only a
|
||||
snapshot of the live `act_runner` task record, so a still-buffering
|
||||
multi-minute phase can come back thin). Passing `--step` reverses
|
||||
that order — only the streamer honors per-step framing (the persisted
|
||||
log is flat), so `--step` goes straight to the streamer and an
|
||||
out-of-range index surfaces as a hard error instead of silently
|
||||
falling back. `--job` selects the job (0-based, default 0);
|
||||
`--attempt` picks the run attempt for the durable path (default 1;
|
||||
re-runs increment it). `--json` wraps the output.
|
||||
- `ci-rerun` re-runs CI: it dispatches a
|
||||
**fresh** run of the workflow via the workflow-dispatch API
|
||||
(`POST …/actions/workflows/<workflow>/dispatches {"ref":"<branch>"}`).
|
||||
Resolve the branch with `--run <n>` (the same run number `ci-log` /
|
||||
`artifact-get` take — resolves the branch + workflow from that run) or
|
||||
`--branch <name>` (directly). `--pr <n>`, and `--run <n>` against a run
|
||||
that was itself PR-triggered, both refuse instead of dispatching:
|
||||
a workflow-dispatch run writes no commit status, so it can't clear a
|
||||
red `(pull_request)` check on that PR's sha — re-run from the web UI
|
||||
instead. `--workflow <file>` picks the workflow file for `--branch`
|
||||
(default `ci.yml`). Dispatch re-runs the whole workflow — there is no
|
||||
single-job variant.
|
||||
- `issue dependency <n> add <dep...>` / `pr dependency <n> add <dep...>`
|
||||
set "blocked by" links via Forgejo's dependency feature — the
|
||||
operator's preferred way to track blocking relationships over labels
|
||||
(labels can go stale; dependencies are structured and show in the
|
||||
forge UI's dependency panel). Same-repo only, matching the web UI (no
|
||||
cross-repo dependency support). `list` (the default action) and `add`/
|
||||
`remove` all print the resulting dependency list as JSON.
|
||||
- `issue reaction <n> add <content>` / `pr reaction <n> add <content>` add
|
||||
an emoji reaction (Forgejo's shortcode vocabulary — `+1`, `heart`,
|
||||
`rocket`, …, same set the web UI's picker offers, not a raw emoji
|
||||
character); `--comment <id>` targets a specific comment's reactions
|
||||
instead of the issue/PR itself. `list`/`add`/`remove` all print the
|
||||
resulting reaction list as JSON, same shape as `dependency`. `view`,
|
||||
`issue`, `pr`, and `comment-show` always show a reaction summary;
|
||||
`comments --show-reactions` adds one per comment shown (opt-in — one
|
||||
extra request per comment, no server-side inline count). `reaction
|
||||
<n> --list-allowed` prints the instance's actual configured shortcode
|
||||
list (`GET /settings/ui`'s `allowed_reactions`) instead of guessing —
|
||||
instance-global, this path ignores `<n>`/`--comment`.
|
||||
- **Don't** use raw `curl` for forge access -- the CLI handles auth,
|
||||
error checking, and output formatting.
|
||||
- `issue create --label <name>` / `pr create --label <name>` are
|
||||
repeatable and take the same spelling `labels <n> add` does. **An
|
||||
unknown name is an error, not a silent drop** — the command fails
|
||||
listing the names that didn't resolve plus every label the repo has,
|
||||
so it's fixable from the message without a second call. On
|
||||
`pr create --agit`, hive-forge applies labels as a follow-up call once
|
||||
it parses the PR number back out of the push output (the AGit push
|
||||
itself has no label field), so they're silently skipped if that
|
||||
parse fails — same fallback as the deferred multi-line body.
|
||||
- `list --label <name>` / `list --milestone <name>` are repeatable and
|
||||
validated the same way, for a sharper reason: the forge **discards** a
|
||||
filter value it doesn't recognise, so a typo returns the
|
||||
**unfiltered** list rather than an empty one. That doesn't waste a
|
||||
query, it inverts the answer — "is anything open in this milestone"
|
||||
comes back as every open issue. `--milestone` takes a title or an id,
|
||||
and closed milestones count as valid (filtering on a shipped one is a
|
||||
normal query).
|
||||
- ⚠️ `--assignee` / `--author` are deliberately **not** validated: someone
|
||||
who has left still legitimately appears on old issues, so a login that
|
||||
isn't a current member isn't necessarily a typo.
|
||||
- `list --limit N` is a *request*: the forge clamps page size to its own
|
||||
`api.MAX_RESPONSE_ITEMS` (50 by default), so `--limit 400` returns at
|
||||
most 50 rows. The stderr trailer (printed in `--json` mode too)
|
||||
is driven by the forge's own `Link: rel="next"` / `X-HasMore`
|
||||
signal, so it stays right when the forge clamps `--limit`: while a next
|
||||
page exists it prints the rows shown so far (out of the response's
|
||||
`X-Total-Count` when sent, `… 50 of 187 shown … 137 more`) and a
|
||||
`re-run with --page N+1` hint. The last page prints no trailer, so
|
||||
silence means the list is complete. Two cases stay ambiguous: without
|
||||
`X-Total-Count` the headers didn't come through, and it falls back to
|
||||
`N shown … more results may exist` on a page of exactly `--limit` rows
|
||||
(silent on a clamped one); a proxy that strips only `Link` while
|
||||
keeping `X-Total-Count` reads as a last page.
|
||||
- `repo-search` reads `X-Total-Count` the same way `list` does and warns
|
||||
with the real remaining count when it's present (`… 50 of 187 shown …
|
||||
137 more exist`); without it (an older forge, a stripping proxy) it
|
||||
falls back to the cruder `count == limit` heuristic, same
|
||||
trust-the-trailer-not-the-row-count principle as `list` above. Unlike
|
||||
`list`, this verb has no `--page` — so once the forge's own page cap
|
||||
has clamped the result (`count < limit`), raising `--limit` again asks
|
||||
for the same clamp and gets nothing new; the warning says to narrow
|
||||
`--query` instead in that case, and only suggests raising `--limit`
|
||||
while still under the cap. Either way it can't tell "doesn't exist"
|
||||
from "you lack permission to see it" — both render as an absent row
|
||||
with no signal either way — and it matches repo **name** only, not
|
||||
owner, so there's no way to enumerate an org's repos through this
|
||||
verb.
|
||||
<!-- vale write-good.Passive = YES -->
|
||||
|
|
@ -1,175 +0,0 @@
|
|||
# Matrix MCP tools and extra MCP servers
|
||||
|
||||
For the operator: this page covers how the harness adds the matrix tools and what
|
||||
they do around the edges. How an agent calls them is in the tools' own MCP
|
||||
descriptions.
|
||||
|
||||
## 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
|
||||
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 operator
|
||||
configures the host-level matrix tuwunel. Tools land as `mcp__matrix__<name>`.
|
||||
|
||||
Each tool's parameters and return values are in its own MCP description, which
|
||||
is what an agent reads at call time. They fall into four groups:
|
||||
|
||||
- **Messaging:** `send_message`, `send_dm`, `send_reply`, `send_reaction`,
|
||||
`send_file`, `send_redact` and `open_dm`. A room is `!id:server` or
|
||||
`#alias:server`, and the daemon resolves either. `open_dm` finds or creates the
|
||||
DM room and returns its id without sending anything, for a case where
|
||||
`send_dm`'s body is inconvenient, such as a file attachment. `send_file` caps
|
||||
an upload at 50 MiB. Redacting someone else's event needs moderator power.
|
||||
- **Reading:** `read_room`, `download_file`, `list_rooms` and
|
||||
`list_room_members`. `read_room` shows media as `[file: …]`, `[image: …]`,
|
||||
`[audio: …]` or `[video: …]` markers, and the event id of one goes to
|
||||
`download_file`. An edited message shows once, with its latest text and
|
||||
`edited: true`. The edit event doesn't appear as a second message and doesn't
|
||||
count as unread. When the original is outside the window read, the edit stays
|
||||
as its own event, and its `replaces_event_id` names the message it replaces.
|
||||
- **Room membership:** `invite_user`, `list_invites`, `resolve_invite` and
|
||||
`join_room`. `resolve_invite` accepts or rejects a pending invite and is the
|
||||
path for invites; `join_room` is for a public room the agent wasn't invited to.
|
||||
`list_invites` gives the inviter and the topic, so an agent can judge an
|
||||
invite before it answers.
|
||||
- **Receipts:** `mark_read` advances the read receipt.
|
||||
|
||||
**DM recipient check.** `send_dm` and `open_dm` report where the other user
|
||||
stands in the room through a `recipient` field. `joined` means they see the
|
||||
message. `invited` means the invite is pending, and the result carries a
|
||||
`warning`. When the user is in neither state, because the invite failed
|
||||
or they left, the call fails and sends nothing.
|
||||
|
||||
**Unread guard.** `send_message`, `send_dm`, `send_file` and `send_reply` fail
|
||||
while the room has unread messages. The agent calls `read_room` and then
|
||||
`mark_read` on the latest event before sending to a room it hasn't read.
|
||||
|
||||
## Multiple accounts
|
||||
|
||||
<!-- vale write-good.Passive = NO -->
|
||||
`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
|
||||
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 +
|
||||
cache), and an optional `homeserver` (defaults to
|
||||
`services.hyperhive.agent.matrix.url`). The hive-internal account is
|
||||
always named `main` and is always the primary; the matrix module
|
||||
declares it for you as an ordinary entry of this map, from
|
||||
`services.hyperhive.agent.matrix.url` + agent state, so what you add
|
||||
here are the *further* accounts. Its token comes from the swarm secret
|
||||
store (`swarm-controller` mints it); its `tokenFile` stays pinned to
|
||||
`<state>/matrix-token` as the fallback, and the dashboard's link-account
|
||||
route refuses to create an account by that name.
|
||||
<!-- vale write-good.Passive = YES -->
|
||||
|
||||
`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. Declaring an external account with
|
||||
its own `homeserver` is enough on its own; a hive homeserver isn't
|
||||
required.
|
||||
|
||||
An account linked from the swarm UI needs no entry in this map.
|
||||
`swarm-controller` stores it at `swarm/agents/<agent>/matrix/<account>`.
|
||||
The daemon lists that directory under the agent's own certificate at
|
||||
start and every two minutes, and restarts itself when the set of linked
|
||||
accounts changes. A declared entry of the same name wins.
|
||||
|
||||
Every matrix tool above takes an optional `account` parameter (a name
|
||||
from this map, or a linked account's) to act as that identity instead
|
||||
of the primary one.
|
||||
|
||||
Declaring an extra account also changes what the agent *receives*: the
|
||||
summaries of unread-room and invite todos gain an `[acct:<name>]` prefix — see
|
||||
[Architecture](#architecture) below.
|
||||
|
||||
## Architecture
|
||||
|
||||
`hive-matrix-daemon` is a single long-running process (one per agent
|
||||
container, systemd service in `nix/agent-modules/matrix.nix`) — no
|
||||
stdio bridge, no separate bin. It owns the matrix-sdk `Client` + sync
|
||||
loop per configured account **and** serves the matrix tool surface
|
||||
directly over streamable-http on `services.hyperhive.agent.mcp.matrixHttpPort`
|
||||
(declared in `services.hyperhive.agent.extraMcpServers.matrix` as
|
||||
`{ type = "http"; url = ...; }`). Same shape as `hive-bash-daemon` and
|
||||
the built-in `hyperhive` surface (`hive-mcp-http`) — claude reconnects
|
||||
to the stable URL every turn instead of respawning a stdio child.
|
||||
Silently exits when the primary account has no token yet; the
|
||||
`systemd.paths.hive-matrix-daemon` watcher restarts it when a token file
|
||||
appears, and, on an agent with a store, a timer restarts it while it's
|
||||
down so it re-reads a token the swarm minted into the store.
|
||||
|
||||
**Wake path**: after every sync the daemon upserts a harness-local todo
|
||||
on the in-agent socket for each unread room (keyed by its room id) and
|
||||
each pending invite (below). A new or changed todo wakes the agent with
|
||||
the generic prompt `you have todos — call get_loose_ends to see them`
|
||||
(from `todo`). The `[matrix]` lines reach the agent through
|
||||
`get_loose_ends`, one per room, as
|
||||
`- todo #<id> [matrix <room_id>, <age>s old]: <summary> (cancel_loose_end …)`.
|
||||
The summary depends on the room's unread state:
|
||||
|
||||
- **One unread message**: `[matrix] <sender> in <room>: <body> —
|
||||
use read_room to view, mark_read to clear`
|
||||
- **Multiple unread messages**: `[matrix] N unread in <room> —
|
||||
use read_room to view, mark_read to clear`
|
||||
|
||||
**Multi-account prefix**: when the daemon serves more than one account
|
||||
(`services.hyperhive.agent.matrixAccounts`), it prefixes the summary of every
|
||||
unread-room todo above and every invite todo below with `[acct:<name>] `, so
|
||||
`get_loose_ends` shows
|
||||
`- todo #<id> [matrix <room_id>, …]: [acct:ccc] [matrix] <sender> in <room>: …`. A single-account agent gets the
|
||||
formats exactly as written — the daemon adds nothing — which is why matching
|
||||
on a leading `[matrix]` works right up until you declare a second account.
|
||||
|
||||
hive-matrix-daemon includes the same per-room breakdown in the `UnreadMatrix`
|
||||
entry `get_loose_ends` returns, so unread rooms surface in the
|
||||
loose-ends list between turns.
|
||||
|
||||
**Invite wakes**: the daemon sweeps `invited_rooms()` after every sync
|
||||
callback (deliberately not a one-shot `m.room.member` event handler —
|
||||
a one-shot signal that raced a socket-down window vanished with no
|
||||
retry, leaving the agent deaf until manually prompted) and upserts a
|
||||
todo (keyed `invite:<room>`) for each pending invite on the harness's
|
||||
in-agent socket, which drives a turn. The daemon does **not**
|
||||
autojoin — the agent calls `list_invites` to see pending invites and
|
||||
`resolve_invite` to accept or reject them.
|
||||
|
||||
**Pending invites as loose ends**: the daemon upserts pending invites as
|
||||
keyed todos, which appear in `get_loose_ends` output as
|
||||
`[matrix] invited to <room> (<room_id>) — use list_invites to see
|
||||
pending invites, resolve_invite to accept or reject`. The daemon clears
|
||||
the keyed todo when a `resolve_invite` (or `join_room`) call resolves
|
||||
the invite.
|
||||
|
||||
See [`docs/2-handbook/integrations/matrix.md`](../integrations/matrix.md) for the homeserver setup,
|
||||
provisioning flow, and federation config.
|
||||
|
||||
## Extra MCP servers (per-agent)
|
||||
|
||||
Each agent's NixOS config can declare additional MCP servers via
|
||||
`services.hyperhive.agent.extraMcpServers.<key> = { type, command, args, env, url,
|
||||
allowedTools }` — `type = "stdio"` (the default, uses `command`/`args`/
|
||||
`env`) or `type = "http"` (uses `url`, a long-lived streamable-http
|
||||
endpoint — see `hive-bash-daemon` and `hive-matrix-daemon` above for
|
||||
the `"http"` shape). The module writes the map to
|
||||
`/etc/hyperhive/extra-mcp.json`; the harness reads it at boot and
|
||||
merges every entry into `--mcp-config` (under `mcpServers.<key>`)
|
||||
and `--allowedTools` (as `mcp__<key>__<pattern>`).
|
||||
|
||||
The agent's `flake.nix` forwards every flake input to `agent.nix` as
|
||||
the `flakeInputs` module arg, so you pull in external MCP-server flakes
|
||||
by adding them to `inputs.*` and reference them as
|
||||
`flakeInputs.<name>.packages.${pkgs.system}.default` — the resolved
|
||||
sha lands in the agent's own `flake.lock` and rolls up to `internal/meta`'s.
|
||||
|
||||
`allowedTools` defaults to `["*"]`, which expands to
|
||||
`mcp__<key>__*` (every tool from that server autoapproved). Restrict
|
||||
to specific tool names when you want finer control.
|
||||
|
|
@ -1,463 +0,0 @@
|
|||
# Subagent daemon
|
||||
|
||||
`hive-subagent-daemon` (crate `hive-subagent-mcp`) spawns nested headless
|
||||
`claude` sessions on request. Own process, own systemd unit, own MCP
|
||||
server (`subagent`, not `hyperhive`) — independent of `hive-bash-daemon`:
|
||||
a subagent is a full nested claude process, a materially heavier
|
||||
capability than a background shell command, so it gets its own
|
||||
deployable/restartable unit rather than living inside the bash daemon.
|
||||
|
||||
Shipped default-on for every agent — `nix/agent-modules/mcp.nix` injects
|
||||
`subagent` into `services.hyperhive.agent.extraMcpServers` via `lib.mkDefault`
|
||||
(`allowedTools = ["*"]`), same as `bash`. Default-on rather than
|
||||
unconditional: an `agent.nix` can override or drop the entry, which is
|
||||
what `mkDefault` is there for.
|
||||
|
||||
For what the tools do and when an agent should reach for them, see the
|
||||
`subagent` MCP server's own tool descriptions and the
|
||||
`base:claude-subagents` skill — this page covers the daemon as deployed
|
||||
infrastructure, not the agent-facing API.
|
||||
|
||||
## Tools
|
||||
|
||||
Served under the `subagent` MCP server (`mcp__subagent__<tool>`): `start`,
|
||||
`continue`, `status`, `interrupt`.
|
||||
|
||||
A second route on the same port serves the two tools a **subagent** calls
|
||||
about its own run — `goal_reached` and `need_help`. It isn't part of the
|
||||
`subagent` server an agent's own config points at; the daemon writes it
|
||||
into each subagent's `--mcp-config` itself, under `subagent_control`. Two
|
||||
routes rather than six tools on one, so that being able to report on a run
|
||||
never carries the ability to start one: there's no route a subagent holds
|
||||
that `start` is reachable from.
|
||||
|
||||
## A subagent can't name a session, not even its own
|
||||
|
||||
Neither signal tool takes a session name. **The URL is the identity.** At
|
||||
each spawn the daemon mints that run an unguessable token, serves it at
|
||||
`/signal/mcp/<token>`, and writes that one URL into that one subagent's own
|
||||
`--mcp-config` — a file per session, not a shared one. The daemon resolves
|
||||
a request to a session before dispatching it, and the tools read the
|
||||
session off the resolution.
|
||||
|
||||
A subagent therefore has no field in which to name a sibling, and knowing
|
||||
a sibling's name buys nothing: a name isn't a token. Two subagents running
|
||||
concurrently can't signal each other — which the earlier shape, a shared
|
||||
route plus a `name` argument guarded by a liveness check, allowed.
|
||||
|
||||
A token that resolves to nothing — never minted, or revoked when its run
|
||||
ended — gets a bare **404**, the same answer for every token, so nothing
|
||||
about the refusal says whether some other session exists.
|
||||
|
||||
## State
|
||||
|
||||
In-memory only: what's running now, where each name's session lives, how
|
||||
each name's last turn ended, when each running turn last produced output,
|
||||
what each session is working toward, how far through its turn budget it
|
||||
is, why its run stopped, where it writes its report, and which signal
|
||||
token belongs to it. All of it lives only as long as the daemon process
|
||||
does — so a daemon restart invalidates every signal URL it had issued,
|
||||
which is the same thing as it having stopped the runs those URLs belonged
|
||||
to. A daemon
|
||||
restart stops whatever was running rather than adopting it. The durable
|
||||
record of a subagent's existence is claude's own on-disk session
|
||||
(`hive_claude::SessionStore`), which `continue` reattaches to independent
|
||||
of the daemon's own lifetime — a restart loses the _in-flight turn_, not
|
||||
the subagent's history.
|
||||
|
||||
`continue`, and each later turn of a goal run, hands claude a session id,
|
||||
not the name: the id of the live session carrying that name in `dir`'s
|
||||
project directory, read off disk at every resume. claude looks a bare name
|
||||
up across every git worktree of the repo and refuses when two sessions
|
||||
match, and `start` archives a prior session only in its own directory, so
|
||||
a name reused under a sibling worktree would otherwise stop resuming. When
|
||||
`dir` holds no session under the name, the daemon refuses and never hands
|
||||
claude the bare name — see below.
|
||||
|
||||
Because the remembered directory goes with the rest of it, a `continue`
|
||||
after a restart has to re-supply `dir` when the session lives anywhere
|
||||
other than the agent's work dir (the default when it exists) — and a restart is the
|
||||
situation you reach for `continue` in most often.
|
||||
|
||||
## Roles
|
||||
|
||||
A `start` may name a **role**: `role: "reviewer"` runs that subagent under
|
||||
the spawning agent's own `reviewer.md`. Role files live in one directory
|
||||
per agent, `subagent_roles/` inside that agent's state directory, one
|
||||
markdown file per role, and the `role` argument is the filename without
|
||||
its extension.
|
||||
|
||||
One convention directory of named files, because a named set enumerates.
|
||||
Anything that watches roles — a check for a safety clause that has gone
|
||||
stale, an operator asking what this agent can dispatch — lists the
|
||||
directory and has the answer. Per-agent rather than hive-wide: the clauses
|
||||
a role carries come out of that one agent's own prompt files, and the
|
||||
daemon only ever spawns on that agent's behalf.
|
||||
|
||||
**The task is the turn, never a standing system prompt — with or without a
|
||||
role.** A task baked into the system prompt re-asserts itself as an
|
||||
instruction on every later turn of the session (`continue`, `goal`), not
|
||||
just the one the caller wrote it for — the system prompt is the
|
||||
subagent's standing identity, not a one-shot channel, so the daemon always
|
||||
reads the task instructions (`prompt_file`) and folds them ahead of the
|
||||
turn's own prompt instead, whether or not the session has a role. The
|
||||
caller's own task file stays untouched either way.
|
||||
|
||||
A role additionally names a standing identity: its text, alone, becomes a
|
||||
per-session file under the harness directory, and `--append-system-prompt-file`
|
||||
points at that — nothing about the task ever reaches it. Without a role
|
||||
there is no such identity to hold a file open for, so the daemon omits
|
||||
`--append-system-prompt-file` altogether rather than pointing it at
|
||||
anything task-shaped.
|
||||
|
||||
⚠️ This applies to every dispatch, not just role-bearing ones.
|
||||
|
||||
### A role with no file refuses the call
|
||||
|
||||
A named role with no file **fails the `start`** and names the roles the
|
||||
directory does hold. No fallback, no spawn, no reservation — the refusal
|
||||
lands before the daemon claims the session name, so a caller that fixes
|
||||
the name may retry it immediately.
|
||||
|
||||
The reason is the state every agent starts in. The directory starts
|
||||
empty: hyperhive ships no role files, and nothing creates one until an
|
||||
agent writes its own, so a name with no file behind it counts as the
|
||||
ordinary first-run answer rather than a rare corruption. Falling back to a spawn
|
||||
without the role would hand that subagent a prompt missing every clause
|
||||
the role existed to carry, and would do it quietly. An empty role file
|
||||
draws the same refusal, for the same reason.
|
||||
|
||||
## Goals, and turns toward them
|
||||
|
||||
`start` takes an optional `goal`. Without one a session is a single turn,
|
||||
exactly as it always was. With one, the daemon keeps the session going:
|
||||
when a turn ends and nothing has said to stop, it starts another turn
|
||||
re-prompting the subagent toward that goal, quoting it verbatim and saying
|
||||
which turn of the budget this is. `max_turns` caps that, defaulting to
|
||||
**5**.
|
||||
|
||||
`status` reports `Turn N of M` for such a session in every state it
|
||||
reaches. Read alongside the last-event age below, it's what separates a
|
||||
subagent that's working from one that's wedged from one that's out of
|
||||
turns — without `ps` and without opening a file.
|
||||
|
||||
Five things stop a run; the daemon records each distinctly, reports it via
|
||||
`status`, and appends it to the one todo it pushes when the run ends:
|
||||
|
||||
- **the turn ended and there was no goal** — the single-turn case;
|
||||
- **`goal_reached`**, which the subagent calls itself;
|
||||
- **`need_help`**, likewise;
|
||||
- **the turn cap**, which says so rather than stopping quietly: the todo
|
||||
states that the run hit the harness limit and the goal was never
|
||||
reported reached, so the work stopped where it had got to;
|
||||
- **`interrupt`**, which cancels the whole run and not merely the turn
|
||||
that was in flight.
|
||||
|
||||
A killed or failed turn ends the run too, and keeps the records it already
|
||||
had — see [A killed turn](#a-killed-turn).
|
||||
|
||||
`interrupt` being on that list is load-bearing, not bookkeeping. Killing
|
||||
the process isn't by itself a cancel: `interrupt` sends SIGINT by
|
||||
default, and `claude --print` handles SIGINT by writing its terminal
|
||||
result event and exiting **zero**, which is indistinguishable from a turn
|
||||
that finished. `interrupt` therefore records the cancel before it
|
||||
signals, and the continuation loop reads that record rather than guessing
|
||||
from how the child exited. `continue` clears it and hands the session a
|
||||
fresh turn allowance, which is what makes an interrupt a pause you can
|
||||
undo rather than a session you have to abandon.
|
||||
|
||||
On `claude`, the signal is always SIGINT, and it goes to claude's whole
|
||||
process group. The signal doesn't reach a command that moved itself out of
|
||||
the group (`setsid`), and claude's Bash tool starts every command that way:
|
||||
claude stops those commands itself when it shuts down on SIGINT. A SIGKILL
|
||||
would leave them running, so nothing escalates to one. `interrupt` returns
|
||||
at once, and the name stays claimed until claude has exited: `status` says
|
||||
**stopping**, and the daemon refuses `start` and `continue` on that name
|
||||
until then. A claude that ignores the SIGINT stays stopping until someone kills
|
||||
it by hand or the unit stops.
|
||||
|
||||
When the session knows where its report goes — `start`'s `report_file`,
|
||||
or the path the subagent names when it signals — the daemon appends the
|
||||
stop reason to that file as well, so the artifact you were going to read
|
||||
anyway also says how the run ended. The daemon infers nothing: with no
|
||||
path given, it touches no file.
|
||||
|
||||
## `goal_reached` is a label, not a gate
|
||||
|
||||
Both signals stop the continuation and **extend** the done message. Extend,
|
||||
not replace: the turn's observed end and the reason the run stopped are
|
||||
different facts, and the second never stands in for the first.
|
||||
|
||||
`goal_reached` is **self-reported**, by a subagent that has just been
|
||||
re-prompted with "you haven't reached the goal" — which is precisely the
|
||||
incentive to claim it. It's the same failure class as a build report
|
||||
asserting "done, tests pass": a claim about an artifact, not the artifact.
|
||||
Nothing in this daemon treats it as verification, and every surface that
|
||||
renders it says so. Read the diff and the gate output regardless.
|
||||
|
||||
`need_help` is the blocking signal. It stops the run and shows up in
|
||||
`status` as its own state — blocked, with the subagent's reason — so a
|
||||
parent that polls `status` sees the block without reading anything else.
|
||||
`continue` is how you answer it.
|
||||
|
||||
## Telling working from wedged
|
||||
|
||||
`status` reporting **running** says the daemon tracks a process, which a
|
||||
wedged subagent satisfies as fully as a busy one. A running answer therefore
|
||||
carries the age of that turn's last event too: seconds means it's working,
|
||||
an age climbing into the minutes means it's stuck. That one number
|
||||
replaces inferring the same thing from `ps` output and CPU-time deltas.
|
||||
It resets at each turn's spawn, so on a goal run it describes the turn in
|
||||
flight rather than the run — which is what you want, since a run that's
|
||||
making progress spends a few perfectly healthy minutes.
|
||||
|
||||
Every line the subagent's `claude` process writes bumps the timestamp —
|
||||
stream-json events, plain stdout chatter and stderr alike — and what the
|
||||
subagent actually said is never read. The record says the child is alive,
|
||||
not what it's doing. The clock starts at the spawn, so a subagent that wedged
|
||||
before it ever emitted anything still reports a climbing age rather than
|
||||
no age at all. It's dropped when the turn ends, since a finished turn has
|
||||
no progress left to describe.
|
||||
|
||||
## A `continue` that finds no session
|
||||
|
||||
`continue` looks for the session before it spawns anything. The lookup is
|
||||
the live session titled the name in the project directory of the `dir` in
|
||||
effect: the one passed, or else the one the daemon remembers for the name.
|
||||
No such session refuses the call — nothing spawns, the name stays free
|
||||
for a retry, and the session keeps its `status` (stop reason and turn
|
||||
budget) and its remembered `dir` — and the error names the directory
|
||||
searched:
|
||||
|
||||
```
|
||||
continue error: no session titled `n` under /home/agent/.claude/projects/-home-agent-work; pass the `dir` the session was started in
|
||||
```
|
||||
|
||||
A name is never searched for in any other directory, because claude would
|
||||
resolve a bare title across every worktree of the repo and could pick, or
|
||||
refuse, the wrong session. A session started in another directory
|
||||
needs that directory passed as `dir` — and after a daemon restart, which
|
||||
forgets the remembered one, a session outside the agent's work dir
|
||||
needs it again. A store location the daemon can't resolve
|
||||
refuses the same way, with the io error.
|
||||
|
||||
A goal run's next turn does the same lookup; a miss there ends the run as
|
||||
a failed turn, with the same message in its end-of-turn todo.
|
||||
|
||||
The refusal covers a session missing from the directory searched.
|
||||
It can't cover one that vanishes between the lookup and the spawn, so
|
||||
`continue` still waits for claude's answer instead of returning the
|
||||
instant the process exists. Where `start` returns at the spawn — it
|
||||
creates its session, so the spawn succeeding is the whole story — a
|
||||
resumed turn can fail a moment _after_ a pid exists. `continue` holds the
|
||||
tool call open until the turn is underway or the resume has come back
|
||||
missed, and reports a miss as the call's own error, carrying claude's
|
||||
message plus the location this daemon searched:
|
||||
|
||||
```
|
||||
continue error: claude error: no session matched the requested id or title (searched /home/agent/.claude for cwd /home/agent/work; if it was started elsewhere, pass the `dir` it was started in)
|
||||
```
|
||||
|
||||
The directory is the part claude's own message never names, and the part
|
||||
that resolves the confusion: pass `dir` to point `continue` at the
|
||||
directory where the session began.
|
||||
|
||||
Nothing here is a fixed delay on the way to a successful turn. The wait
|
||||
ends on whichever comes first — the turn's first stream event or its
|
||||
early exit — and on this box both land inside a second, so a `continue`
|
||||
that works answers about as fast as it did before. A five-second cap
|
||||
bounds the one case neither covers: a child that neither speaks nor
|
||||
exits, reported as started, with the end-of-turn todo left to say how it
|
||||
goes. That todo carries every failure that happens after the one
|
||||
`continue` already returned to the caller.
|
||||
|
||||
## A killed turn
|
||||
|
||||
A subagent whose `claude` process dies on a signal — the kernel's OOM
|
||||
killer under memory pressure, a stopped unit, an `interrupt` from the
|
||||
owning agent — didn't finish its turn, and the daemon says so rather
|
||||
than letting it settle back into `idle`:
|
||||
|
||||
- `status` reports the session **killed**, naming the signal, instead of
|
||||
the `idle` it reports for a turn that ended on its own;
|
||||
- the end-of-turn todo the daemon pushes unprompted says something killed
|
||||
the subagent mid-turn, not that it finished;
|
||||
- `continue` still resumes such a session — often what you want — but
|
||||
its reply says something killed the previous turn, so nobody carries on
|
||||
from cut-off work believing it was complete.
|
||||
|
||||
The record is per-name, in memory with the rest of this daemon's state,
|
||||
and the next confirmed spawn under that name clears it. A daemon restart
|
||||
loses it along with everything else — the daemon has to outlive the kill
|
||||
to report it, which is what `OOMPolicy=continue` on the unit is for.
|
||||
|
||||
## Compaction trade-off
|
||||
|
||||
<!-- vale write-good.Passive = NO -->
|
||||
|
||||
Built on `hive_claude::Claude::spawn` + `RunningClaude::wait` directly
|
||||
rather than `InfiniteSession::run`, since only the low-level driver
|
||||
exposes a cancel handle to stop a turn mid-flight — that's what makes
|
||||
`interrupt` genuinely stop a running turn rather than only cancelling a
|
||||
still-pending one. The cost: a turn that overflows the context window
|
||||
surfaces as an error rather than self-healing via reactive compaction.
|
||||
Subagents are meant to be bounded, single-batch work, not sessions
|
||||
long-lived enough to need in-place compaction — a real follow-up if that
|
||||
assumption stops holding.
|
||||
|
||||
<!-- vale write-good.Passive = YES -->
|
||||
|
||||
## Configuration
|
||||
|
||||
`services.hyperhive.agent.mcp.subagentHttpPort` — the daemon's streamable-http listen
|
||||
port. Same pattern as `bashHttpPort`/`matrixHttpPort`: a per-agent default
|
||||
assigned by `nix/agent-modules/mcp.nix`, only worth overriding for an
|
||||
agent that needs a stable or non-default port.
|
||||
|
||||
Own systemd unit, defined alongside the other per-agent MCP daemons in
|
||||
`nix/agent-modules/mcp.nix`.
|
||||
|
||||
## Which models a subagent may run on
|
||||
|
||||
The daemon refuses a `start` or `continue` that names a `model` outside the
|
||||
agent's list before anything spawns, and the error names the list and where
|
||||
it came from. One without a `model` runs on claude's own default, which the
|
||||
daemon doesn't check. The list is:
|
||||
|
||||
- the swarm's `subagent_models` for this agent, when it sets one
|
||||
([`inference.md`](../../4-implementation/swarm/inference.md)). It replaces the agent's own
|
||||
list, so it can name models that list leaves out.
|
||||
- otherwise `services.hyperhive.agent.availableModels`, which reaches the
|
||||
daemon as `HIVE_AVAILABLE_MODELS`. An empty list restricts nothing.
|
||||
|
||||
The daemon reads the swarm's value on each such call, so a change applies
|
||||
to the next one. When the read fails, or doesn't answer within 2 seconds,
|
||||
the daemon logs a warning and checks against `availableModels`. The same
|
||||
happens until the daemon's first connection to the swarm queue succeeds.
|
||||
The match ignores case. On an ACP agent the names are the agent's own, such
|
||||
as `<provider>/<model>` on `opencode`.
|
||||
|
||||
## How many subagents may run at once
|
||||
|
||||
When the swarm sets `max_subagents` for the agent
|
||||
([`inference.md`](../../4-implementation/swarm/inference.md)), the daemon refuses a `start`, or a
|
||||
`continue` of a session that isn't running, once that many subagents are
|
||||
running, before anything spawns:
|
||||
|
||||
```text
|
||||
3 subagents running, cap is 3 (set in swarm-ui → this agent → inference) — wait for one to finish, or `interrupt` one
|
||||
```
|
||||
|
||||
Every subagent the daemon tracks counts, whatever its role: one running a
|
||||
turn, one starting, a goal run between turns, and an interrupted one whose
|
||||
process hasn't exited yet. A goal run's next turn isn't a new start, and the
|
||||
cap never stops it. Lowering the cap stops nothing already running.
|
||||
|
||||
The daemon reads the cap with the model list, on every `start` and
|
||||
`continue`, so a change applies to the next call. A cap of 0 refuses every
|
||||
`start` and `continue`, and the message says the cap is 0. Without a swarm value, or when the read fails, no cap applies. A failed read logs a warning naming the agent and the error, and the start proceeds: an unreachable swarm must not block subagent work.
|
||||
|
||||
## Runtime
|
||||
|
||||
A subagent runs on its parent agent's runtime
|
||||
(`services.hyperhive.agent.runtime`). The unit gets the harness's
|
||||
`HIVE_RUNTIME`/`HIVE_ACP_*` variables, and the daemon reads them once at
|
||||
startup (`hive_runtime::RuntimeSpec`). On `claude`, everything else on this
|
||||
page applies as written.
|
||||
|
||||
On `acp`:
|
||||
|
||||
- each run spawns its own copy of the parent's ACP agent. The end of the
|
||||
run sends SIGINT to it and every command in its process group. The unit loads
|
||||
`backendEnvironmentFile` for it. On the `opencode` preset, the daemon
|
||||
reads a provider key that file leaves unset from the swarm secret store
|
||||
at `swarm/agents/<agent>/acp-provider`, as the harness does.
|
||||
- the session id lives in `subagent-acp-session-<name>` under the harness
|
||||
dir. `start` moves an old one aside, `continue` loads it into a fresh
|
||||
agent process, and a `continue` with none recorded fails straight away.
|
||||
- a role goes in front of the session's first prompt, since ACP has no
|
||||
system prompt.
|
||||
- `interrupt` sends `session/cancel`. The runtime sends SIGINT to the
|
||||
process group of an agent that ignores the cancel for 10s, and when the
|
||||
run ends. Nothing escalates past that SIGINT. The name is free as soon as
|
||||
`interrupt` sends the cancel.
|
||||
- the runtime sets `model` and `effort` on the session when the agent
|
||||
offers that value for its `model` or `thought_level` config option. Any
|
||||
other value does nothing (`hive_runtime::AcpRuntime`).
|
||||
- the agent's permission requests get the same answers a claude subagent's
|
||||
tool list gives: MCP tools and file tools, `fetch` with `web_tools`,
|
||||
`execute` with `execution` (`session::acp_permits`).
|
||||
|
||||
## The tool surface a subagent gets
|
||||
|
||||
Two flags, each covering one half, and neither covering the other:
|
||||
`--tools` governs claude's built-in tools, `--strict-mcp-config` governs
|
||||
the MCP ones. Dropping either brings that half back in full; in
|
||||
particular `--tools` does **not** filter `mcp__*` tools.
|
||||
|
||||
### Built-in tools (`--tools`)
|
||||
|
||||
**A subagent gets exactly the built-ins its parent agent has, plus one
|
||||
deliberate exception** — the base resolution
|
||||
(`hive_sh4re::permissions::subagent_builtin_tools_for`, wrapping the
|
||||
harness's own `builtin_tools_for`) over the agent's permissions, which the
|
||||
daemon reads from the swarm on each `start`/`continue`:
|
||||
`Edit`, `Glob`, `Grep`, `Read`, `Skill`, `Write`, plus `WebFetch`/
|
||||
`WebSearch` for an agent granted `web_tools` and not otherwise. A running
|
||||
subagent keeps the tools it started with. See
|
||||
[the harness's own allowlist](../../4-implementation/turn-loop/mcp.md#tool-allowlist-hive_sh4repermissionsallowed_builtin_tools)
|
||||
for what that list contains and why.
|
||||
|
||||
The exception: **a subagent gets Claude's built-in `Bash` iff its parent
|
||||
holds the `execution` permission** — the permission that already grants the
|
||||
parent shell execution, via the out-of-process `bash` MCP server
|
||||
(`mcp__bash__run`/`status`/`kill`). A subagent has no MCP server of its
|
||||
own by default (see [MCP servers](#mcp-servers---strict-mcp-config)
|
||||
below), so there is no `mcp__bash__*` for it to inherit that capability
|
||||
through; it gets Claude's own tool instead. The rule is the capability
|
||||
transfers, not the mechanism — the parent itself never gains built-in
|
||||
`Bash` no matter which permissions it holds, only a subagent spawned by an
|
||||
`execution`-holding parent does.
|
||||
|
||||
Inheriting rather than listing is the point: a hardcoded subagent list
|
||||
would hand web egress (or `Bash`) to the subagent of an agent that isn't
|
||||
allowed it, and would diverge from the parent's on the first tool anyone
|
||||
adds to either.
|
||||
|
||||
Everything else in claude's built-in set is absent, in particular the
|
||||
tools that let a session act outside the run for which it began: peer and
|
||||
operator messaging, nested agents (including the stop verb, which takes
|
||||
an _agent_ id rather than a session), schedule and webhook creation, and
|
||||
worktree switching.
|
||||
|
||||
One rule worth knowing before editing any of this: **the daemon never
|
||||
emits an empty `--tools` value**, and asserts rather than doing so. Not
|
||||
because of what an empty value does — that's exactly the point. The hive's own
|
||||
measurement and the installed `claude --help` disagree about whether
|
||||
`--tools ""` means "no tools" or reads as the flag being absent, and the
|
||||
answer belongs to whichever claude release the system has installed
|
||||
rather than to this repo. Refusing the case is correct under either reading, so the code
|
||||
never has to know which holds. Nothing here wants a subagent with no
|
||||
built-in tools anyway.
|
||||
|
||||
### MCP servers (`--strict-mcp-config`)
|
||||
|
||||
A subagent runs with `--strict-mcp-config` and, by default, exactly one
|
||||
MCP server: the two-tool `subagent_control` route above — not the
|
||||
parent's `mcp__bash__*` / `mcp__hyperhive__*` surface. Those two signal
|
||||
tools are deliberately unnamed in `--tools`, which doesn't govern them;
|
||||
they survive on this flag alone. Nothing implicit reaches it: the built-in
|
||||
hyperhive surface (todos/messaging) isn't an `extraMcpServers` entry at
|
||||
all, and the automatically injected `bash`/`subagent` entries default to excluded
|
||||
too (a subagent can't spawn hive-bash tasks or its own nested subagents
|
||||
unless an operator opts them in explicitly, same as anything else).
|
||||
|
||||
Set `services.hyperhive.agent.extraMcpServers.<name>.availableToSubagents = true` on a
|
||||
specific entry to hand that one server to subagents as well — useful for,
|
||||
say, a read-only lookup or scraper MCP a subagent's bounded, single-batch
|
||||
task might need. `hive-subagent-mcp`'s `mcp_config` module renders the
|
||||
opted-in subset into a `--mcp-config` file per session, rewritten each
|
||||
turn; an entry left at the default `false` never appears there. Per
|
||||
session rather than one shared file, because the `subagent_control` entry
|
||||
in it carries that session's own signal URL — one file for everyone would
|
||||
be a race over whose identity each subagent reads at startup.
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
# Command-Line Help for `swarm-logs`
|
||||
|
||||
This document contains the help content for the `swarm-logs` command-line program.
|
||||
|
||||
**Command Overview:**
|
||||
|
||||
* [`swarm-logs`↴](#swarm-logs)
|
||||
* [`swarm-logs query`↴](#swarm-logs-query)
|
||||
|
||||
## `swarm-logs`
|
||||
|
||||
query the swarm's log store from an agent
|
||||
|
||||
**Usage:** `swarm-logs <COMMAND>`
|
||||
|
||||
###### **Subcommands:**
|
||||
|
||||
* `query` — Run a LogsQL query and print the matched log lines
|
||||
|
||||
|
||||
|
||||
## `swarm-logs query`
|
||||
|
||||
Run a LogsQL query and print the matched log lines
|
||||
|
||||
**Usage:** `swarm-logs query [OPTIONS] <LOGSQL>`
|
||||
|
||||
###### **Arguments:**
|
||||
|
||||
* `<LOGSQL>` — The LogsQL query. A bare word is a full-text search for it.
|
||||
|
||||
Quote it: LogsQL uses characters the shell also claims, and an unquoted `|` pipes this command into the rest of your query instead of sending it.
|
||||
|
||||
###### **Options:**
|
||||
|
||||
* `--limit <LIMIT>` — Stop after this many records.
|
||||
|
||||
Unset means the store's own default. Worth setting on a broad query — nothing between here and the store narrows one, so a bare word matches across every hive in the swarm.
|
||||
* `--format <FORMAT>` — What to print for each matched record
|
||||
|
||||
Default value: `message`
|
||||
|
||||
Possible values:
|
||||
- `message`:
|
||||
One log message per line — the `_msg` field and nothing else
|
||||
- `json`:
|
||||
The store's response body, unmodified, one JSON object per line
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
# Command-Line Help for `swarm-metrics`
|
||||
|
||||
This document contains the help content for the `swarm-metrics` command-line program.
|
||||
|
||||
**Command Overview:**
|
||||
|
||||
* [`swarm-metrics`↴](#swarm-metrics)
|
||||
* [`swarm-metrics query`↴](#swarm-metrics-query)
|
||||
* [`swarm-metrics query-range`↴](#swarm-metrics-query-range)
|
||||
* [`swarm-metrics series`↴](#swarm-metrics-series)
|
||||
* [`swarm-metrics labels`↴](#swarm-metrics-labels)
|
||||
* [`swarm-metrics label-values`↴](#swarm-metrics-label-values)
|
||||
|
||||
## `swarm-metrics`
|
||||
|
||||
query the swarm's metrics store from an agent
|
||||
|
||||
**Usage:** `swarm-metrics <COMMAND>`
|
||||
|
||||
###### **Subcommands:**
|
||||
|
||||
* `query` — Evaluate a `MetricsQL` (`PromQL`) expression at one instant
|
||||
* `query-range` — Evaluate an expression over a window, one point per step
|
||||
* `series` — List the series that match one or more selectors
|
||||
* `labels` — List label names, optionally only those on matching series
|
||||
* `label-values` — List one label's values; `__name__` lists every metric name
|
||||
|
||||
|
||||
|
||||
## `swarm-metrics query`
|
||||
|
||||
Evaluate a `MetricsQL` (`PromQL`) expression at one instant
|
||||
|
||||
**Usage:** `swarm-metrics query [OPTIONS] <PROMQL>`
|
||||
|
||||
###### **Arguments:**
|
||||
|
||||
* `<PROMQL>` — The expression. Quote it: `{`, `|` and `"` are the shell's too.
|
||||
|
||||
OTLP metric names keep their dots, so match them by `{__name__="jobq.nodes"}` rather than as a bare name.
|
||||
|
||||
###### **Options:**
|
||||
|
||||
* `--time <TIME>` — The instant: RFC 3339, a unix timestamp, or relative (`-5m`). Unset means now
|
||||
|
||||
|
||||
|
||||
## `swarm-metrics query-range`
|
||||
|
||||
Evaluate an expression over a window, one point per step
|
||||
|
||||
**Usage:** `swarm-metrics query-range [OPTIONS] --start <START> <PROMQL>`
|
||||
|
||||
###### **Arguments:**
|
||||
|
||||
* `<PROMQL>` — The expression, quoted as for `query`
|
||||
|
||||
###### **Options:**
|
||||
|
||||
* `--start <START>` — Start of the window: RFC 3339, a unix timestamp, or relative (`-1h`). Required, so a range is always bounded
|
||||
* `--end <END>` — End of the window, in the same forms. Unset means now
|
||||
* `--step <STEP>` — Seconds or a duration (`1m`) between points. Unset means the store's default
|
||||
|
||||
|
||||
|
||||
## `swarm-metrics series`
|
||||
|
||||
List the series that match one or more selectors
|
||||
|
||||
**Usage:** `swarm-metrics series [OPTIONS] <MATCHERS>...`
|
||||
|
||||
###### **Arguments:**
|
||||
|
||||
* `<MATCHERS>` — A series selector, such as `{"service.name"="nats"}`. Repeatable
|
||||
|
||||
###### **Options:**
|
||||
|
||||
* `--start <START>` — Start of the window: RFC 3339, a unix timestamp, or relative (`-1h`).
|
||||
|
||||
Unset means the store's default: the last day, from 00:00 UTC.
|
||||
* `--end <END>` — End of the window, in the same forms. Unset means now
|
||||
* `--limit <LIMIT>` — Return at most this many entries
|
||||
|
||||
|
||||
|
||||
## `swarm-metrics labels`
|
||||
|
||||
List label names, optionally only those on matching series
|
||||
|
||||
**Usage:** `swarm-metrics labels [OPTIONS]`
|
||||
|
||||
###### **Options:**
|
||||
|
||||
* `--match <MATCHERS>` — A series selector to narrow to. Repeatable
|
||||
* `--start <START>` — Start of the window: RFC 3339, a unix timestamp, or relative (`-1h`).
|
||||
|
||||
Unset means the store's default: the last day, from 00:00 UTC.
|
||||
* `--end <END>` — End of the window, in the same forms. Unset means now
|
||||
* `--limit <LIMIT>` — Return at most this many entries
|
||||
|
||||
|
||||
|
||||
## `swarm-metrics label-values`
|
||||
|
||||
List one label's values; `__name__` lists every metric name
|
||||
|
||||
**Usage:** `swarm-metrics label-values [OPTIONS] <NAME>`
|
||||
|
||||
###### **Arguments:**
|
||||
|
||||
* `<NAME>` — The label name
|
||||
|
||||
###### **Options:**
|
||||
|
||||
* `--match <MATCHERS>` — A series selector to narrow to. Repeatable
|
||||
* `--start <START>` — Start of the window: RFC 3339, a unix timestamp, or relative (`-1h`).
|
||||
|
||||
Unset means the store's default: the last day, from 00:00 UTC.
|
||||
* `--end <END>` — End of the window, in the same forms. Unset means now
|
||||
* `--limit <LIMIT>` — Return at most this many entries
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in a new issue