Skip to content

feat: one-call, env-driven observability and audit instrumentation - #159

Merged
hung12ct merged 5 commits into
mainfrom
feat/one-call-instrumentation
Nov 3, 2026
Merged

hung12ct merged 5 commits into
mainfrom
feat/one-call-instrumentation

Conversation

@hung12ct

@hung12ct hung12ct commented Nov 3, 2026

Copy link
Copy Markdown
Owner

What & why

Makes observability and audit logging one call each, enabled purely by env. Without env they do nothing, so the same code runs everywhere. Also fixes a bug that broke the env-only OTel setup.

tel, shutdown, _ := otelsetup.Setup(ctx, otelsetup.Config{ServiceName: "my-agent"})
defer shutdown(ctx)
tel.Instrument(loop, otelllm.WithSystem("anthropic"), otelllm.WithModel(model))

audit, _ := auditlog.FromEnv(ctx) // nil client when GOPHERAGENT_AUDIT_ENDPOINT is unset
defer audit.Close(ctx)
audit.Instrument(loop)

Changes

  • Providers.Instrument(loop, ...): wires the loop, its LLM and every registered tool. No-op when disabled; idempotent.
  • tools.Registry.Use(mw...): wraps an already-populated registry, including YAML-built ones.
  • Fix: OTEL_EXPORTER_OTLP_ENDPOINT=host:port (no scheme, as our docs showed) failed every export with dns resolver: missing address. Scheme-less values are now passed as host:port; URLs still go to the exporter.
  • auditlog.FromEnv + Client.Instrument(loop, ...): a nil *Client is a valid disabled client.
  • Config.DefaultTenantID: single-tenant apps no longer need WithActor. Before this, every Instrument-produced record failed validation.
  • Demo now uses one call each.

Testing

  • One-call output matches the manual wiring (OTel span tree and audit trail), calling twice doesn't double anything, and disabled or nil is a no-op. Mutation checks confirm each guard is tested.
  • The env-only export test covers both host:port and URL forms; it fails without the fix.
  • Ran the real demo with no env (nothing sent) and with env against an OTLP receiver (spans, metrics and audit all arrived).

Checklist

  • Title is a Conventional Commit
  • gofmt -l . prints nothing
  • make lint is clean
  • make build passes
  • make test and make test-race pass
  • Errors wrapped with a package prefix
  • CHANGELOG.md updated if this PR is cut as a release tag (at tag time)
  • pkg/telemetry/ changes approved by the maintainer

…ndpoint

- tel.Instrument(loop) wires loop, LLM and every registered tool; no-op when
  telemetry is off, idempotent (otelllm/oteltools skip already-wrapped).
- tools.Registry.Use wraps an already-populated registry.
- Fix: OTEL_EXPORTER_OTLP_ENDPOINT=host:port (no scheme) failed every export
  with an empty host; it is now passed to the exporter as host:port.
- Chat span is named "chat" when the model is unknown.
- FromEnv reads GOPHERAGENT_AUDIT_*; unset endpoint returns a nil client whose
  methods are no-ops, so the same code runs with auditing on or off.
- audit.Instrument(loop) wires tools, HITL, permissions and hook rejections.
- Config.DefaultTenantID (env default "default") so single-tenant apps need
  no WithActor; without it every record failed validation.
- GOPHERAGENT_AUDIT_REQUIRED=true makes a missing endpoint an error instead
  of silently disabling auditing.
- FromEnv reports audit health on the global OTel meter, so failed/dropped
  records alert without extra wiring.
- Both Instrument calls warn once about tools, HITL or permissions attached
  after them (compared by name, no false alarm on extra middleware).
@hung12ct
hung12ct merged commit 967df32 into main Nov 3, 2026
2 checks passed
@hung12ct
hung12ct deleted the feat/one-call-instrumentation branch November 3, 2026 12:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant