Standing agents

Maintainer, 2026-09-29: "we should have standing bug triage. find out where it is in docs. wanna also have url to see in platform in an app ==> all standing agents" — and: "issue triage should probably run on control and should be triggered by changes in github."

Most agents run because a person opened a chat with them. A standing agent is one the platform wakes by itself: a GitHub webhook delivery, a signed event on the pooled inbox, a sweep on a schedule. Nobody watches it start, so the only way to know it is alive is to be able to see it — what wakes it, where it runs, when it last did something, and what it has open.

The app is Standing Agents — on any instance, https://{instance}/Essentials/StandingAgents. It ships with Essentials, so it is on every portal; each portal shows the standing agents whose packages it has installed and whose records the viewer can read.

Declaring one — data, never a list in code

A package declares a standing agent with ONE node beside its agents:

{Package}/StandingAgents.json             a Markdown node backing the folder
{Package}/StandingAgents/{id}.json        nodeType: Essentials/StandingAgent
Field Meaning
agentPath the agent node, e.g. Hosting/Agent/triage; empty for a deterministic job that runs no model
purpose one line
triggers what wakes it, one entry per trigger — webhook event kinds, inbox event kinds, a sweep and its cadence
runsOn a ROLE — the control instance, the build instance, every portal — never a host name
wired false when the agent is designated but nothing starts its threads yet; the page says so
ledgerPath + activityFields the node that records its activity and the instant fields on it that count; the latest one is the row's last activity
queuePath, threadNamespace where its work items and its threads live; a pattern such as {recipient}/_Triage is shown but not linked
openItemsQuery an ANCHORED query whose matches are its open items, counted live; empty = not derivable, and the page says so
docPath, note the page that documents it, and what an operator must know besides

The app lists every such record by one query — nodeType:Essentials/StandingAgent partitions:all (StandingAgentCatalog.Query). There is no list of agents anywhere in the code: a package that adds a record adds a row, on every instance where the package is installed, and an agent that is not declared is not listed, whatever it is called. Both halves are pinned by the Tests area of Essentials/StandingAgent, including a live journey that writes a scratch declaration, finds it by the query, renders its row and deletes it again.

Why a companion record and not a property on the agent. Agent is the AI engine's compiled type (AgentConfiguration in src/MeshWeaver.AI), parsed from the agent's front matter by the engine. Putting operational fields there would change a compiled module, its parser and every portal's engine for facts only an operator page reads — and most of them (a ledger path, a webhook, the instance it runs on) describe the deployment that wakes the agent, not the agent. The record needs no core or engine change, lets a package declare a standing job that runs no agent at all, and lives in the package that owns the agent, so it disappears with that package.

Records are shipped content: change one in the repository, never live on a mesh — a GitSync would revert the edit.

What the columns read

Column Read from Live
Agent, Purpose, What wakes it, Runs on, Wired, Queue / threads the declaration the record's node, through the query
Last activity the latest of activityFields on the node at ledgerPath, in the viewer's time zone, with the field it came from; no ledger when none is declared, reading the ledger…, ledger unreadable: why, or the ledger records no activity yet the ledger's node stream — a ledger write re-renders the row
Open items the number of nodes openItemsQuery matches; not derivable when none is declared, query failed: why when it fails — never a silent zero the query's change stream

Each row links to its record's detail page ({Package}/StandingAgents/{id}): the declaration as a field table, one row per trigger, the note, and links to the agent, queue, threads, ledger and documentation. Every surface is composed from platform controls — headings, body text, DataGrids of property columns, navigation links.

Issue and bug triage — on the control instance, triggered by GitHub

Issue triage runs on the control instance and is triggered by changes in GitHub: one ORGANISATION webhook (events issues, pull_request, pull_request_review, check_suite) delivers every repository's changes, HMAC-signed, to the pooled inbox Hosting/PlatformBuilds/_Inbox; the always-on watcher turns an opened or reopened issue into a Hosting/TriageItem and a thread with Hosting/Agent/triage, and a pull request into a review thread with Hosting/Agent/pr-reviewer. Hourly (TriageIssueSweep) and ten-minutely (PullRequestSweep) sweeps catch what a delivery missed. The ledger is Hosting/Triage/Status. The mechanism, the scope and how to verify it end to end: Hosting/Triage. Both arm only on the portal that IS the triage inbox, which is why their records say the control instance.

Bug triage is the Essentials concept on top of it — one queue, one agent (Essentials/Agent/bug-triage), one permanent thread per defect (BugTriage). Its record is declared with wired: false: the intake still starts every thread with Hosting/Agent/triage, whose work folds into bug-triage once the intake is switched. The app says so rather than showing a loop that does not run yet.

Planned: the control role moves from the company portal to a dedicated, minimal control instance (the design record is kept on the control instance). Nothing here changes when it does — the records name the ROLE, not a host, and the webhook's payload URL is the one thing that moves.

Declared today

Package Record Agent Wired
Hosting Hosting/StandingAgents/triage Hosting/Agent/triage yes
Hosting Hosting/StandingAgents/pr-reviewer Hosting/Agent/pr-reviewer yes
Hosting Hosting/StandingAgents/pr-babysitter Hosting/Agent/pr-babysitter yes (build instance, opt-in)
Hosting Hosting/StandingAgents/platform-update Hosting/Agent/platform-update yes, but the release wave is off by default
Essentials Essentials/StandingAgents/bug-triage Essentials/Agent/bug-triage not yet
Essentials Essentials/StandingAgents/log-triage Agent/LogTriage yes (where the log watcher is configured)
Essentials Essentials/StandingAgents/notification-triage Agent/NotificationTriage yes
Feedback Feedback/StandingAgents/wrong-answer-digest — a deterministic weekly job yes

Deliberately not declared — each was checked against its code, and a person starts it: Feedback/Agent/feedback-agent (the /feedback skill), Hosting/Agent/live-defect-triage (a person reports a live defect) and Hosting/Agent/loki-error-triage (its instructions say ideally on a schedule, but nothing schedules it). The day one of them gets a trigger, its package adds a record.