Skip to main content

OpenTelemetry Data Model

Complete schema of all telemetry events emitted by Gas Town. Each event consists of:

  1. Log record (→ any OTLP v1.x+ backend, defaults to VictoriaLogs) with full structured attributes
  2. Metric counter (→ any OTLP v1.x+ backend, defaults to VictoriaMetrics) for aggregation

run.id correlation: automatic run.id injection into all log records is implemented in PR #2199 (otel-p0-work-context), not yet on main. On main, correlation is possible only via resource attributes (gt.role, gt.rig, gt.agent, gt.actor).


Event Index

EventCategoryStatus
session.startSession✅ Main
session.stopSession✅ Main
agent.eventAgent🔲 PR #2199
agent.usageAgent🔲 PR #2199
agent.state_changeAgent✅ Main
bd.callWork✅ Main
mailWork✅ Main
primeWorkflow✅ Main
prime.contextWorkflow✅ Main
prompt.sendWorkflow✅ Main
nudgeWorkflow✅ Main
slingWorkflow✅ Main
doneWorkflow✅ Main
polecat.spawnLifecycle✅ Main
polecat.removeLifecycle✅ Main
daemon.restartLifecycle✅ Main
pane.readInternal✅ Main
pane.outputInternal✅ Main
formula.instantiateMolecule✅ Main
convoy.createMolecule✅ Main
agent.instantiateSession❌ Roadmap
mol.cookMolecule❌ Roadmap
mol.wispMolecule❌ Roadmap
mol.squashMolecule❌ Roadmap
mol.burnMolecule❌ Roadmap
bead.createMolecule❌ Roadmap

1. Identity hierarchy

1.1 Instance

The outermost grouping. Derived at agent spawn time from the machine hostname and the town root directory basename.

AttributeTypeDescription
instancestringhostname:basename(town_root) — e.g. "laptop:gt"
town_rootstringabsolute path to the town root — e.g. "/Users/pa/gt"

1.2 Run

Resource attributes set at process start via OTEL_RESOURCE_ATTRIBUTES (populated by buildGTResourceAttrs() in internal/telemetry/subprocess.go).

AttributeTypeSourceNotes
gt.rolestringGT_ROLE env vare.g. "gastown/polecats/Toast"
gt.rigstringGT_RIG env vare.g. "gastown"
gt.actorstringBD_ACTOR env varbd actor identity
gt.agentstringGT_POLECAT or GT_CREW env varagent name
gt.sessionstringGT_SESSION env vartmux session name — PR #2199
gt.run_idstringGT_RUN env varcorrelation key — PR #2199
gt.work_rigstringGT_WORK_RIG env varwork rig at last gt primePR #2199
gt.work_beadstringGT_WORK_BEAD env varhooked bead at last gt primePR #2199
gt.work_molstringGT_WORK_MOL env varmolecule step at last gt primePR #2199

Attributes marked PR #2199 are only set after otel-p0-work-context merges. On main, only gt.role, gt.rig, gt.actor, gt.agent are set.


2. Events

session.start / session.stop

tmux session lifecycle events.

AttributeTypeDescription
session_idstringtmux pane name
rolestringGastown role
statusstring"ok" · "error"
errorstringerror message; empty when "ok"

prime

Emitted on each gt prime invocation. The rendered formula is emitted separately as prime.context (same attributes plus formula).

AttributeTypeDescription
rolestringGastown role
hook_modebooltrue when invoked from a hook
statusstring"ok" · "error"
errorstringerror message; empty when "ok"
work_rigstring⚠️ PR #2199 — rig whose bead is on the hook
work_beadstring⚠️ PR #2199 — bead ID currently hooked
work_molstring⚠️ PR #2199 — molecule ID if the bead is a molecule step; empty otherwise

prime.context

Companion to prime, emitted in the same invocation. Carries the full rendered formula text.

AttributeTypeDescription
rolestringGastown role
hook_modebooltrue when invoked from a hook
formulastringfull rendered formula text
statusstring"ok" · "error"

prompt.send

Each gt sendkeys dispatch to an agent's tmux pane. Prompt content is not logged — only the length is recorded.

AttributeTypeDescription
sessionstringtmux pane name
keys_lenintprompt length in bytes
debounce_msintapplied debounce delay
statusstring"ok" · "error"
errorstringerror message; empty when "ok"

agent.event

Status: PR #2199 (otel-p0-work-context) — not on main. Requires GT_LOG_AGENT_OUTPUT=true and GT_OTEL_LOGS_URL.

One record per content block in the agent's conversation log. Full content, no truncation.

AttributeTypeDescription
sessionstringtmux pane name
native_session_idstringagent-native session UUID (Claude Code JSONL filename UUID)
agent_typestringadapter name ("claudecode", "opencode")
event_typestring"text" · "tool_use" · "tool_result" · "thinking"
rolestring"assistant" · "user"
contentstringfull content — LLM text, tool JSON input, tool output

For tool_use: content = "<tool_name>: <full_json_input>" For tool_result: content = <full tool output>


agent.usage

Status: PR #2199 (otel-p0-work-context) — not on main. Requires GT_LOG_AGENT_OUTPUT=true.

One record per assistant turn (not per content block, to avoid double-counting).

AttributeTypeDescription
sessionstringtmux pane name
native_session_idstringagent-native session UUID
input_tokensintinput_tokens from the API usage field
output_tokensintoutput_tokens from the API usage field
cache_read_tokensintcache_read_input_tokens
cache_creation_tokensintcache_creation_input_tokens

bd.call

Each invocation of the bd CLI, whether by the Go daemon or by the agent in a shell.

AttributeTypeDescription
subcommandstringbd subcommand ("ready", "update", "create", …)
argsstringfull argument list
duration_msfloatwall-clock duration in milliseconds
stdoutstringfull stdout (opt-in: GT_LOG_BD_OUTPUT=true)
stderrstringfull stderr (opt-in: GT_LOG_BD_OUTPUT=true)
statusstring"ok" · "error"
errorstringerror message; empty when "ok"

mail

All operations on the Gastown mail system. Carries operation and result only; message payload attributes are not recorded.

AttributeTypeDescription
operationstring"send" · "read" · "archive" · "list" · "delete" · …
statusstring"ok" · "error"
errorstringerror message; empty when "ok"

Call RecordMail(ctx, operation, err) for all mail operations.


agent.state_change

Emitted whenever an agent transitions to a new state (idle → working, etc.).

AttributeTypeDescription
agent_idstringagent identifier
new_statestringnew state ("idle", "working", "done", …)
has_hook_beadbooltrue when the agent has a non-empty bead on its hook
statusstring"ok" · "error"
errorstringerror message; empty when "ok"

Note: the attribute is has_hook_bead (bool), not hook_bead (string). The bead ID itself is not recorded in the state change event.


pane.read

Each tmux CapturePane call to read agent output.

AttributeTypeDescription
sessionstringtmux pane name
lines_requestedintnumber of lines requested
content_lenintbyte length of captured content
statusstring"ok" · "error"
errorstringerror message; empty when "ok"

pane.output

Raw pane output chunks emitted to VictoriaLogs (streaming tail of agent output).

AttributeTypeDescription
sessionstringtmux pane name
contentstringcaptured pane content chunk

Other events

All carry status and error fields.

Event bodyKey attributesMetric
slingbead, target, status, errorgastown.sling.dispatches.total
nudgetarget, status, errorgastown.nudge.total
doneexit_type (COMPLETED · ESCALATED · DEFERRED), status, errorgastown.done.total
polecat.spawnname, status, errorgastown.polecat.spawns.total
polecat.removename, status, errorgastown.polecat.removes.total
formula.instantiateformula_name, bead_id, status, errorgastown.formula.instantiations.total
convoy.createbead_id, status, errorgastown.convoy.creates.total
daemon.restartagent_typegastown.daemon.agent_restarts.total

3. Roadmap Events (not yet implemented)

The following events have no corresponding Record* function in internal/telemetry/recorder.go. They are listed here to document intended design.

agent.instantiate (roadmap)

Intended to anchor all subsequent events for a run. One span per agent spawn.

AttributeTypeDescription
agent_typestring"claudecode" · "opencode" · …
rolestringGastown role
agent_namestringagent name
session_idstringtmux pane name
rigstringallocation rig (empty for generic polecats)
issue_idstringbead ID passed at spawn via --issue; empty if none
git_branchstringgit branch of the working directory at spawn time
git_commitstringHEAD SHA of the working directory at spawn time

mol.cook / mol.wisp / mol.squash / mol.burn (roadmap)

Molecule lifecycle events. No RecordMol* functions exist yet.

bead.create (roadmap)

Per-child-bead event during molecule instantiation. No RecordBeadCreate function exists yet.


4. Metrics Reference

MetricTypeLabelsStatus
gastown.session.starts.totalCounterstatus, role✅ Main
gastown.session.stops.totalCounterstatus✅ Main
gastown.agent.state_changes.totalCounterstatus, new_state✅ Main
gastown.bd.calls.totalCounterstatus, subcommand✅ Main
gastown.bd.duration_msHistogramsubcommand✅ Main
gastown.mail.operations.totalCounterstatus, operation✅ Main
gastown.prime.totalCounterstatus, role, hook_mode✅ Main
gastown.prompt.sends.totalCounterstatus✅ Main
gastown.pane.reads.totalCounterstatus✅ Main
gastown.pane.output.totalCountersession✅ Main
gastown.nudge.totalCounterstatus✅ Main
gastown.sling.dispatches.totalCounterstatus✅ Main
gastown.done.totalCounterstatus, exit_type✅ Main
gastown.polecat.spawns.totalCounterstatus✅ Main
gastown.polecat.removes.totalCounterstatus✅ Main
gastown.daemon.agent_restarts.totalCounteragent_type✅ Main
gastown.formula.instantiations.totalCounterstatus, formula✅ Main
gastown.convoy.creates.totalCounterstatus✅ Main
gastown.agent.events.totalCountersession, event_type, role🔲 PR #2199

gt.role, gt.rig, gt.actor, gt.agent, session_id, event_type, subcommand,
operation, new_state, exit_type

6. Environment variables

VariableSet byDescription
GT_OTEL_LOGS_URLdaemon startupOTLP logs endpoint URL
GT_OTEL_METRICS_URLdaemon startupOTLP metrics endpoint URL
GT_LOG_BD_OUTPUToperatorSet to true to include bd stdout/stderr in bd.call log records
GT_LOG_AGENT_OUTPUToperatorPR #2199 — set to true to enable agent conversation event streaming. Requires GT_OTEL_LOGS_URL.
GT_RUNtmux session / subprocessPR #2199 — run UUID; correlation key across all events

7. Status Field Semantics

All events include a status field:

ValueMeaning
"ok"Operation completed successfully
"error"Operation failed

When status is "error", the error field contains the error message. When status is "ok", error is an empty string.


8. Backend Compatibility

This data model is backend-agnostic — any OTLP v1.x+ compatible backend can consume these events:

  • VictoriaMetrics/VictoriaLogs — Default for local development. Override with GT_OTEL_METRICS_URL/GT_OTEL_LOGS_URL to use any OTLP-compatible backend.
  • Prometheus — Via remote_write receiver
  • Grafana Mimir — Via write endpoint
  • OpenTelemetry Collector — Universal forwarder to any backend

The schema uses standard OpenTelemetry Protocol (OTLP) with protobuf encoding, which is universally supported.


Appendix: Source Reference Audit

Audited against origin/main @ 2d8d71ee35fafda3bbdf353683692bfcc9165476

Metrics (internal/telemetry/recorder.go)

ClaimSource
initInstruments() functionrecorder.go:59
gastown.bd.calls.total Counterrecorder.go:64
gastown.session.starts.total Counterrecorder.go:67
gastown.session.stops.total Counterrecorder.go:70
gastown.prompt.sends.total Counterrecorder.go:73
gastown.pane.reads.total Counterrecorder.go:76
gastown.pane.output.total Counterrecorder.go:79
gastown.prime.total Counterrecorder.go:82
gastown.agent.state_changes.total Counterrecorder.go:85
gastown.polecat.spawns.total Counterrecorder.go:88
gastown.polecat.removes.total Counterrecorder.go:91
gastown.sling.dispatches.total Counterrecorder.go:94
gastown.mail.operations.total Counterrecorder.go:97
gastown.nudge.total Counterrecorder.go:100
gastown.done.total Counterrecorder.go:103
gastown.daemon.agent_restarts.total Counterrecorder.go:106
gastown.formula.instantiations.total Counterrecorder.go:109
gastown.convoy.creates.total Counterrecorder.go:112
gastown.bd.duration_ms Histogramrecorder.go:117

Log events (internal/telemetry/recorder.go)

EventFunctionKey attributesSource
bd.callRecordBDCallsubcommand, args, duration_ms, status, error, stdout/stderr (opt-in)recorder.go:187, emit at recorder.go:214
session.startRecordSessionStartsession_id, role, status, errorrecorder.go:218, emit at recorder.go:227
session.stopRecordSessionStopsession_id, status, errorrecorder.go:236, emit at recorder.go:242
prompt.sendRecordPromptSendsession, keys_len, debounce_ms, status, errorrecorder.go:250, emit at recorder.go:256
pane.readRecordPaneReadsession, lines_requested, content_len, status, errorrecorder.go:266, emit at recorder.go:272
primeRecordPrimerole, hook_mode, status, errorrecorder.go:282, emit at recorder.go:292
prime.contextRecordPrimeContextrole, hook_mode, formularecorder.go:305, emit at recorder.go:310
agent.state_changeRecordAgentStateChangeagent_id, new_state, has_hook_bead (bool), status, errorrecorder.go:318, emit at recorder.go:328
polecat.spawnRecordPolecatSpawnname, status, errorrecorder.go:338, emit at recorder.go:344
polecat.removeRecordPolecatRemovename, status, errorrecorder.go:352, emit at recorder.go:358
slingRecordSlingbead, target, status, errorrecorder.go:366, emit at recorder.go:372
mailRecordMailoperation, status, errorrecorder.go:381, emit at recorder.go:390
nudgeRecordNudgetarget, status, errorrecorder.go:398, emit at recorder.go:404
doneRecordDoneexit_type, status, errorrecorder.go:413, emit at recorder.go:422
daemon.restartRecordDaemonRestartagent_typerecorder.go:431, emit at recorder.go:436
formula.instantiateRecordFormulaInstantiateformula_name, bead_id, status, errorrecorder.go:442, emit at recorder.go:451
convoy.createRecordConvoyCreatebead_id, status, errorrecorder.go:460, emit at recorder.go:466
pane.outputRecordPaneOutputsession, contentrecorder.go:477, emit at recorder.go:482

prompt.send: keys attribute absent (confirmed)

RecordPromptSend passes keys string but only emits keys_len (int64(len(keys))). The prompt content is deliberately not logged. recorder.go:256–263.

agent.state_change: has_hook_bead is bool, not string

hookBead *string pointer is converted to bool: hasHookBead := hookBead != nil && *hookBead != "". Emitted as has_hook_bead bool at recorder.go:321,328.

mail: no msg.* attributes

RecordMail(ctx, operation, err) at recorder.go:381 only emits operation, status, error. No msg.id, msg.from, msg.to, etc. No RecordMailMessage function exists — grep recorder.go for RecordMailMessage → zero matches.

GT_LOG_BD_OUTPUT

recorder.go:208os.Getenv("GT_LOG_BD_OUTPUT") == "true" gates stdout/stderr logging.

Absent events (confirmed by grep)

ClaimVerification
agent.instantiate — does not existgrep -r "agent.instantiate" internal/ → zero matches
RecordAgentInstantiate — does not existgrep -r "RecordAgentInstantiate" internal/ → zero matches
mol.cook/wisp/squash/burn — do not existgrep -r "mol\.cook|mol\.wisp|mol\.squash|mol\.burn" internal/ → zero matches
bead.create — does not existgrep -r "bead\.create|RecordBeadCreate" internal/ → zero matches
RecordMailMessage — does not existgrep -r "RecordMailMessage|MailMessageInfo" internal/ → zero matches
gastown.agent.instantiations.total — not in initInstruments()grep -r "agent.instantiations" internal/ → zero matches
gastown.mol.cooks.total etc. — not in initInstruments()grep -r "mol\.cooks|mol\.wisps|mol\.squashes|mol\.burns" internal/ → zero matches
gastown.bead.creates.total — not in initInstruments()grep -r "bead\.creates" internal/ → zero matches

PR #2199 additions (in otel-p0-work-context, not yet on main)

ClaimSource (commit 8b88de15)
RecordAgentEvent / agent.eventrecorder.go (added in 8b88de15)
RecordAgentTokenUsage / agent.usagerecorder.go (added in 8b88de15)
gastown.agent.events.total Counterrecorder.go (added in 8b88de15)
WithRunID(ctx, runID) / RunIDFromCtx(ctx)recorder.go (added in 8b88de15)
addRunID(ctx, *record) — injects run.id into all emit callsrecorder.go (added in 8b88de15)
gt.session in OTEL_RESOURCE_ATTRIBUTESsubprocess.go (updated in 8b88de15)
gt.run_id in OTEL_RESOURCE_ATTRIBUTESsubprocess.go (updated in 8b88de15)
gt.work_rig/bead/mol in OTEL_RESOURCE_ATTRIBUTESsubprocess.go (updated in 8b88de15)
GT_RUN propagation to subprocessessubprocess.go (updated in 8b88de15)
work_rig, work_bead, work_mol on prime eventrecorder.go (updated in 8b88de15)
internal/agentlog/ packagenew package in 8b88de15
internal/cmd/agent_log.gonew file in 8b88de15
internal/session/agent_logging_unix.gonew file in 8b88de15
GT_LOG_AGENT_OUTPUT env varnew in 8b88de15
telemetry.IsActive()telemetry.go (added in 8b88de15)