feat: observability via kit bootstrap - #296
Merged
Merged
Conversation
The CLI and its daemon start @agentage/observability before anything else loads, so node:http, fetch and the embedded server-memory tools are instrumented. Both bins become loaders (program.ts / daemon/entry.ts hold what they used to), because the kit's module hooks only see modules imported after they register. A real dependency here, unlike server-memory: `agentage` is installed globally once. Inert without OTEL_EXPORTER_OTLP_ENDPOINT, stderr-only, and stdout is guarded while the kit boots so its tracer banner cannot land in --json output.
Contributor
|
🎉 PR Validation ✅ PASSED Commit: Checks:
Ready to merge! ✨ 🔗 View workflow run |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Goal
Give the CLI and its local daemon the estate's observability, without breaking a single byte of stdout.
Dependency decision: a real one
@agentage/observability(+ its@opentelemetry/apipeer, listed so one copy is hoisted) is a plaindependencyhere -agentageis installed globally, once (npm install -g @agentage/cli), so the kit's ~41 packages are a one-time cost, not a per-invocation one. That is the opposite call from@agentage/server-memory(agentage/server-memory#38), which is run throughnpxper MCP client start and takes the kit optionally instead.src/package-guard.test.ts, which pins the runtime dependency set exactly, is updated with that rationale.Change
Both bins become loaders: the kit instruments
node:http,fetchand MCP tool registration through module hooks, and a hook only sees modules imported after it registers - so nothing may be statically imported ahead of the bootstrap.src/cli.ts- node-version guard,startObservability('agentage-cli'), then dynamically importssrc/program.ts(the commander wiring, moved verbatim).src/daemon-entry.ts- same shape; the daemon itself moved tosrc/daemon/entry.tsand exportsboot()instead of self-invoking. The bin path is unchanged (dist/daemon-entry.js), which is whatspawnDaemonresolves.src/observability.ts- the guarded bootstrap. Wrapped in try/catch even though the kit is a dependency: a half-installed global must degrade to a working CLI.spawnDaemonpinsOTEL_SERVICE_NAMEfor the child, so the daemon reports asagentage-daemonand never inherits the name we defaulted into the CLI's env. A name the user set is passed through to both.Inert until
OTEL_EXPORTER_OTLP_ENDPOINTnames a collector. What you get then: HTTP client/server spans, onekind: "tool"wide event permemory__*call served by the daemon (the embedded@agentage/server-memorysurface), one error shape, crash capture.stdout purity
@agentage/observability1.0.0 announces an enabled tracer with aconsole.log(dist/internal/tracer.js:88) - which would land inside--jsonoutput or on the daemon's wire.startObservabilitydivertsprocess.stdout.writeto stderr for the duration of the bootstrap import and restores it in afinally; a unit test asserts both the silence and the restore. The real fix belongs in the kit (that line should be alog.info); drop the workaround when it ships.Runtime evidence, built bin, collector configured (
OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:9):Verified
npm run verifygreen: type-check (+ e2e) + lint + format:check + 462 tests / 46 files + build.npm run test:e2e -- --grep @offlinegreen: 35 passed, including the daemon/mcptier that spawnsdist/daemon-entry.js- the restructured entry path.Notes, not fixed here
stdio: 'ignore', so its stderr (kit lines included) is discarded in normal use; telemetry reaches a collector over OTLP regardless. Pre-existing, unchanged by this PR.node:http, so it gets HTTP spans and tool events but no request-log line (the kit's request log wires itself onto expresslisten). Its/api/healthis still the daemon's own envelope, not@agentage/observability/health.User-Agent: agentage-cli/<v>+X-Agentage-*-Versionbut nox-client-type, so the edge classifies them by UA (service). The e2e suite does tagx-client-type: test(e2e/client-type.ts). Flagging only - out of scope here.Not in this PR
No publish.
npm publishis workflow-only in this estate; the release rides the Friday train and wants dogfooding first.