Observability configuration¶
The settings that turn each observability sink on and point it somewhere: the audit event log, the analytics JSONL
sink and the Postgres database it feeds, the trajectory sink, skill-trigger tracking, and the dashboard read mode.
Every other setting is in ../configuration.md; what the sinks record is in
../observability/event-streams.md and
../observability/trajectories.md, and how the database, dashboards, and usage
parser read them back is in ../observability/analytics-database.md,
../observability/analytics-dashboard.md, and
../observability/usage.md, which ../observability.md maps.
Settings¶
EVENT_LOG_PATH— default (unset). optional JSONL audit sink, one event per line, no built-in rotation. Seeevent-streams.md#audit-event-log.ANALYTICS_LOG_PATH— defaultLOG_DIR/analytics.jsonl. project-local analytics JSONL sink. Set to empty /off/disabled/noneto disable. Which kinds land here — the per-tick and per-run records, the one bounded record each candidate of the daily terminal-artifact maintenance pass earns, and the three families written to this sink and the audit log alike (human-wait transitions, agent-run budget, and late-split events) — is inventoried onevent-streams.md#analytics-sinkrather than repeated here, so the list has one home as it grows. Two of them answer different questions and are not to be read as one: astage_evaluationwithresult="ok"says the stage handler completed without raising, not that the issue moved — a handler that parked the issue for a human evaluatesok— while apark_awaiting_humanrecord says the workflow is now waiting on a human and why. A park is a workflow-progress record, not an execution error.ANALYTICS_RETENTION_DAYS— default90. retention window forANALYTICS_LOG_PATH. The polling loop callsretention.prune_with_retention_logging()once per tick. Set to0(or any non-positive value) to keep raw data indefinitely.ANALYTICS_DB_URL— default (unset). libpq connection string for the analytics Postgres service in../../analytics-db/compose.yml. NOT read by the polling loop — orchestrator correctness does not depend on database availability. Empty /off/disabled/nonedisables both the sync CLI and dashboard reads. Seeanalytics-database.md.TRAJECTORY_LOG_PATH— default (unset, off). opt-in path switch for the trajectory sink — an independent JSONL file for per-run agent reasoning trajectories, separate fromANALYTICS_LOG_PATH. Defaults off: unset / empty /off/disabled/none(case-insensitive) all disable it; any other value is the explicit opt-in path. When enabled,record_agent_exitwrites one redacted, head/tail-truncatedagent_trajectoryrecord per tracked run (the orchestrator prompt lands asuser_input); when off, no trajectory work runs and theagent_exitrecord is unchanged. Never touchesANALYTICS_LOG_PATH, the analytics Postgres sync, or the analytics dashboard. Local filesystem only and observation-only — the polling loop never reads it back, so the file is safe to delete without affecting workflow state. A dedicated developer Streamlit viewer reads this JSONL file directly — no Postgres or sync — when you browse recorded trajectories. Privacy: redaction masks only secret-shaped env values (and the GitHub token), not issue/repo content, so an enabled trajectory file can carry issue titles/bodies, quoted source, and the agent's text turns in cleartext; scope its permissions accordingly. Seetrajectories.md#trajectory-sinkand the developer launch instructions.TRAJECTORY_RETENTION_DAYS— default90. retention window forTRAJECTORY_LOG_PATH, same semantics asANALYTICS_RETENTION_DAYS:prune_trajectory_records()removes older records and0(or any non-positive value) keeps trajectories indefinitely. Parsed from.env, but not yet called from the polling loop, so it affects the file only when an operator-driven prune process runs; do not overlap an external prune with live trajectory appends. See the trajectory cron examples intrajectories.md.TRACK_SKILL_TRIGGERS— default (unset, off). opt-in switch for skill-trigger tracking.1/true/on/yes(case-insensitive) makesrecord_agent_exitparse the agent's triggered skills and foldskills_triggered/skills_triggered_count/skills_available, the per-load evidence tierskills_evidence(confirmedfor a claudeSkillcall,inferredfor a codex directSKILL.mdread), and the incidental pairskills_incidental/skills_incidental_count(path-only codex references —git diff/git status/rg, a redirect target, any non-reader command — recorded independently of loads and kept out of the trigger fields/events) into eachagent_exitrecord. It also makes_run_agent_trackedemit oneskill_triggeredaudit event per distinct triggered skill toEVENT_LOG_PATH(when that sink is set); incidental references never emit one. Default off so a default install's records and audit log stay shape-compatible with today's, and needs no Postgres DDL — theextras JSONBcolumn absorbs the new fields. Both backends' triggered-skill shapes are now pinned against captured streams (claudeSkilltool-use blocks; codexskills/<name>/SKILL.mdreads, a heuristic file-open signal — seeusage.md). The offered-skills set (skills_available) is read from claude'ssystem/initframeskillsarray (confirmed against a real stream capture); codex's stream carries no such frame, so it is backfilled out-of-band from the filesystem viaskills.discovery.discover_local_skill_sources(cwd)(a scan of the run's worktree.agents/skills/.claude/skillsroots plus the global$CODEX_HOME/skills), whose per-nameproject/user/harnesssource level is recorded beside the names asskill_levels— dropped for a claude run, whose stream names no source directory to classify. Both per-skill read models file a cell under that level as well as the name, and both first fill a name no recorded map covers from the repository's ownrepo_skill_catalog, so a claude run's load — and, for adoption, the incidental reference and the historical offer beside it — of a name the repository offers at exactly one level lands in that level's cell instead of anunknownone beside it. Only an uncatalogued or two-level name keepsunknown, and a level the record itself carried is never overwritten. Both skill tables render that level as a sortableLevelcolumn of their own. Once on, the dashboard's "Skill adoption" panel opens on a collapsed invocation-level diagnostic carrying the per-role/backend trigger rate (skill_reads.get_skill_trigger_rates) and the per-skill trigger matrix (skill_reads.get_skill_trigger_matrix) pairing each repo's offered-skill catalog with the skills its runs triggered, and reports per-session adoption (skill_reads.get_skill_adoption, underobservability/analytics/query/) beneath it — how many logical sessions had each skill available and how many loaded it, with incidental references kept as a separate column that never raises the rate — split into a collapsed section per source level (project, user, harness, plus one for the cells no record classified), all over the accumulated fields. Theskills_evidencetier carries only the emittedconfirmed/inferredload values; the incidental bucket and the read-side legacy availability inference (a load whose session reported noskills_availablemetadata) are described alongside the per-session adoption semantics inevent-streams.md#session-aware-skill-adoption. See alsoevent-streams.md#agent_exit-recordsand the audit event log.DASHBOARD_PARALLEL_READS— default (unset, off). opt-in switch for the Streamlit dashboard's parallel read fan-out.1/true/on/yes(case-insensitive) flips the dashboard's widget reads from sequential to aThreadPoolExecutor(eight workers). Parsed once per Streamlit process, when the page's read-mode owner is first imported; the polling loop never reads it.
ANALYTICS_LOG_PATH, ANALYTICS_RETENTION_DAYS, ANALYTICS_DB_URL, TRACK_SKILL_TRIGGERS, TRAJECTORY_LOG_PATH, and
TRAJECTORY_RETENTION_DAYS are parsed by orchestrator/observability/analytics/environment.py and bound on
orchestrator/observability/analytics/settings.py, the one holder every analytics owner reads them off (the analytics
surfaces own their own configuration rather than the orchestrator/config resolver). EVENT_LOG_PATH is resolved by
the orchestrator/config resolver and bound on the config package because the audit event log is a general-purpose
audit surface rather than analytics-specific.
Analytics dashboard quickstart¶
The pipeline is opt-in and layered: the orchestrator writes JSONL (ANALYTICS_LOG_PATH), a local Postgres aggregates it
(ANALYTICS_DB_URL), and Streamlit reads from Postgres. Each layer is independent; the polling loop never touches
Postgres or Streamlit. The sync runs directly from the installed pipx environment; its launch commands and cron
example are in the operator workflow. The dashboard needs a
source checkout and the optional dashboard dependencies; setup and launch commands are in the
developer guide.
For a pipx installation, the JSONL sink defaults to LOG_DIR/analytics.jsonl, typically
~/.local/state/chipping-orchestrator/logs/analytics.jsonl after package setup. Configure ANALYTICS_DB_URL in
~/.config/chipping-orchestrator/.env for the installed sync. Set the same database URL in the dashboard checkout's
.env, and an absolute ANALYTICS_LOG_PATH there if testing the sync from source. The operator provisions the
database following service layout.
Replay is idempotent, and relaunching the dashboard never affects workflow progress.
See analytics database for the schema and sync internals, and analytics dashboard for the layout and empty/error banners.