@nightowlsdev/agent-diagnostics
Pre-built agentsThe pre-built diagnostics crew: a read-only run `inspector` and an approval-gated `bug-reporter`, so a stalled lane and a vague apology become which call failed, whether it recovered, and a report a developer can act on.
What it does
@nightowlsdev/agent-diagnostics is the pre-built diagnostics crew, and the split into two agents is the point (the same FR-049 precedent as agent-seo): audiences and `delegateSlugs` gate AGENTS, never tools within one, so the agent boundary is the only mechanism that keeps a write/egress capability away from a caller's LLM. The `inspector` is danger 0 all the way down (`run_timeline`, `run_failure`, and the read-only `check_issue`), holds zero write and zero egress capability, and is safe to delegate to freely; everything that writes lives on the `bug-reporter` (`report_issue`, danger 2 — mutating, irreversible, egress). The read-only claim is ENFORCED, not merely stated: `createInspector()` declares `builtinTools: { exclude: ["report_issue"] }` (core's per-agent built-in scope, harvested by `defineSwarm` keyed by the agent's own slug), and `diagnosticsSwarmOptions()` emits the SAME scope as deployment configuration so the boundary survives agents rehydrated from the store, where no in-memory `AgentDef` exists to read a declaration off; the two fold most-restrictive-wins, so carrying both can only narrow. The four tools are ENGINE BUILT-INS, not package-authored handles: they read the calling run's tenant/user/run identity off the RequestContext (which is what makes their authorization gate and per-run cap un-forgeable), so the package DECLARES them (`DIAGNOSTICS_TOOL_NAMES`, each manifest's `requiredTools`) and hands the host the exact switches that register them, and `diagnosticsSwarmOptions()` THROWS when no `bugCapture` sink is wired rather than assembling a bug-reporter that looks configured, apologizes to every request, and files nothing. `report_issue` carries INTRINSIC approval (built `intrinsicApproval: true`, attributed `"explicit"`, which no approval mode may downgrade and no permissive host hook can relax), so a human approves every filing — `reporterApprovalRule()` is exported for defence-in-depth at the swarm seam but deliberately NOT attached, because a bundle-attached rule referencing the absent `report_issue` handle makes `createDiagnosticsCrew()` throw and an `AgentSpec.rules` entry would not survive rehydration anyway. Guidance ships as two versioned, tool-less `defineSkill` singletons (`diagnostic-investigation`, the protocol, and `bug-report-writing`, the write-up contract), rendered as non-callable skill instructions and overridable per deployment by name. `run_timeline` is a bounded, redacted projection — the last 200 steps, texts clipped, tool calls reduced to name/id/normalized-outcome, no argument or result values — and the investigation skill requires an indeterminate verdict when the truncated window could hold the deciding evidence.
Install
pnpm add @nightowlsdev/agent-diagnosticsKey exports
- createInspector (read-only, danger-0, freely delegatable) / createBugReporter (approval-gated filer) / createDiagnosticsCrew (BundleDef, delegation wired)
- diagnosticsSwarmOptions (the fail-loud swarm switches: runIntrospection + bugCapture + rehydration-safe read-only scope)
- reporterApprovalRule (the OPTIONAL swarm-seam approval floor — deliberately not attached; see the persistence caveat)
- DIAGNOSTICS_TOOL_NAMES / DIAGNOSTICS_READONLY_TOOL_NAMES (the four declared built-ins + the honest read subset)
- INSPECTOR_MANIFEST / BUG_REPORTER_MANIFEST / DIAGNOSTICS_MANIFESTS (the introspectable manifests)
- DIAGNOSTIC_INVESTIGATION_SKILL / BUG_REPORT_WRITING_SKILL (the two instruction-only guidance handles, overridable by name)
Usage
import { createBugCapture, createInMemoryIssueSink, defineSwarm } from "@nightowlsdev/core";
import { createDiagnosticsCrew, diagnosticsSwarmOptions } from "@nightowlsdev/agent-diagnostics";
// 1. A capture plane — the sink is where filed bugs land (in-memory shown; your tracker in production).
const bugCapture = createBugCapture({ sink: createInMemoryIssueSink() });
// 2. The crew: a read-only inspector + an approval-gated bug-reporter, delegation already wired.
const crew = createDiagnosticsCrew(); // BundleDef — publish with applyBundle, or drop crew.agents in directly.
// 3. diagnosticsSwarmOptions() registers the four engine built-ins (run_timeline / run_failure / check_issue /
// report_issue) AND re-asserts the inspector's read-only scope for a store-rehydrated deployment. It THROWS
// when no bugCapture is wired — a bug-reporter that files nothing fails loud, it does not apologize silently.
const swarm = defineSwarm({
storage,
agents: crew.agents,
models: { allow: ["tier:"], tier },
modelFactory,
cost: { maxSteps: 20, maxCostUsd: 1 },
...diagnosticsSwarmOptions({ bugCapture, release: process.env.RELEASE }),
});
// report_issue carries INTRINSIC approval, so a human approves every filing even without reporterApprovalRule().