Developer Tools Guide
Hey there! This guide covers the two developer-facing tools TruvaG3 ships for building, exploring, and debugging agent networks: Swagger UI for discovering what each component can do (its API contract), and the Registry Viewer for seeing what the running system is actually doing right now (live observability). Between them, they answer almost every "what is this thing?" and "what just happened?" question you'll hit while developing.
If you've ever had to keep a hand-written spec file in sync with your handlers, tail five different log streams to trace one request, or guess why the LLM made the plan it did — all three of those problems get solved here.
Working Examples
Everything in this guide is backed by working code in the repo:
Swagger UI (API exploration)
- Spec generator:
core/openapi.go- Swagger UI deployment:
examples/k8-deployment/swagger-ui.yaml- Auto-discovery feed:
examples/registry-viewer-app/main.go(handleSwaggerURLs)- Live at: http://swagger.localhost
Registry Viewer (runtime observability and skill management)
- App:
examples/registry-viewer-app/- Views:
examples/registry-viewer-app/static/js/views/—registry.js,llm-debug.js,hitl.js,dag.js,skills.js,memory.js- Live at: http://registry.localhost
Both URLs are available after running
examples/k8-deployment/setup-infrastructure.shagainst the local kind cluster. We recommend having the cluster running alongside this guide so you can click through while you read.
Table of Contents
Swagger UI — API Exploration
- The
/openapi.jsonContract - Enabling the Endpoint
- Local Kind Cluster Setup
- Enterprise Integration Patterns
- Security Considerations
Registry Viewer — Runtime Observability and Skill Management
1. Overview
Building an agent network gives you two kinds of questions that developer tools need to answer. The first is "what can this thing do?" — a design-time and integration-time question about contracts and capabilities. The second is "what did this thing just do?" — a runtime question about behavior, decisions, and state. TruvaG3 ships one tool for each.
The Two Tools at a Glance
| Tool | Answers | Think of it as | URL (local kind) |
|---|---|---|---|
| Swagger UI | What can this service do? What's the request shape? What does it return? Can I try calling it? | A self-updating API catalog — every service publishes its own OpenAPI spec at /openapi.json, no hand-written YAML | http://swagger.localhost |
| Registry Viewer | Which services are running? What LLM calls did the orchestrator make and why? Is there a human approval waiting? For this specific execution, what was the plan, which skills were used, and how did each step run? Which skill packages are published? What's in shared memory right now? | A runtime dashboard plus the bundled skill-management host — reads through framework stores and APIs, refreshes on demand | http://registry.localhost |
Both are optional, opt-in, and developer-facing. Neither is required to run TruvaG3 in production — they exist so you can build, debug, and explain agent behavior while you're developing it.
Why This Matters
Think of Swagger UI as the restaurant menu — every TruvaG3 tool and agent is a restaurant, its capabilities are the dishes, and instead of maintaining a hand-written menu that drifts out of sync with the kitchen, the menu prints itself from what the chef is actually cooking today. No external tooling, no build-time code generation, no spec files to maintain.
Without self-describing APIs: With /openapi.json:
Register capability "get_weather" Register capability "get_weather"
→ Forget to update openapi.yaml → /openapi.json reflects it
→ Docs drift → Swagger UI picks it up
→ Callers guess field names → Callers "Try it out" live
→ API gateway rejects payloads → Gateway routes auto-configured
The /openapi.json endpoint is opt-in — disabled by default — so a production deployment never accidentally leaks its full API surface to anyone who can reach the pod. You enable it explicitly in dev and staging environments (via one env var or one framework option), and leave it off in prod unless you have a specific reason to turn it on behind a trust boundary.
Think of the Registry Viewer as the security camera and flight recorder for the same restaurant — it shows what's happening on the floor right now. Which waiters are working (service registrations), what the head chef is reasoning about (LLM calls), which orders got held up for manager approval (HITL checkpoints), exactly how a specific ticket moved through the kitchen (execution DAG), which reusable playbooks are published and entered that execution (skills), and what the team remembers about the dinner rush so far (shared memory).
Swagger UI alone is enough to build against TruvaG3. Registry Viewer becomes essential the moment you start debugging it — especially when the question shifts from "my code is wrong" to "my code is correct but the LLM made a decision I don't understand."
By exposing a standards-compliant OpenAPI spec at a well-known path, TruvaG3's Swagger UI side also integrates with any of the following without custom code:
- Swagger UI — interactive spec browser with "Try it out" forms
- Redoc / Redocly — read-only reference documentation
- Backstage — Spotify's developer portal and service catalog
- Kong, Apigee, AWS API Gateway — gateways that ingest OpenAPI to auto-configure routes
- SwaggerHub, Stoplight, Postman — spec registries for cross-team sharing
- Internal dev portals — anything that consumes OpenAPI URLs
You write zero code for any of this. You set one environment variable and point your existing tool at /openapi.json.
Part 1 — Swagger UI: API Exploration
Sections 2 through 6 cover the first tool: the /openapi.json contract every TruvaG3 component publishes, how to enable it, the local kind cluster Swagger UI deployment, how enterprise teams integrate with existing documentation systems, and the security model.
2. The /openapi.json Contract
This is the one promise the framework makes. When enabled, every tool and agent exposes exactly one additional endpoint:
GET /openapi.json
The response is a valid OpenAPI 3.0.0 document as JSON, generated at request time from the component's registered capabilities. "At request time" matters — if you register a new capability after startup, the very next call to /openapi.json reflects it. There is no cache to bust, no file to regenerate, and no process to restart.
What the Spec Contains
| Field | Source |
|---|---|
info.title | Component name (e.g. weather-tool-v2) |
info.description | Auto-generated OpenAPI spec for {tool|agent} {name} |
info.version | 1.0.0 |
paths.{endpoint}.post | One POST operation per registered Capability (all capabilities use POST with a JSON body) |
paths.{endpoint}.post.operationId | Capability.Name |
paths.{endpoint}.post.summary | Capability.Description |
paths.{endpoint}.post.tags | Capability type (tool, reasoning, or orchestrator — defaults to tool if unset) plus internal if Capability.Internal == true |
paths.{endpoint}.post.requestBody | If InputSummary != nil: references #/components/schemas/{Name}Input. Otherwise: inline {type: "object"} with no properties |
paths.{endpoint}.post.responses.200 | Always present. If OutputSummary != nil: references #/components/schemas/{Name}Output. Otherwise: description-only with no content field (clients should treat the 200 body as opaque) |
components.schemas.{Name}Input | Emitted only when InputSummary != nil. Built from RequiredFields (added to schema required[]) and OptionalFields (properties only) |
components.schemas.{Name}Output | Emitted only when OutputSummary != nil. Same construction rules as the input schema |
paths — /api/capabilities | Always included. GET returning an array of capability objects, tagged framework |
paths — /health | Always included. GET returning a simple health JSON (status, type, name, id), tagged framework |
The endpoint path for a capability is taken from Capability.Endpoint. If unset, it defaults to /api/capabilities/{Name}.
What is NOT in the spec: two other endpoints the framework registers automatically are intentionally left out of the generated spec to keep it focused on the public API contract:
/openapi.jsonitself (the spec endpoint does not self-document)/api/capabilities/{name}/schema— the auto-generated JSON Schema endpoint the framework exposes per capability whenInputSummaryis provided, used by the orchestration module's schema-cache validation layer (see TOOL_SCHEMA_DISCOVERY_GUIDE.md Phase 3)
How Field-Level Schemas Are Built
Each FieldHint maps directly to an OpenAPI property:
FieldHint | OpenAPI property |
|---|---|
Name | property key |
Type | type (already JSON Schema types: string, number, boolean, object, array) |
Example | example |
Description | description |
Fields in RequiredFields are added to the schema's required[] array; fields in OptionalFields are properties only.
Quality of the spec is entirely a function of the quality of the InputSummary and OutputSummary you provide when registering capabilities. See TOOL_DEVELOPMENT_GUIDE.md Section 5 and TOOL_SCHEMA_DISCOVERY_GUIDE.md for the authoring conventions.
Capability Filtering
All registered capabilities are included in the spec, including ones marked Internal: true. The Internal flag means "hidden from the LLM orchestration planner", not "hidden from API documentation" — internal capabilities are still real REST endpoints developers need to call (chat streams, session management, admin endpoints, HITL resumes). They are tagged with "internal" in the OpenAPI tags list so UI tooling can group or filter them.
If you want an endpoint completely absent from the spec, do not register it through RegisterCapability() — register the handler directly on the mux.
3. Enabling the Endpoint
The endpoint is disabled by default in core. That's deliberate — a component that ships with its full API surface pre-exposed would be a security footgun in production. You turn it on explicitly, and the framework gives you three ways to do that in priority order:
Option 1: Explicit framework option (highest priority)
tool := core.NewTool("weather-tool-v2")
tool.RegisterCapability(...)
framework, err := core.NewFramework(tool,
core.WithPort(8096),
core.WithOpenAPI(true), // enable /openapi.json
)
Use this when a single component should always expose its spec regardless of environment — for example, a reference tool whose sole purpose is to demonstrate the framework. Most components do not need this and instead rely on Option 2 at the deployment level.
Option 2: Environment variable
TRUVAG3_ENABLE_OPENAPI=true
Set this at the deployment level (ConfigMap, Helm values, kustomize patch, etc.). Every tool or agent in that deployment will expose its spec. This is the recommended path for dev and staging environments.
Option 3: Default
If neither of the above is set, the endpoint is not registered. A GET /openapi.json against the component returns 404.
Precedence
- Explicit
WithOpenAPI(...)always wins over env var and default TRUVAG3_ENABLE_OPENAPIwins over the default- Default is
false
Verifying
After enabling, curl the component's /openapi.json endpoint directly. The exact URL depends on where you are running curl:
# From inside the cluster (another pod in the same namespace):
curl -s http://weather-tool-v2-service/openapi.json | jq .openapi
# → "3.0.0"
# From your laptop via kubectl port-forward:
kubectl port-forward -n truvag3-examples svc/weather-tool-v2-service 8096:80
curl -s http://localhost:8096/openapi.json | jq .openapi
# → "3.0.0"
# From your laptop via the example swagger-ui proxy (local kind cluster only):
curl -s http://swagger.localhost/svc/weather-tool-v2-service/openapi.json | jq .openapi
# → "3.0.0"
A 404 response means the endpoint is disabled (either the option wasn't set or TRUVAG3_ENABLE_OPENAPI is unset/false). A valid OpenAPI document means the gate is on and the spec generator is running.
4. Local Kind Cluster Setup
The repo ships a working Swagger UI deployment for the local kind cluster so you can see the whole thing in action without building anything yourself. Open http://swagger.localhost, pick a service from the dropdown, and you're looking at its auto-generated spec — complete with "Try it out" buttons that actually call the pod. New tools show up in the dropdown on the next page refresh; tools scaled to zero disappear within ~30 seconds.
Important: this is scaffolding for the repo's demo environment, not part of the framework itself. If it doesn't match your environment, skip ahead to Section 5 for the patterns that do.
What Gets Deployed
Three pieces live under examples/k8-deployment/:
| File | Purpose |
|---|---|
swagger-ui.yaml | Deployment + Service + ConfigMap for the swaggerapi/swagger-ui image, plus a custom nginx config that proxies all /svc/{service-name}/... requests (spec fetches AND "Try it out" capability calls) to internal ClusterIP services |
ingress-infra.yaml | Adds an Ingress rule exposing swagger.localhost → swagger-ui service (alongside the other infra ingress rules) |
setup-infrastructure.sh | Deploys Swagger UI alongside Redis, Prometheus, Grafana, etc. |
Separately, the registry-viewer app exposes GET /swagger-urls.json — a list of every TruvaG3 service currently registered in Redis, formatted as Swagger UI's urls config array. The kind-cluster Swagger UI fetches this URL at page load to auto-populate its service dropdown.
How Auto-Discovery Works
┌──────────┐ GET / ┌────────────┐ GET /swagger-urls.json ┌────────────────┐
│ Browser ├────────────────>│ swagger-ui ├──────────────────────────>│ registry-viewer │
│ │ │ (nginx) │ │ │
│ │<────HTML + JS───┤ │<────[{name,url},...]──────┤ │
│ │ │ │ └────────┬────────┘
│ │ GET /swagger- │ │ │
│ ├──urls.json─────>│ │ │ Redis
│ │ │ │ │ SCAN
│ │ GET /svc/{svc} │ │ GET /openapi.json │
│ ├──/openapi.json─>│ ├──────────────────────>┌────────┐ │
│ │ │ │ + X-Forwarded-Host: │ tool X │<──┘
│ │ │ │ swagger.localhost │ pod │
│ │ │ │ + X-Forwarded-Prefix:│ │
│ │ │ │ /svc/{svc} │ │
│ │<────spec────────┤ │<──OpenAPI 3.0 spec────┤ │
│ │ servers:[{url: │ │ with servers URL │ │
│ │ "http://swagger│ │ pointing back here │ │
│ │ .localhost/svc/│ │ │ │
│ │ {svc}"}] │ │ │ │
│ │ │ │ │ │
│ │ POST /svc/{svc}│ │ POST /api/... │ │
│ ├──/api/capabili──> ├──────────────────────>│ │
│ │ ties/X │ │ (Try it out call) │ │
│ │<────result──────┤ │<───────result─────────┤ │
└──────────┘ └────────────┘ └────────┘
- Browser loads
http://swagger.localhost/ swagger-initializer.jsfetches/swagger-urls.json— proxied by nginx to the registry-viewer, which reads all registered services from Redis- Swagger UI renders a dropdown with every service
- When the user picks a service, Swagger UI fetches
/svc/{service-name}/openapi.json. The catch-all nginx location proxies it to that ClusterIP service's/openapi.jsonendpoint, settingX-Forwarded-Host: swagger.localhostandX-Forwarded-Prefix: /svc/{service-name}so the spec'sserversURL points back through this same proxy. - Swagger UI renders the spec, offering "Try it out" forms for every capability. Each form generates a request to
http://swagger.localhost/svc/{service-name}/api/capabilities/..., which the same nginx location proxies through to the capability endpoint on the cluster service.
This means individual agents never need to know their public ingress hostname (e.g. hitl-agent.localhost, telemetry-agent.localhost) for Swagger UI to work — the K8s Service name (already in the Redis registration) is the only routing key. New tools appear in the dropdown on the next page refresh. No config edits, no redeploys of Swagger UI. Tools that are scaled to zero or crash disappear within the Redis TTL (~30 seconds).
Dev Default: TRUVAG3_ENABLE_OPENAPI=true
For the local kind cluster workflow to work out of the box, every tool and agent's ConfigMap needs TRUVAG3_ENABLE_OPENAPI=true. Rather than require developers to add this to ~40 .env files, the shared setup-env-lib.sh library injects it automatically into every ConfigMap it creates. The injection is two-step:
# Step 1 — while scanning the .env file, track whether the developer set
# TRUVAG3_ENABLE_OPENAPI explicitly (to either true or false):
if [[ "$key" == "TRUVAG3_ENABLE_OPENAPI" ]]; then
_openapi_set_from_env=true
fi
# Step 2 — after the scan, inject the dev default only if the developer
# did NOT set it explicitly AND they have not globally disabled the default:
if [[ "${_openapi_set_from_env:-false}" != "true" ]] && [[ "${TRUVAG3_DEV_DISABLE_OPENAPI:-false}" != "true" ]]; then
kubectl_args="$kubectl_args --from-literal=TRUVAG3_ENABLE_OPENAPI=true"
fi
This applies only when deploying through the example setup.sh scripts that source this library. Production deployments managed by SRE pipelines do not source setup-env-lib.sh and therefore do not get the default — they must opt in explicitly via whatever config management they use (Helm values, kustomize, plain ConfigMap YAML, etc.).
Overriding the Dev Default
A developer can override in two ways:
- Per-tool — set
TRUVAG3_ENABLE_OPENAPIto a non-empty value (trueorfalse) in the tool's.envfile. The value wins over the library default because the library tracks the key while scanning.envand skips the injection step when it finds it. Note:TRUVAG3_ENABLE_OPENAPI=with an empty value does not register as "explicitly set" because the library skips empty values during its.envscan; to disable for a single tool, useTRUVAG3_ENABLE_OPENAPI=false. - Globally for a run —
export TRUVAG3_DEV_DISABLE_OPENAPI=truebefore runningsetup.sh rebuild. The library skips injecting the default for every tool in that run. The ConfigMap will then have noTRUVAG3_ENABLE_OPENAPIat all, so the core module's own default (false) takes effect and the endpoint is disabled.
Deploying Swagger UI to the Local Cluster
cd examples/k8-deployment
kubectl apply -f swagger-ui.yaml
kubectl apply -f ingress-infra.yaml
Or, more commonly, it is deployed as part of the infrastructure bootstrap:
./setup-infrastructure.sh
Access it at http://swagger.localhost once the pod is running.
5. Enterprise Integration Patterns
Here's the honest truth about adopting TruvaG3 in an enterprise environment: most teams already have an API documentation story. They already run a central Swagger UI, or Backstage, or an API gateway that ingests OpenAPI specs. They don't need another one.
Good news — you don't need any of the scaffolding above. The framework's contract is just the /openapi.json endpoint on each component. Everything else (the kind-cluster Swagger UI, the registry-viewer feed, the nginx proxy) is example code you can ignore. You point your existing documentation system at each service's spec URL, and it just works.
This section walks through the five most common integration patterns. Pick the one that matches your environment, or mix and match.
Pattern 1: Centralized Swagger UI / Redocly (most common)
Most orgs already run a central Swagger UI or Redocly instance backed by a list of API URLs. Add each TruvaG3 service to that list.
For Swagger UI, the urls config array accepts a list of {name, url} pairs:
[
{"name": "weather-tool-v2", "url": "https://apis.dev.corp/weather-tool-v2/openapi.json"},
{"name": "travel-chat-agent", "url": "https://apis.dev.corp/travel-chat-agent/openapi.json"}
]
The URL points to the TruvaG3 service's /openapi.json endpoint, exposed through whatever ingress or service mesh the team uses. No registry-viewer needed.
Operational model: the dev-portal team maintains the urls.json. When a new TruvaG3 service is promoted to dev or staging, it gets added via a PR.
Pattern 2: Backstage (service catalog with auto-discovery)
Backstage is Spotify's open-source developer portal. Its software catalog has a first-class API kind that renders OpenAPI specs inline using a bundled Swagger UI plugin.
Register each TruvaG3 service as a Backstage API entity in the repo's catalog-info.yaml:
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: weather-tool-v2
description: Open-Meteo weather data for locations by coordinate
spec:
type: openapi
lifecycle: production
owner: team-platform
definition:
# The exact field name and syntax for loading a spec from a URL varies
# by Backstage version. Consult the Backstage docs for the version
# your team runs:
# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api
$text: http://weather-tool-v2-service.truvag3-prod.svc.cluster.local/openapi.json
Backstage caches the spec, surfaces an interactive API explorer in the catalog, and links the API to its owning team, repo, and docs. Supports search and deep-linking.
Operational model: each service repo contains a catalog-info.yaml that registers the component and its API. Backstage's GitHub/GitLab discovery plugin auto-picks up new files on push. Your team's Backstage admin will know which loader syntax applies to your installation.
Pattern 3: API Gateway Ingestion (Kong, Apigee, AWS API Gateway)
These gateways ingest OpenAPI specs to auto-configure routes, authentication, rate limiting, and request validation.
Kong: use the Gateway declarative config with the OpenAPI plugin. Point it at each TruvaG3 service's spec URL and Kong creates routes automatically.
Apigee: use the generate-apis-from-openapi flow with the spec URL as the source.
AWS API Gateway: import the spec via the console, CLI (aws apigateway import-rest-api --body fileb://openapi.json), or Terraform.
Operational model: the platform team runs a periodic job that pulls each TruvaG3 service's /openapi.json and pushes it through the gateway's spec importer.
Pattern 4: CI/CD Spec Publishing (SwaggerHub, Stoplight, Git)
Some teams don't want live spec endpoints — they publish frozen specs to a registry during CI. This is common when APIs cross trust boundaries (internal → partner) or when spec history needs to be auditable.
Add a step to the build pipeline:
# Assuming the tool is running in a test environment during CI
curl -sf http://weather-tool-v2-service.ci-test/openapi.json > openapi.json
# Publish to SwaggerHub
curl -X POST https://api.swaggerhub.com/apis/myorg/weather-tool-v2/1.0.0 \
-H "Authorization: $SWAGGERHUB_API_KEY" \
-H "Content-Type: application/json" \
--data @openapi.json
# Or commit to a spec repo
cp openapi.json ../api-specs/weather-tool-v2.json
cd ../api-specs && git add . && git commit -m "Update weather-tool-v2 spec" && git push
Operational model: every merge to main in a tool's repo triggers a spec publish. Consumers (partner teams, SDK generators, documentation sites) pull from the registry.
Pattern 5: Dynamic Multi-Service Aggregation (the pattern we ship)
If your team wants the kind-cluster experience — one Swagger UI dropdown showing every live service with auto-refresh on deploy — the registry-viewer's /swagger-urls.json feed is a reference implementation you can port to your environment.
The endpoint reads service registrations from Redis (TruvaG3's default discovery backend) and emits the {name, url} array that Swagger UI expects. ~50 lines of Go in examples/registry-viewer-app/main.go (handleSwaggerURLs).
To adapt it:
- Different discovery backend (Consul, Eureka, etcd, k8s API): replace the
getRedisServices()call with a query against your backend. The rest of the handler is unchanged. - Different service hosting (not in-cluster): update the URL template. Instead of
/svc/{svc}/openapi.jsonproxied via nginx, point directly athttps://apis.dev.corp/{svc}/openapi.json. - Filter by team or domain: each
ServiceInfocarries aMetadata map[string]interface{}that the framework populates at registration time. Add a query-parameter filter so a team-scoped Swagger UI only shows services matching a metadata key (e.g.team=platform).
The key insight is that the aggregation step is trivial once each component publishes a standard spec. A team can build this in a day and tailor it to their environment.
6. Security Considerations
Now the tricky part. The /openapi.json endpoint exposes the full API surface of the component — every capability name, every endpoint path, every input and output field, every description. For a dev environment that's a feature. For production, it's something you need to think about carefully. This section tells you exactly what the endpoint reveals, what it doesn't, when it's safe to enable in production, and when it isn't.
Production Defaults
The framework's default is EnableOpenAPI: false. Production deployments that do not explicitly opt in do not expose the /openapi.json endpoint — GET /openapi.json returns 404.
Important caveat: disabling the OpenAPI gate does not fully hide a component's schema information. The framework separately registers a
GET /api/capabilities/{name}/schemaendpoint for every capability that provides anInputSummary, and that endpoint is wired at capability-registration time — independent of theEnableOpenAPIsetting. It returns the full JSON Schema draft-07 form of the capability's input. This is used by the orchestration module's schema-cache validation layer (see TOOL_SCHEMA_DISCOVERY_GUIDE.md Phase 3).The endpoint returns more limited information than
/openapi.json(one capability's input schema at a time, nothing about outputs, nothing about the capability list), but if your threat model requires hiding all schema data from reachable callers, you must protect these endpoints at the network layer in addition to disabling the OpenAPI gate.
What the Endpoint Reveals
Anyone who can reach the pod and is allowed to GET /openapi.json can see:
- All capability names, descriptions, and operation IDs
- All endpoint paths, including those of capabilities marked
Internal: true. This is the most architecturally revealing part of the spec — the path structure tells a reader how the component is organized internally (/chat/session/{id},/hitl/command,/admin/reindex, etc.) - All input field names, types, examples, and descriptions (from
InputSummary) - All output field names, types, examples, and descriptions (from
OutputSummary) - Tags that distinguish
tool,reasoning,orchestrator, andinternalcapabilities - The framework-provided endpoints
/api/capabilitiesand/health
The endpoint does not reveal:
- Secrets, API keys, or credentials (these live in environment variables and K8s Secrets, which are not exported to the spec)
- Request or response bodies from past calls (the spec is a schema, not a log)
- Other components' configuration, registration state, or discovery data
- End-user data
- Source code or stack traces
Note: the generator intentionally does not self-document /openapi.json or the per-capability /api/capabilities/{name}/schema endpoints. The latter are still reachable — see the caveat under Production Defaults above.
When to Enable in Production
There are legitimate reasons to enable the endpoint in production:
- Internal API portal discovery
- Dynamic API gateway configuration
- Partner API documentation behind a trust boundary
In all of these cases, the endpoint should be protected at the network layer:
- Ingress rules that only allow traffic from known systems (the API portal pod, the gateway controller)
- Mutual TLS at the service mesh level
- Network policies that restrict lateral access
Do not rely on the endpoint itself for access control — it has none. It is a publishing surface, not a secure endpoint.
When NOT to Enable in Production
- Public-facing components with anonymous access
- Components whose capability names or input schemas leak internal architecture
- Multi-tenant systems where the capability list varies by tenant
- Any deployment where the output schemas contain hints about downstream data stores or integration partners
Part 2 — Registry Viewer: Runtime Observability and Skill Management
Sections 7 and 8 cover the second tool: the Registry Viewer web app. If Swagger UI tells you what your services can do, Registry Viewer tells you what they are doing for a selected request and provides the bundled workflow for managing published skill packages. Section 9 ties both tools together with a decision table you can scan when you're stuck and not sure which one to open.
7. Registry Viewer Overview
Open http://registry.localhost against the local kind cluster and you land in a single-page web app with six tabs across the top: Registry, LLM Debug, HITL Interrupted, Execution DAG, Skills, and Memory. Each tab is backed by a framework store or management API; an optional trace link is navigation only. Most activity is observational. Deliberate business/control mutations are HITL Approve/Reject and skill publication/deletion; execution grouping also performs bounded maintenance by removing confirmed-dead IDs from the DB 0 execution-debug indexes.
The app is a reference implementation — not a framework component. It ships in examples/registry-viewer-app/ and you can fork it, strip it down, or rewrite it for your own environment. It demonstrates how framework interfaces and the included Redis/Valkey and optional Qdrant adapters can back a small Go + vanilla-JS operational UI. The Skills view calls the provider-neutral SkillAdminHandler; it does not make raw Redis writes.
What It Shows You
A quick tour of the six views before we go deep:
| View | What it answers | Data source |
|---|---|---|
| 📋 Registry | Which services are registered right now? What capabilities does each one expose? Are they healthy? When was each last seen? | Versioned registry subspace in Redis/Valkey DB 0—the same store Swagger UI's /swagger-urls.json feed reads |
| 🔍 LLM Debug | Every LLM call the orchestrator made on behalf of an agent (plan generation, synthesis, tool selection, semantic retry, memory hook calls, error analysis) plus any direct AI calls an agent made through an instrumented client—with full prompts and responses, searchable across all requests and agents. How many tokens? How long? Did it error? | Versioned llm-debug subspace in Redis/Valkey DB 0. Two writers feed the same schema: the orchestration module's RedisLLMDebugStore and telemetry.RedisLLMCallRecorder. Both writers can be active independently. |
| ✋ HITL Interrupted | Is there a human approval waiting? What did the agent pause for? Who needs to act? How much time until it expires? | Redis HITL checkpoint store (orchestration.CheckpointStore) |
| 🔀 Execution DAG | For this specific request, what happened? What was the plan? How did each step run? Which skills and LLM calls were involved? Which pipeline hooks ran, in what order, for how long, and with what outcome? Did HITL interrupt it? Was the plan regenerated mid-flight? | Versioned execution-debug subspace in Redis/Valkey DB 0, populated by the orchestrator's debug recorder |
| 🧩 Skills | Which skill packages are published? What domains and tags describe them? Does an edited package validate? What immutable versions exist, and which old versions may be deleted? | Provider-neutral SkillAdminHandler; the bundled host composes redisprovider.SkillStore in the versioned DB 0 skills subspace |
| 📝 Memory | What's in shared memory right now for this domain? What events happened in the last 24 hours? What investigations are active? What's the current digest the LLM sees? | Redis shared-memory stores + optional Qdrant for knowledge vectors |
All six views use a consistent list-and-detail visual language. Most have a list panel on the left and a detail panel on the right that populates when you pick a row. Each detail panel has view-specific tabs, and the header shows relevant stats such as record count, token count, pending count, or published-skill count.
How It Gets Its Data
The Registry Viewer does not scrape logs, query Prometheus, or query Jaeger. Its observational execution and hook views read the included Redis-backed stores; an execution's stored trace ID is only an optional browser link to the configured Jaeger UI. HITL actions proxy a command to the owning agent, while Skills actions call the framework's management handler. Grouped execution reads also perform bounded execution-index hygiene as described below. Its data comes from these sources:
| Source | What it holds | Which views use it |
|---|---|---|
| Redis/Valkey DB 0 registry subspace | Service registrations (tools and agents) with TTL refresh | Registry view; also feeds /swagger-urls.json for the Swagger UI dropdown |
Redis/Valkey DB 0 truvag3:v1:<deployment>:llm-debug:* subspace | Full LLM interaction records (prompt, response, tokens, duration, type, category, success). RedisLLMDebugStore and telemetry.RedisLLMCallRecorder write the same format. | LLM Debug view; DAG detail panel's LLM Calls tab fetches the same record by request ID |
Redis/Valkey DB 0 HITL subspace (truvag3:v1:<deployment>:hitl:{<deployment>:hitl:<agent>}:*) | Pending and expired checkpoints with plan, current step, resolved parameters, decision, status, agent name, agent address, and request mode. | HITL Interrupted view; DAG detail panel's HITL tab |
Redis/Valkey DB 0 truvag3:v1:<deployment>:execution-debug:* subspace | Full execution records (plan, per-step results, HITL state, phase history, pipeline-hook outcomes, skill evidence, metadata, and optional post-AfterSynthesis application response). Grouped/timeline reads may remove a confirmed-missing member from advisory execution/conversation indexes, but never delete an execution record. | Execution DAG view |
Stored trace ID + JAEGER_UI_URL (optional navigation) | Browser link to the corresponding Jaeger trace. Registry Viewer does not query Jaeger or derive execution/hook data from spans. | Execution DAG header only |
| Provider-neutral skill management API; included Redis/Valkey DB 0 skills subspace | Published skill metadata, immutable manifests/resources, version history and tombstones, revision tokens, and body-free audit records | Skills view; Execution DAG's Skills tab reads the separate execution-debug evidence for a request |
| Redis shared memory keys (episodic events, activities, investigations, digest cache) | Source agents' episodic events; live activity signals from ActivityCoordinator; investigation locks from InvestigationCoordinator; cached compaction digests | Memory view |
Qdrant collections (when TRUVAG3_VECTOR_DB_URL is set) | Semantic knowledge vectors (Phase 2 shared memory) | Memory view (knowledge detail) — currently behind feature flag |
Registry Viewer is not the source of truth: restarting it loses no persisted data. Its Registry, LLM Debug, execution, and memory surfaces are lenses over framework state. The execution list has one maintenance side effect: after an authoritative execution record is confirmed absent, the Viewer removes that stale ID from the affected sorted-set index. Consequently the LLM-debug subspace may be read-only to the Viewer, while the execution-debug subspace requires index-write permission. Its Skills surface is also the bundled management client and host, so successful publication or deletion intentionally changes the authoritative skill store through framework concurrency, validation, protection, and audit rules.
User-memory boundary. The Memory view exposes shared memory only; there is no per-user fact browser. Pre/Post-Execution hook cards show lifecycle activity without rendering stored fact bodies. However, opt-in LLM debug persistence preserves complete prompts and responses. A planning prompt can therefore contain user-memory enrichment that was actually supplied to the model, and the LLM Debug or execution LLM Calls view can display it. Protect Registry Viewer and the LLM-debug keyspace accordingly; the framework does not silently redact those payloads.
Prerequisites
For the views to show data, the underlying features have to be enabled in your cluster:
| View | What must be true for data to appear |
|---|---|
| Registry | Services must register with a shared Redis via core.WithDiscovery(true, "redis"). This is the default for every example tool/agent in the repo. |
| LLM Debug | Two enablement paths, often both at once: (a) Orchestrator-internal calls (plan_generation, tiered_selection, synthesis_streaming, memory hooks, error analysis, etc.) populate the store automatically when the orchestrator is constructed with TRUVAG3_LLM_DEBUG_ENABLED=true — the orchestration module's RedisLLMDebugStore is wired up by the factory. (b) Direct agent AI calls (an agent that calls ai.GenerateResponse(...) outside of an orchestration step) populate the store only when the agent wraps its AI client with ai.NewInstrumentedClient(..., debugRecorder, ...) using telemetry.NewRedisLLMCallRecorder. See examples/agent-with-telemetry/research_agent.go for the wiring. Most chat agents in the kind cluster don't need (b) because their LLM calls go through the orchestrator and are captured by (a). |
| HITL Interrupted | At least one agent must run the orchestration module with HITL enabled (HITLConfig.Enabled: true and a configured checkpoint store). See HUMAN_IN_THE_LOOP_USER_GUIDE.md. |
| Execution DAG | The orchestrator must run with TRUVAG3_EXECUTION_DEBUG_STORE_ENABLED=true. This is independent of TRUVAG3_LLM_DEBUG_ENABLED; the DB 0 execution-debug and LLM-debug subspaces supply different evidence. Pipeline-hook diagnostics are stored with the execution record. Configure JAEGER_UI_URL only if the optional Trace browser link is wanted; Registry Viewer never queries Jaeger. |
| Skills | The Registry Viewer must compose its skills backend and SkillAdminHandler; the bundled deployment uses the same topology and TRUVAG3_REDIS_NAMESPACE as runtime agents. Runtime skill evidence inside an execution also requires that agent to enable skills and TRUVAG3_EXECUTION_DEBUG_STORE_ENABLED=true. Full skill-bearing prompts require TRUVAG3_LLM_DEBUG_ENABLED=true. |
| Memory | Agents need shared-memory backends/hooks. The Viewer must use the matching Redis namespace and include the agents' domain in its own TRUVAG3_VIEWER_MEMORY_DOMAINS list. Setting only the agent's TRUVAG3_AGENT_DOMAIN does not populate the Viewer's configured domain list. See AGENT_MEMORY_USER_GUIDE.md. |
Persisted LLM interactions include available separate reasoning, finish reason and content source; these are provider facts, not success verdicts. Execution records separately carry request lifecycle and revision, so a failed request may contain successful delegated steps. Legacy records without lifecycle remain unknown. The Viewer is a consumer of those records; storage availability does not imply every field has its own rendered UI card.
On the local kind cluster, enablement varies per agent because the env vars are independent and each example was originally written for its own purpose:
| Agent | TRUVAG3_LLM_DEBUG_ENABLED | TRUVAG3_EXECUTION_DEBUG_STORE_ENABLED | Implication |
|---|---|---|---|
travel-chat-agent | ✅ | ✅ | Fully populated across execution, LLM, skill, and configured hook evidence; actual visibility still depends on which features ran for a request |
devops-chat-agent | ✅ | ✅ | Fully populated across both views |
qa-agent | ✅ | ✅ | Fully populated |
event-driven-agent | ✅ | ✅ | Fully populated |
agent-with-telemetry (research-agent-telemetry) | ✅ | ❌ | Its direct AI calls (instrumented client) appear in LLM Debug, but it does NOT appear in the Execution DAG view at all because the execution store isn't enabled |
The asymmetric setup shown above explains why agent-with-telemetry can have a
record in LLM Debug without a corresponding Execution DAG row. The reverse
configuration is also possible for a custom or newly created agent: enabling
the execution store but disabling LLM debug produces a DAG row whose LLM Calls
tab has no LLM-debug interactions. None of the other agents in this table currently
uses that reverse configuration. Always check both environment variables when
diagnosing a new agent.
8. The Six Views
This section walks through each view in detail: what the list panel shows, what the filters do, what each detail tab reveals, and concretely when to open that view while debugging. The views have similar shapes, so once you understand one, the others follow the same pattern.
8.1 Registry View — Service Catalog
Use this view when you want to ask: "Is my new tool actually registered? Is it healthy? When was it last seen? What capabilities did it publish?"
This is the simplest view and the one you'll open first when checking whether a deployment succeeded. It lists every tool and agent in the versioned Redis/Valkey DB 0 registry subspace, with the record TTL driving health—a service that stops sending heartbeats drops off the list within 30 seconds.
List panel. A sortable table of registered service rows with these columns:
- Type —
tooloragent(with an icon for quick scanning) - Name — service name as registered (e.g.
weather-tool-v2) - Health — color-coded badge:
healthy,degraded,unhealthy, orunknown - Last Seen — relative time since the last heartbeat ("3s ago", "12m ago")
- Capabilities — count; click the row to expand in the detail panel
Filters. A search box (matches name, description, and capability names) plus three filter buttons: All / Agents / Tools.
Detail panel. Pick a service and the right side shows its registration record in two tabs:
- Formatted — human-readable breakdown organized into sections: Service Info (ID, name, type, health, last seen, description, address, port), Metadata (any registered metadata key/value pairs), and Capabilities (a card per capability showing name, internal/external badge, description, endpoint, and the input parameter list — required fields shown as
name: typetags, optional asname?: type). Each capability card has a "View Full JSON" expandable for the raw capability object. - JSON — the raw service registration record returned by
/api/services, syntax-highlighted.
Note on what this view does NOT show. The registry-viewer's backend uses its own narrower
Capabilitystruct that omits theOutputSummaryfield — it only surfacesInputSummary. So neither the Formatted view nor the JSON view will show output schemas for capabilities, even when the underlying agent registered them. To see output schemas, open the same service in Swagger UI instead — Swagger UI fetches the agent's/openapi.jsondirectly, which includes both input and output schemas. This is purely a registry-viewer-side gap, not a framework-level one.
Header stats. Total, Agents, Tools.
Typical workflow. You deploy a new tool, port-forward or open http://registry.localhost, switch to the Tools filter, search for your tool name, and confirm (a) it appears, (b) health is green, (c) the capability you just added is listed with the field hints you expected. If any of those are missing, the problem is in the tool's startup — it never reached Initialize() or Start() successfully, or RegisterCapability() wasn't called.
8.2 LLM Debug View — Raw LLM Call Stream
Use this view when you want to ask: "What LLM calls has this system made? How many tokens are we burning? Why did that call fail? Show me the exact prompt and the exact response."
This is the flat, searchable log of every LLM interaction recorded into the LLM debug store, written by two sources working in parallel: the orchestration module's internal components (planner, synthesizer, tool selector, memory hooks, error analyzer, semantic retry — all of which write directly to the store when TRUVAG3_LLM_DEBUG_ENABLED=true) and any agent that wraps its AI client with InstrumentedAIClient for direct LLM calls outside of orchestration. Unlike the DAG view (which assembles everything for a single execution), this view cuts across all executions, all agents, and all categories.
List panel. A sortable table with columns:
- Request ID — the orchestration request ID this LLM debug record belongs to (e.g.
orch-1775509658444116982) - Conversation —
original_request_id || request_id. For a one-shot request these are the same value, so this column is never blank; it just mirrors the Request ID. For a HITL conversation that spans multiple resume requests, all related records share the sameoriginal_request_id, and the field is prefixed with a 🔗 icon and rendered as a clickable link that filters the table to the linked records. - Source —
source_components, an array of agent/component names that contributed LLM calls to this record. When the array is empty (orchestrator-internal calls only — plan_generation, synthesis, etc.), the column shows a singleorchestratorbadge. When agents wrap their AI client withInstrumentedAIClientand passWithComponentName(...), their names appear here as additional badges. So a record can show one orchestrator badge, one or more agent badges, or both. - Interactions — how many distinct LLM calls rolled up under this request ID (plan + synthesis + retries + memory hooks + everything else)
- Tokens — total tokens for the record (sum of all interactions)
- Status —
SuccessorHas Errors(set when any interaction in the record failed) - Time — relative time since the record was created
Filters. Search box (matches request ID, source component name, or interaction type strings) plus four filter buttons:
- All — every record
- Success — only records where all interactions succeeded
- Errors — only records with at least one failed interaction
- Grouped — sorts the table by
original_request_idthencreated_at, so all records belonging to the same HITL conversation cluster together. Useful when one user-visible conversation spans multiple checkpoint resumes — each resume becomes its own request_id but shares the original_request_id. Combined with the 🔗 click-to-filter on the Conversation column, this is the workflow for inspecting a multi-step HITL session end-to-end.
Detail panel. Pick a record and two tabs open:
- Interactions — an expandable list of every LLM call in the record, each showing: type (
plan_generation,tiered_selection,synthesis_streaming,user_memory_reconciliation, etc. — see §8.4 for the full list), category (typicallyllmorembedding), duration, token counts (prompt + completion + total), model, attempt number, success flag, and the full prompt and response. Click an interaction to expand it; click again to collapse. There's an "expand all" button when you need to scan fast. - JSON — the full record as stored in Redis, for when you want to pipe it into
jqor share it in a bug report.
Header stats. Records, Total Tokens.
Typical workflow. Something weird happened — an orchestrator picked the wrong tool, or the synthesis is nonsense, or the LLM refused a task. You open LLM Debug, find the request by ID (or by searching the conversation text), and walk through the interactions. Within seconds you can tell whether the problem was bad tool-selection prompting, a broken plan-generation output, a stale memory digest, or a retry loop that burned tokens without progressing.
Gotcha. If a request is missing from this view entirely, check that the agent that handled it has TRUVAG3_LLM_DEBUG_ENABLED=true — the orchestration module's LLM debug store is gated on this env var. If the request appears but the Source column shows only an orchestrator badge (no agent name), the agent's direct AI client isn't wrapped with InstrumentedAIClient. That's fine for chat agents whose LLM calls all flow through the orchestrator (the orchestrator handles them), but it means a custom agent that calls ai.GenerateResponse(...) directly outside of an orchestration step won't have its direct calls attributed to it. See examples/agent-with-telemetry/research_agent.go for the wiring pattern.
8.3 HITL Interrupted View — Pending Approvals
Use this view when you want to ask: "Is anything waiting on me right now? Which agent paused for approval, and what did it want to do?"
This is the operational queue for Human-in-the-Loop checkpoints. Every time an agent hits a sensitive capability (or a full-plan approval gate) and pauses, a checkpoint record lands in Redis with a TTL. This view shows every pending one, sorted by expiry (most-urgent-first by default), and lets a developer review and respond.
List panel. A sortable table with columns:
- Priority — derived from the interrupt reason and time remaining; expiring-soon checkpoints bubble to the top
- Agent — which agent created the checkpoint (sortable)
- Type —
plan(full-plan approval before any step runs) orstep(step-level sensitive capability) orerror(semantic retry requesting guidance) - Request — the original user request that triggered the workflow
- Progress — how many steps have already run vs. how many remain
- Created — when the checkpoint was created (sortable)
- Expires — time until the checkpoint auto-expires (sortable, default-sorted ascending so the most urgent are at the top)
Filters. Search box (matches checkpoint ID, request text, or decision message) plus All / Plan / Step / Error.
Detail panel. Pick a checkpoint and three tabs open:
- Overview — the essential info: which agent, why it paused, the blocked step (with resolved parameters — not templates, so a developer can see
amount: 15000instead ofamount: "{{step-1.total}}"), the interrupt decision with reason and sensitive-capability matches, and the action panel. - Plan — the full
RoutingPlanthat was running, rendered as a step list so you can see the full intended workflow, not just the blocked step. - JSON — the complete
ExecutionCheckpointobject.
Header stats. Pending, Critical.
Action panel — when buttons appear. The Overview tab's action panel adapts based on the checkpoint's request_mode field:
- Non-streaming checkpoints (
request_mode == "non_streaming"): inline Approve & Resume and Reject buttons that POST to/api/hitl/command(proxied from the registry-viewer to the owning agent). An optional reason text box is rendered above the buttons. Buttons are disabled if the checkpoint has noagent_nameset, with a warning suggestingTRUVAG3_AGENT_NAMEneeds to be configured on the agent. - Streaming checkpoints (
request_mode == "streaming"): the buttons are hidden and replaced with a warning: "Streaming request — approve/reject must be performed by the connected client." This is intentional — for streaming HITL, the UI that launched the conversation is the only place that can complete the round-trip cleanly. The dashboard reverts to a read-only view of the checkpoint, useful for inspecting state but not for resolving it.
Typical workflow. An agent paused a destructive operation (say, a Kubernetes rollout restart) and you want to review it before approving. You open this view, see the pending checkpoint at the top of the list, switch to the Plan tab to confirm the full workflow makes sense, check the resolved parameters on the blocked step, and either approve (which resumes the workflow from where it paused) or reject. If you walk away and the checkpoint expires, the default action configured in HITLConfig.DefaultAction kicks in.
8.4 Execution DAG View — The Flagship Debugging View
Use this view when you want to ask: "For this specific request, walk me through exactly what happened — the plan, the steps, the LLM calls, the hooks, the memory, everything — in one place."
This is the view you open when logs aren't enough. It takes a single orchestration execution and assembles every piece of data the orchestrator recorded about it into one interactive timeline: the planned DAG, per-step results, skill lifecycle evidence, all LLM calls grouped by phase, pre-execution hooks, post-execution hooks, HITL checkpoints that fired during the run, and any plan regenerations that happened mid-flight. This is the most information-dense view in the app and where most debugging time is spent.
List panel. Conversation grouping is the default. A conversation header shows its matching/total turn count and can be expanded into chronological user turns. HITL resumes and delegated work appear as related executions under their owner when that relationship is verifiable. If the owner record has expired, the row is kept visible without drawing a false parent branch and is labeled Parent execution unavailable. Incomplete lineage metadata is labeled separately from confirmed retention loss. A Grouped / Flat control exposes the legacy one-row-per-execution list when needed.
Each execution row retains these sortable columns:
- Status — success / failed / interrupted (with colored icon)
- Request — the original user query text (sortable by request text)
- Steps — how many steps the plan had
- Duration — best available non-additive elapsed envelope (sortable)
- Created — when the request started (sortable, default-sorted descending so the newest is at the top)
Filters. Search box (matches request ID or query text) plus All / Success / Failed / Interrupted.
Detail panel — eight tabs, most of which only appear when the execution has relevant data. The visibility rules are: LLM Calls appears if the record has any LLM interactions, Pre-Execution and Post-Execution appear if any pre/post hook ran, HITL appears if any checkpoint fired, and Skills appears if skill lifecycle evidence was recorded. DAG Visualization, Step Details, and Raw JSON are always visible.
Tab 1: DAG Visualization
The headline tab renders the execution as an interactive graph using Cytoscape.js with the Dagre layout algorithm. Tool nodes have unique displayed step numbers across the selected execution, including an HITL continuation; phase-local step IDs remain visible as identifiers. Click a node for details or an edge for its relationship.
Edges do not all mean the same thing. Scheduler dependencies control execution order within a plan. Cross-phase data-lineage edges show that a later step read prior-phase results but do not impose scheduler order. Phase-transition and pipeline-hook edges describe lifecycle flow. The popup and legend identify the edge type so visual provenance is not mistaken for a planner or scheduler contract.
The header strip above the canvas shows a comprehensive metadata line:
- Original request text (click to expand/collapse if truncated)
- Agent name (which agent owned this execution)
- Request ID and one compact Trace link to the configured Jaeger UI
- Step count and Elapsed time: the largest credible phase-loop, step-timestamp, and LLM/step wall-clock envelope, without adding overlapping durations
- Success / failed / interrupted badge
- LLM call count and aggregate call time (if any); aggregate call time is a workload measure and may overlap elapsed time
- HITL badge (if HITL fired but didn't interrupt)
- Phase count (for iterative multi-phase plans — shown when > 1)
- Forced-terminal badge (when the orchestrator had to force-terminate a phase)
Below the header, if the plan was regenerated mid-flight, a warning strip shows each regeneration event: which phase regenerated, what validation error caused it, and the old → new plan ID transition. This is the single biggest debugging shortcut for "why did the orchestrator change its mind halfway through?"
A Steps Only / Full Flow toggle at the top switches between two modes:
- Steps Only — just the tool-invocation steps, clean and compact
- Full Flow — steps + orchestration/agent LLM calls + HITL checkpoints + stored deterministic pipeline-hook outcomes, interleaved by lifecycle, phase-local order, and timing evidence. The execution record is the source of truth; the optional trace link is navigation only.
Full Flow also works when a hook stops the request before a plan is admitted. Later-phase failures keep earlier completed steps. Hook popups show the stored Pipeline consequence separately from invocation status and reported effects. An optional hook failure may say Request continued; a required rejection says Request stopped. Decision details exposes policy, action, and reason. Older records without these fields say not recorded. A hook without usable phase placement stays visible as a labeled, non-directional evidence branch.
A hook panic says Panic propagated · request stopped; an optional error policy does not suppress a panic. The graph retains completed earlier work. A successful native synthesis call can remain visible in LLM Calls alongside a failed AfterSynthesis hook and failed request, with no final application response. These are separate outcomes, not conflicting status labels.
Tab 2: Pre-Execution
Visible when BeforePlanning or AfterPlanning activity appears in stored hook
records or LLM-debug evidence. It shows context preparation before planning and
deterministic/LLM-backed plan governance around the planner:
- User Memory Enrichment — which recall calls ran (
user_memory_recall_identity,user_memory_recall_summary,user_memory_recall_query,user_memory_recall_universal), how long each took, and whether theuser_memory_enrichment_injectedstep succeeded (meaning the<user_profile>XML fragment made it into the plan-generation prompt). Note: this tab shows that the enrichment ran, not the contents of the profile itself — see the privacy note in §7. - Memory Compaction —
activity_compaction_incrementalcalls and their durations. Useful for diagnosing slow first-requests where the digest cache was cold.
Stored hook cards prove that a deterministic hook ran even when it made no LLM call. They show the lifecycle phase, terminal status, phase-local order, duration, start time, iterative plan phase where applicable, and exact recorded failure. This tab is the place to look when you suspect the planner did not receive expected context or a post-planning policy altered/validated its plan.
Tab 3: Step Details
Flat per-step list with everything the orchestrator recorded about each step: step ID, tool/agent name, resolved capability, resolved parameters (not templates), the response body, success flag, error message if any, duration, attempt count (retries), start and end timestamps, and any metadata the step recorded (e.g., HITL checkpoint metadata, resolution-layer info from the 4-layer parameter binding system).
When a step retried, you can see exactly how many attempts happened and which resolution layer fixed the parameters on the retry — the orchestration module's 4-layer parameter-binding story is fully visible here.
Tab 4: LLM Calls
Only visible when the execution had LLM interactions recorded. This tab shows the orchestration-level calls for the execution—planning, selection, continuation, error-analysis, synthesis, and similar calls—in contiguous display order. Hook-internal interactions are intentionally routed to Pre-Execution or Post-Execution so the same call is not presented twice. The cross-request LLM Debug view remains the place to inspect every stored interaction family.
Each call shows its type, category, model, token counts, aggregate call duration, attempt number, generation settings, success state, prompt, and response. Click an interaction to expand long content. Synthesis labels distinguish two paths:
- Buffered synthesis (
synthesis): Pre-hook LLM synthesis output, with View Pre-hook Output. - Native streaming (
synthesis_streaming): LLM synthesis output streamed to client, with View Streamed Output.
Both show model output produced before application AfterSynthesis hooks.
Hooks can change the final application response, but cannot retract tokens
already streamed. Neither card proves the final business outcome; a later hook
can fail after the model call succeeded. A stored post-hook application response,
when available, appears separately in Post-Execution.
When to open this tab vs. the LLM Debug view: use this tab when you already have the request ID open and want to see the LLM calls in the context of the steps they produced. Use the LLM Debug view when you want to search across requests (e.g., "show me every synthesis call that errored yesterday").
Tab 5: Post-Execution
Visible when an AfterExecution/AfterSynthesis hook ran or when the execution
contains a stored post-hook application response. It is the mirror image of
Pre-Execution:
-
Post-hook application response — the
StoredExecution.FinalResponsecaptured afterAfterSynthesishooks, with its source label. If it differs from non-streaming synthesis, the application changed the draft. On native streaming, the card also warns that post-hook text can differ from content already streamed to the client. -
Event Summarization — LLM calls that generated the episodic event text written to shared memory
-
User Memory Write-back —
user_memory_extraction,user_memory_embed_candidate,user_memory_similarity_search,user_memory_reconciliation(per-candidate path) oruser_memory_reconciliation_batch+ per-itemuser_memory_reconciliation_batch_itemrows (batched path),user_memory_reconciliation_skip(no neighbors), anduser_memory_remember. The batched path emits ONEuser_memory_reconciliation_batchrow with categoryllmcarrying the full token usage, plus N lightweight_batch_itemrows with categoryderivedso the registry viewer's LLM-call totals are not double-counted. Same privacy rule as Pre-Execution: you see the invocations, not the fact contents.
This tab answers both "what did the system learn from this request?" and "what
application response existed after post-synthesis governance?" Workflow-mode
ExecutePlanWithSynthesis and older records can legitimately lack the latter;
the UI states that no post-hook evidence was stored instead of treating the raw
synthesis as the outcome.
The deterministic hook cards use the same stored fields as Pre-Execution. They do not expose a Jaeger span as “evidence”: the persisted execution record is the evidence, while a trace remains an optional, request-level FYI link.
Tab 6: HITL
Only visible when a checkpoint fired during this execution. Shows the checkpoint lifecycle within the execution timeline:
- When the checkpoint was created
- What decision triggered it (policy match, sensitive-capability match, semantic retry request)
- When and how it was resolved (approve / reject / expire with default action)
- The resumed execution path, if any
For current complete records, lifecycle joins use the exact checkpoint ID. The
initial interruption and its resume are linked when their checkpoint identities
agree; unrelated interrupted siblings are not attached merely because they
share an original_request_id. For an older or partial interrupted record that
has no checkpoint identity at all, the Viewer falls back to the shared initial
request ID and can therefore attach multiple siblings from that request family.
Treat that fallback as less precise than an identity-backed lifecycle.
Useful for tracing "the user approved X, then what happened?"
Tab 7: Skills
Only visible when the execution contains skill lifecycle evidence. It presents an expandable five-stage flow—Pin, Activate, Select resources, Load, and Project—followed by cards with the exact body-free decision records. Use it to answer which developer-bound candidates resolved, which exact revisions were pinned, why an automatic skill or resource was selected or skipped, whether a manifest/resource came from the registry or local cache, and which admitted identities entered each phase prompt. Diagnostics explain bounded degradation or omission without storing instruction or resource bodies.
Use the LLM Calls tab when you need to inspect the corresponding effective prompt text. Use the Skills tab for the safer lifecycle and integrity view.
Tab 8: Raw JSON
The full execution record as stored in the versioned Redis/Valkey DB 0 execution-debug subspace. Use it when you need to copy-paste into a bug report or pipe into external tooling.
Header stats. Executions, Success Rate.
Typical workflow. The chat UI shows a weird answer, or an agent took 40 seconds when you expected 4. You grab the request ID from the chat UI or from the agent's logs, open DAG view, paste the ID into the search box, and click into the record. The DAG Visualization tab gives you the shape immediately; the Step Details tab shows which step was slow; the LLM Calls tab shows the prompts that drove the decisions; and the Pre-/Post-Execution tabs reveal whether the memory hooks did something unexpected.
8.5 Memory View — Shared Memory Inspector
Use this view when you want to ask: "What does the LLM know right now? What events happened in the last 24 hours? Are any investigations in progress? What does the compacted digest look like?"
This view is for inspecting the state of shared memory (domain-scoped). It answers "what does my domain's memory look like right now, and what's the LLM currently seeing when it generates plans?"
List panel. The left side has three controls instead of a table:
- Domain dropdown — populated from Redis (
/api/memory/domains). Defaults to the last domain you picked (stored in localStorage). Agents from the same domain share memory, so you pick the domain to inspect. Typical values:infrastructure,travel,devops. - Time range buttons —
1h,6h,24h(default),3d,7d. Filters the events list below. - Investigations strip — a horizontal scrolling bar showing currently-active investigations from the
InvestigationCoordinator. Each entry is a pill rendered asentity_id (holder)— the entity that's being investigated (e.g. a pod name or deployment name) and the agent currently holding the investigation lock. Strip is hidden entirely when there are no active investigations. The underlying data comes from/api/memory/investigations?domain={domain}which returnsentity_id,holder, anddomainonly — there's no started-at timestamp in the response. - Events list — the actual event timeline for the chosen domain and time range, with relative timestamps, source agents, entity references, and outcomes.
Filters. Type in the search area to narrow the events list; click an event to pull it into the detail panel.
Detail panel — three tabs:
- Event Detail — everything about the selected event: source agent, request ID, timestamp, entities, actions, outcome, and any metadata the event carries.
- Live Activity — the real-time activity signals from other agents in the same domain. This is the data that goes into the
<agent_coordination>XML block the LLM sees before planning. - Digest — the compacted LLM-generated summary of domain activity, as it would appear in the
<agent_memory>XML block at plan time. This is the single most useful tab when you're debugging "why did the LLM make that assumption?" — the digest is what the LLM actually reads on each request, and it's the output of an LLM call itself (compaction).
Header stats. Domain, Events, Investigations.
What this view does not show. The Memory view has no per-user fact list, vector-entry browser, or identity lookup. Pre/Post-Execution presents user- memory hook activity without fact bodies. This does not make Registry Viewer a payload-redaction boundary: when LLM debug is enabled, full planning and hook prompts/responses can contain the user context supplied to a model and are visible in LLM Debug or LLM Calls. Direct Qdrant inspection is still required to enumerate authoritative stored facts.
Typical workflow. Two agents keep making conflicting decisions about the same entity (say, the same Kubernetes deployment). You open Memory view, pick the infrastructure domain, widen the time range to 7 days, and scroll the event list looking for what each agent recorded about that entity. The Digest tab tells you what the LLM is currently reading about the domain; the Live Activity tab tells you whether one of the agents is mid-investigation right now.
8.6 Skills View — Package Management
Use this view when you want to ask: "Which reusable skill packages are published, what is in the current package, and how do I validate or publish a safe update?"
The left panel lists published skills with their display name, exact
namespace/name, description, published version, domains, and tags. Domain
and tag values appear as pills, and search matches identity and taxonomy.
Select a row to open three detail tabs:
- Package — a readable package summary and complete JSON editor. Validate runs deterministic normalization and checks without mutation. Analyze is available only when the host configured an optional advisor. Publishing uses the current ETag and an idempotency key automatically.
- Versions — body-free immutable history, deletion/tombstone status, and guarded single-version or inclusive-range deletion. The published revision and its immediate predecessor remain protected.
- JSON — the syntax-highlighted current published representation.
The Skills view is a control-plane surface, unlike the observational Registry, LLM Debug, execution, and memory views. It never binds a skill to a running agent; bindings remain developer-owned code or complete deployment environment configuration. For the authoring schema, binding examples, progressive runtime loading, operational safety, and troubleshooting, use the Agent Skills Guide.
9. Which Tool for What: A Decision Table
When you're stuck and not sure which tab to open, scan this table.
| I want to... | Open | In view | Tab / filter |
|---|---|---|---|
| Try calling a tool's capability with a filled-in form | Swagger UI | dropdown → service | "Try it out" on the operation |
| Know whether my new tool registered at all | Registry Viewer | Registry | All filter, search for name |
See the exact InputSummary AND OutputSummary a capability published | Swagger UI | dropdown → service | look at requestBody and responses.200 |
See only the InputSummary (input fields and types) plus the rest of the registration record | Registry Viewer | Registry | click service → Formatted or JSON tab. Note: OutputSummary is NOT exposed by registry-viewer's API — use Swagger UI for output schemas |
| Find every LLM call that errored today | Registry Viewer | LLM Debug | Errors filter |
| Read the exact prompt the planner sent for request X | Registry Viewer | LLM Debug | search by request ID → Interactions tab → plan_generation |
| See every LLM call for request X in the context of the steps they produced | Registry Viewer | Execution DAG | search by request ID → LLM Calls tab |
| Check total token usage across all agents | Registry Viewer | LLM Debug | header stats (Total Tokens) |
| Visualize the DAG the orchestrator planned for a request | Registry Viewer | Execution DAG | search by request ID → DAG Visualization tab |
| See which memory recall calls fired before planning on a specific request | Registry Viewer | Execution DAG | click request → Pre-Execution tab |
| Find out why the orchestrator regenerated a plan mid-flight | Registry Viewer | Execution DAG | click request → DAG Visualization → look for the regeneration warning strip |
| Walk through per-step parameters (resolved, not template) for a failed request | Registry Viewer | Execution DAG | click request → Step Details tab |
| See the 4-layer parameter resolution metadata for a retry | Registry Viewer | Execution DAG | click request → Step Details tab → step metadata |
| See which exact skill revisions and resources affected request X | Registry Viewer | Execution DAG | click request → Skills tab |
| Inspect or validate the current published skill package | Registry Viewer | Skills | select the skill → Package tab |
| Review or delete eligible old skill revisions | Registry Viewer | Skills | select the skill → Versions tab |
| Find any pending HITL approvals right now | Registry Viewer | HITL Interrupted | All filter, default sort shows most-urgent first |
| Approve or reject a paused workflow (dev testing) | Registry Viewer | HITL Interrupted | click checkpoint → Overview tab → Approve/Reject buttons (non-streaming checkpoints only; streaming checkpoints must be resolved by the connected client) |
See the full RoutingPlan for a paused checkpoint | Registry Viewer | HITL Interrupted | click checkpoint → Plan tab |
| Inspect what the LLM is currently reading from shared memory | Registry Viewer | Memory | pick domain → Digest tab |
| See recent events in a domain | Registry Viewer | Memory | pick domain + time range → events list |
| Find which agents are actively working on the same entity right now | Registry Viewer | Memory | investigations strip at the top, or Live Activity tab on an event |
| Inspect stored facts for a specific user | Not in the dashboard (by design) | — | Query Qdrant directly or use the agent's user-memory HTTP endpoints |
| Generate an OpenAPI spec to feed into Kong / Apigee / Backstage | Swagger UI | — | GET /openapi.json directly from each service |
Confirm /openapi.json is enabled on a component | curl | — | curl http://svc/openapi.json — 404 means disabled, 200 means enabled |
10. See Also
Framework contract (what ships in core):
core/openapi.go— Spec generator and/openapi.jsonHTTP handler. Only registered whenEnableOpenAPIis true.core/config.go—HTTPConfig.EnableOpenAPIfield,WithOpenAPI(bool)option, andTRUVAG3_ENABLE_OPENAPIenv var loader. Default isfalse.core/tool.go/core/agent.go—/openapi.jsonendpoint gate insetupStandardEndpoints()/Start(). Also the/api/capabilities/{name}/schemaendpoint registration inRegisterCapability()— note that this one is NOT gated byEnableOpenAPI(see Section 6).core/openapi_test.go— Unit tests for the on/off gate (TestOpenAPIEndpoint_*) and the spec generator (TestGenerateOpenAPISpec_*).
Swagger UI scaffolding (local kind cluster only):
examples/k8-deployment/swagger-ui.yaml— Swagger UI Deployment + Service + ConfigMap + nginx proxy for the local kind cluster.examples/registry-viewer-app/main.go—handleSwaggerURLsis the reference implementation for Pattern 5. Reads from Redis and emits a Swagger UI compatibleurlsfeed.examples/k8-deployment/setup-env-lib.sh—truvag3_create_configmapinjectsTRUVAG3_ENABLE_OPENAPI=truefor every tool deployed via the example setup scripts. Dev-workflow only.
Registry Viewer implementation:
examples/registry-viewer-app/— Full app source (Go backend + vanilla-JS frontend).examples/registry-viewer-app/main.go— Backend: Redis scan loops, HTTP handlers for every view's data, HITL command proxy.examples/registry-viewer-app/static/js/views/registry.js— Registry view (§8.1).examples/registry-viewer-app/static/js/views/llm-debug.js— LLM Debug view (§8.2).examples/registry-viewer-app/static/js/views/hitl.js— HITL Interrupted view (§8.3).examples/registry-viewer-app/static/js/views/dag.js— Execution DAG view (§8.4), including grouped lineage, full-flow graph, and all detail tabs. Treat its size as implementation detail rather than a stable contract.examples/registry-viewer-app/static/js/views/skills.js— Skill package-management view (§8.6).examples/registry-viewer-app/static/js/views/memory.js— Memory view (§8.5).examples/registry-viewer-app/k8-deployment.yaml— Kubernetes deployment and service.examples/k8-deployment/ingress-infra.yaml— Ingress rule exposingregistry.localhost.
Related guides:
- Tool Development Guide — How to write good
InputSummaryandOutputSummarywhen registering capabilities. Spec quality depends on this. - Tool Schema Discovery Guide — The 3-phase schema architecture (descriptions → field hints → full JSON Schema) that underpins all capability metadata. Phase 3 is what the per-capability
/schemaendpoint implements. - Agent Memory User Guide — How to wire shared and user memory into an agent. The Memory view in the Registry Viewer reads the storage this guide tells you how to set up.
- Human-in-the-Loop User Guide — How HITL checkpoints work. The HITL Interrupted view in the Registry Viewer is an operational dashboard for the checkpoints this guide tells you how to create.
- Agent Skills Guide — How to author, publish, bind, observe, and operate skills. The Skills view hosts its management workflow, while the execution Skills tab explains runtime selection and projection.
- Distributed Tracing Guide — How W3C trace IDs flow through TruvaG3. The DAG view surfaces trace IDs so you can pivot into Jaeger for even lower-level detail.
- Environment Variables Guide — Full list of
TRUVAG3_*environment variables, includingTRUVAG3_ENABLE_OPENAPI,TRUVAG3_LLM_DEBUG_ENABLED, andTRUVAG3_AGENT_DOMAIN. - Architecture Overview — Framework-wide architectural overview.