convertible-cli 0.6.0__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.markdownlint-cli2.yaml +3 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/CHANGELOG.md +13 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/CLAUDE.md +29 -3
- convertible_cli-0.6.0/README.md → convertible_cli-0.7.0/PKG-INFO +62 -0
- convertible_cli-0.6.0/PKG-INFO → convertible_cli-0.7.0/README.md +40 -17
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/cli/__init__.py +3 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/cli/_commands/drive.py +61 -30
- convertible_cli-0.7.0/convertible/cli/_commands/telemetry.py +115 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/explain/catalog.py +53 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/loop.py +77 -52
- convertible_cli-0.7.0/convertible/telemetry/__init__.py +237 -0
- convertible_cli-0.7.0/convertible/telemetry/_otel.py +325 -0
- convertible_cli-0.7.0/docs/plans/2026-05-28-convertible-gains-otel-gps-observability.md +73 -0
- convertible_cli-0.7.0/docs/specs/2026-05-28-convertible-gains-otel-gps-observability.md +54 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/pyproject.toml +19 -1
- convertible_cli-0.7.0/tests/test_telemetry.py +270 -0
- convertible_cli-0.7.0/tests/test_telemetry_cli.py +88 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_zero_deps.py +9 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/uv.lock +256 -1
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/agent-config/SKILL.md +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/agent-config/scripts/show.sh +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/cicd/SKILL.md +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/cicd/scripts/workflow.sh +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/communicate/SKILL.md +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/run-tests/SKILL.md +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/sonarclaude/SKILL.md +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/spec-to-plan/SKILL.md +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/think/SKILL.md +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/think/scripts/think.sh +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/version-bump/SKILL.md +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills/version-bump/scripts/bump.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.claude/skills.local.yaml.example +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.devague/current +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.devague/current_plan +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.devague/frames/convertible-gains-an-extensibility-layer-like-clau.json +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.devague/frames/convertible-v0-ships-point-it-at-a-repo-task-and-i.json +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.devague/plans/convertible-gains-an-extensibility-layer-like-clau.json +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.devague/plans/convertible-v0-ships-point-it-at-a-repo-task-and-i.json +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.flake8 +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.github/workflows/publish.yml +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.github/workflows/tests.yml +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/.gitignore +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/LICENSE +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/__init__.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/__main__.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/artifact.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/cli/_banner-big.txt +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/cli/_banner.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/cli/_banner.txt +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/cli/_commands/__init__.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/cli/_commands/agents.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/cli/_commands/cli.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/cli/_commands/commands.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/cli/_commands/doctor.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/cli/_commands/explain.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/cli/_commands/hooks.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/cli/_commands/learn.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/cli/_commands/overview.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/cli/_commands/session.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/cli/_commands/skills.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/cli/_commands/wheels.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/cli/_commands/whoami.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/cli/_errors.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/cli/_output.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/commands.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/config.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/configdir.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/contract.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/engine.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/engines/__init__.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/engines/mock.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/engines/vllm_openai.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/explain/__init__.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/handoff.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/hooks.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/layers.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/registry.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/convertible/tools.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/culture.yaml +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/docs/plans/2026-05-26-convertible-v0-ships-point-it-at-a-repo-task-and-i.md +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/docs/plans/2026-05-27-convertible-gains-an-extensibility-layer-like-clau.md +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/docs/skill-sources.md +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/docs/specs/2026-05-26-convertible-v0-ships-point-it-at-a-repo-task-and-i.md +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/docs/specs/2026-05-27-convertible-gains-an-extensibility-layer-like-clau.md +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/sonar-project.properties +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/__init__.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_agents_cli.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_artifact.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_banner.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_boundary.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_cli.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_cli_introspection.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_commands.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_commands_cli.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_config.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_configdir.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_contract.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_drive.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_e2e_extensibility.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_e2e_mock.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_engine.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_handoff.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_hooks.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_hooks_cli.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_layers.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_layers_engine_parity.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_loop.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_mock_engine.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_registry.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_review_fixes.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_session.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_skills_cli.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_tools.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_vllm_live.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_vllm_openai.py +0 -0
- {convertible_cli-0.6.0 → convertible_cli-0.7.0}/tests/test_wheels.py +0 -0
|
@@ -19,6 +19,9 @@ ignores:
|
|
|
19
19
|
- ".local/**"
|
|
20
20
|
- ".afi/**"
|
|
21
21
|
- ".teken/**"
|
|
22
|
+
# The virtualenv — installed packages (e.g. the [otel] extra's opentelemetry,
|
|
23
|
+
# idna) ship their own non-conformant Markdown; never lint dependencies.
|
|
24
|
+
- ".venv/**"
|
|
22
25
|
# Vendored skills are cited verbatim from guildmaster — do not reformat them.
|
|
23
26
|
- ".claude/skills/**"
|
|
24
27
|
# devague artifacts (frames, exported specs/plans) are generated verbatim from
|
|
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
Format follows [Keep a Changelog](https://keepachangelog.com/). This project
|
|
6
6
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.7.0] - 2026-05-28
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- GPS: opt-in OpenTelemetry traces + metrics for a drive (issue #22). Spans (`convertible.drive` -> `convertible.tool.*` -> `convertible.handoff`) and metrics (steps, tokens, tool latency, tool calls, hook denials, drive duration) emit over OTLP from the loop + the shared drive path, so every engine is instrumented identically (all-engines rule).
|
|
13
|
+
- `convertible telemetry status` / `overview` introspection noun, plus an explain catalog entry.
|
|
14
|
+
- `TelemetryConfig` resolved from `CONVERTIBLE_OTEL_*` / standard `OTEL_*` env vars (`OTEL_SDK_DISABLED` honored as a kill-switch).
|
|
15
|
+
- Optional `[otel]` extra (opentelemetry SDK + OTLP/HTTP exporter); install with `pip install "convertible-cli[otel]"`.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- `loop.run()` and `execute_drive` accept/own telemetry, defaulting to a no-op resolved from the environment (mirrors the hooks pattern). Off by default it is a strict no-op: no spans, no SDK import, `TaskResult` unchanged.
|
|
20
|
+
|
|
8
21
|
## [0.6.0] - 2026-05-28
|
|
9
22
|
|
|
10
23
|
### Added
|
|
@@ -21,6 +21,11 @@ The car metaphor *is* the architecture:
|
|
|
21
21
|
- **Wheels** — engines are plugins discovered via the `convertible.engines`
|
|
22
22
|
Python entry-point group (`convertible/registry.py`).
|
|
23
23
|
- **Dashboard** — the JSON result artifact + step trace (`convertible/artifact.py`).
|
|
24
|
+
- **GPS** — opt-in OpenTelemetry traces + metrics (`convertible/telemetry/`).
|
|
25
|
+
Instrumented in the loop + the shared drive path so every engine emits it
|
|
26
|
+
(all-engines rule), exactly like hooks. Off by default; the OpenTelemetry SDK
|
|
27
|
+
is an optional `[otel]` extra, imported lazily, so the base install stays
|
|
28
|
+
dep-free. Surfaced via the `telemetry` introspection noun.
|
|
24
29
|
- **Handoff** — branch/commit/push + `gh pr create`, gated for offline/CI
|
|
25
30
|
(`convertible/handoff.py`).
|
|
26
31
|
- **Command templates** — named, parameterized task recipes in
|
|
@@ -53,8 +58,9 @@ The buildable spec and plan this implementation converged from live in
|
|
|
53
58
|
|
|
54
59
|
In scope: the chassis, the entry-point wheel contract, exactly two engines
|
|
55
60
|
(`mock`, `vllm-openai`), the git/PR handoff, command templates, lifecycle
|
|
56
|
-
hooks, the foreground interactive palette,
|
|
57
|
-
config (`convertible/layers.py`)
|
|
61
|
+
hooks, the foreground interactive palette, layered per-model AGENTS/skills
|
|
62
|
+
config (`convertible/layers.py`), and GPS — opt-in OpenTelemetry traces +
|
|
63
|
+
metrics (`convertible/telemetry/`), with the SDK as an optional `[otel]` extra.
|
|
58
64
|
|
|
59
65
|
**Out of scope for v0** — do not add without re-speccing: a multi-engine
|
|
60
66
|
router/policy "gearbox", an execution sandbox, a daemon/server mode,
|
|
@@ -81,7 +87,15 @@ test (`tests/test_e2e_mock.py`) is the guard.
|
|
|
81
87
|
- **No runtime dependencies.** `pyproject.toml` keeps `dependencies = []`; the
|
|
82
88
|
vLLM driver speaks the OpenAI wire format over stdlib `urllib`; commands and
|
|
83
89
|
hooks use only stdlib (`json`, `subprocess`, `pathlib`). Don't add a runtime
|
|
84
|
-
dep without a strong reason — dev-only deps go in the `dev` group.
|
|
90
|
+
dep without a strong reason — dev-only deps go in the `dev` group. The one
|
|
91
|
+
documented exception is **GPS**: the OpenTelemetry SDK ships as an optional
|
|
92
|
+
`[project.optional-dependencies] otel` extra, never a base dependency. It is
|
|
93
|
+
imported **lazily** inside `convertible/telemetry/_otel.py` (only when
|
|
94
|
+
telemetry is enabled), so `dependencies = []` and the zero-deps guard
|
|
95
|
+
(`tests/test_zero_deps.py`) still hold — the guard imports `convertible.loop`
|
|
96
|
+
/ `convertible.telemetry` / `convertible.cli` and asserts no third-party leak
|
|
97
|
+
even with the extra installed. Keep the SDK confined to `_otel.py`; never
|
|
98
|
+
import `opentelemetry` from any other convertible module.
|
|
85
99
|
- **Agent-first CLI.** New verbs are `convertible/cli/_commands/` modules with a
|
|
86
100
|
`register(sub)`, wired in `convertible/cli/__init__.py`. Results to stdout,
|
|
87
101
|
diagnostics/errors to stderr (never mixed); every command supports `--json`;
|
|
@@ -98,6 +112,11 @@ test (`tests/test_e2e_mock.py`) is the guard.
|
|
|
98
112
|
hook firing — new engine wheels inherit the full lifecycle layer automatically
|
|
99
113
|
and must not duplicate it. The all-engines rule applies: a hook config that
|
|
100
114
|
fires on `mock` must fire identically on `vllm-openai`.
|
|
115
|
+
- **Telemetry belongs to the chassis too.** `convertible/loop.py` (per tool
|
|
116
|
+
call) and the shared `execute_drive` path (root + handoff spans) own all
|
|
117
|
+
telemetry; no engine module touches the `telemetry` package. Off by default it
|
|
118
|
+
is a strict no-op (no spans, no SDK import, `TaskResult` unchanged) — protect
|
|
119
|
+
that so the e2e shape test and zero-deps guard keep passing.
|
|
101
120
|
- **Repo-shipped hooks run by default (trusted-operator-env model D2).** There
|
|
102
121
|
is no `--no-hooks` flag today. A per-repo trust gate is a tracked follow-up.
|
|
103
122
|
Document this gap clearly; never document a non-existent flag.
|
|
@@ -118,6 +137,13 @@ uv run convertible hooks list --repo . # list configured hooks
|
|
|
118
137
|
uv run convertible hooks overview # surface description
|
|
119
138
|
uv run convertible session --repo . --engine mock # interactive palette
|
|
120
139
|
|
|
140
|
+
# GPS / telemetry (opt-in; needs the [otel] extra):
|
|
141
|
+
uv run convertible telemetry status # resolved telemetry config
|
|
142
|
+
uv run convertible telemetry overview # surface description
|
|
143
|
+
uv sync --extra otel # install the OpenTelemetry SDK
|
|
144
|
+
CONVERTIBLE_OTEL_ENABLED=1 OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \
|
|
145
|
+
uv run convertible drive "<task>" --repo . --engine mock --no-pr # emits a trace
|
|
146
|
+
|
|
121
147
|
# Lint + gates CI enforces:
|
|
122
148
|
uv run black --check convertible tests
|
|
123
149
|
uv run isort --check-only convertible tests
|
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: convertible-cli
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: Convertible CLI is a swappable coder-agent harness that turns different models into repo workers behind one shared task contract.
|
|
5
|
+
Project-URL: Homepage, https://github.com/agentculture/convertible
|
|
6
|
+
Project-URL: Issues, https://github.com/agentculture/convertible/issues
|
|
7
|
+
Author: AgentCulture
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Software Development
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Provides-Extra: otel
|
|
17
|
+
Requires-Dist: opentelemetry-api>=1.25; extra == 'otel'
|
|
18
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.25; extra == 'otel'
|
|
19
|
+
Requires-Dist: opentelemetry-sdk>=1.25; extra == 'otel'
|
|
20
|
+
Requires-Dist: opentelemetry-semantic-conventions>=0.46b0; extra == 'otel'
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
1
23
|
# convertible
|
|
2
24
|
|
|
3
25
|
```text
|
|
@@ -42,6 +64,7 @@ which one ran.
|
|
|
42
64
|
| **Tool-loop** | the bounded agentic loop the engine drives the repo through |
|
|
43
65
|
| **Wheels** | replaceable engine plugins, discovered via Python entry points |
|
|
44
66
|
| **Dashboard** | the JSON result artifact + step trace each run writes |
|
|
67
|
+
| **GPS** | opt-in OpenTelemetry traces + metrics (`convertible/telemetry/`) |
|
|
45
68
|
| **Garage** | `convertible wheels list` — the engines installed in this env |
|
|
46
69
|
|
|
47
70
|
## What ships in v0
|
|
@@ -313,6 +336,43 @@ The session loops until the user enters `q`, `quit`, or an empty line. Any
|
|
|
313
336
|
driver flags accepted by `drive` (`--engine`, `--no-pr`, `--base-url`, etc.)
|
|
314
337
|
are also accepted by `session`.
|
|
315
338
|
|
|
339
|
+
## GPS: OpenTelemetry observability
|
|
340
|
+
|
|
341
|
+
A drive can emit **OpenTelemetry traces + metrics** so it's observable against an
|
|
342
|
+
OTLP collector — not just the per-run JSON artifact. Telemetry lives in the
|
|
343
|
+
chassis (the loop + the shared drive path), so **every engine** emits it
|
|
344
|
+
identically, exactly like lifecycle hooks.
|
|
345
|
+
|
|
346
|
+
It is **off by default** and a strict no-op when off (no spans, no SDK import,
|
|
347
|
+
the result artifact unchanged). The OpenTelemetry SDK is an **optional extra** —
|
|
348
|
+
the base install keeps zero runtime dependencies:
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
pip install 'convertible-cli[otel]' # or: uv sync --extra otel
|
|
352
|
+
export CONVERTIBLE_OTEL_ENABLED=1
|
|
353
|
+
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 # OTLP/HTTP collector
|
|
354
|
+
uv run convertible drive "<task>" --repo . --engine mock --no-pr
|
|
355
|
+
# -> stderr prints "trace: <id>"; the collector receives the spans + metrics
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Requested without the extra installed, convertible degrades to a no-op with a
|
|
359
|
+
one-line stderr notice — it never fails the drive.
|
|
360
|
+
|
|
361
|
+
**Signals.** Spans: `convertible.drive` (root) → `convertible.tool.*` (per tool
|
|
362
|
+
call) → `convertible.handoff`. Metrics: `convertible.steps`, `convertible.tokens`,
|
|
363
|
+
`convertible.tool.latency`, `convertible.tool.calls`, `convertible.hook.denials`,
|
|
364
|
+
`convertible.drive.duration`.
|
|
365
|
+
|
|
366
|
+
**Config** (precedence: explicit > `CONVERTIBLE_OTEL_*` > standard `OTEL_*` >
|
|
367
|
+
default): `CONVERTIBLE_OTEL_ENABLED`, `CONVERTIBLE_OTEL_ENDPOINT` /
|
|
368
|
+
`OTEL_EXPORTER_OTLP_ENDPOINT`, `CONVERTIBLE_OTEL_SERVICE_NAME` /
|
|
369
|
+
`OTEL_SERVICE_NAME`. `OTEL_SDK_DISABLED=true` is honored as a kill-switch.
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
uv run convertible telemetry status # resolved config + whether the SDK is installed
|
|
373
|
+
uv run convertible telemetry overview # describe the surface
|
|
374
|
+
```
|
|
375
|
+
|
|
316
376
|
## Per-model instructions & skills
|
|
317
377
|
|
|
318
378
|
Convertible composes a model-specific **system prompt** for every drive from two
|
|
@@ -394,6 +454,8 @@ rely on a non-existent flag.
|
|
|
394
454
|
| `agents overview` | Describe the agents surface. |
|
|
395
455
|
| `skills list` | List resolved skill docs for a model. |
|
|
396
456
|
| `skills overview` | Describe the skills surface. |
|
|
457
|
+
| `telemetry status` | Show the resolved GPS / OpenTelemetry config + whether the SDK is installed. |
|
|
458
|
+
| `telemetry overview` | Describe the telemetry surface. |
|
|
397
459
|
| `session` | Open a foreground interactive palette. |
|
|
398
460
|
| `wheels list` | List discovered engine wheels (the garage). |
|
|
399
461
|
| `whoami` | Report this agent's nick, version, backend, and model. |
|
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: convertible-cli
|
|
3
|
-
Version: 0.6.0
|
|
4
|
-
Summary: Convertible CLI is a swappable coder-agent harness that turns different models into repo workers behind one shared task contract.
|
|
5
|
-
Project-URL: Homepage, https://github.com/agentculture/convertible
|
|
6
|
-
Project-URL: Issues, https://github.com/agentculture/convertible/issues
|
|
7
|
-
Author: AgentCulture
|
|
8
|
-
License-Expression: MIT
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
Classifier: Development Status :: 3 - Alpha
|
|
11
|
-
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
-
Classifier: Topic :: Software Development
|
|
15
|
-
Requires-Python: >=3.12
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
|
|
18
1
|
# convertible
|
|
19
2
|
|
|
20
3
|
```text
|
|
@@ -59,6 +42,7 @@ which one ran.
|
|
|
59
42
|
| **Tool-loop** | the bounded agentic loop the engine drives the repo through |
|
|
60
43
|
| **Wheels** | replaceable engine plugins, discovered via Python entry points |
|
|
61
44
|
| **Dashboard** | the JSON result artifact + step trace each run writes |
|
|
45
|
+
| **GPS** | opt-in OpenTelemetry traces + metrics (`convertible/telemetry/`) |
|
|
62
46
|
| **Garage** | `convertible wheels list` — the engines installed in this env |
|
|
63
47
|
|
|
64
48
|
## What ships in v0
|
|
@@ -330,6 +314,43 @@ The session loops until the user enters `q`, `quit`, or an empty line. Any
|
|
|
330
314
|
driver flags accepted by `drive` (`--engine`, `--no-pr`, `--base-url`, etc.)
|
|
331
315
|
are also accepted by `session`.
|
|
332
316
|
|
|
317
|
+
## GPS: OpenTelemetry observability
|
|
318
|
+
|
|
319
|
+
A drive can emit **OpenTelemetry traces + metrics** so it's observable against an
|
|
320
|
+
OTLP collector — not just the per-run JSON artifact. Telemetry lives in the
|
|
321
|
+
chassis (the loop + the shared drive path), so **every engine** emits it
|
|
322
|
+
identically, exactly like lifecycle hooks.
|
|
323
|
+
|
|
324
|
+
It is **off by default** and a strict no-op when off (no spans, no SDK import,
|
|
325
|
+
the result artifact unchanged). The OpenTelemetry SDK is an **optional extra** —
|
|
326
|
+
the base install keeps zero runtime dependencies:
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
pip install 'convertible-cli[otel]' # or: uv sync --extra otel
|
|
330
|
+
export CONVERTIBLE_OTEL_ENABLED=1
|
|
331
|
+
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 # OTLP/HTTP collector
|
|
332
|
+
uv run convertible drive "<task>" --repo . --engine mock --no-pr
|
|
333
|
+
# -> stderr prints "trace: <id>"; the collector receives the spans + metrics
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Requested without the extra installed, convertible degrades to a no-op with a
|
|
337
|
+
one-line stderr notice — it never fails the drive.
|
|
338
|
+
|
|
339
|
+
**Signals.** Spans: `convertible.drive` (root) → `convertible.tool.*` (per tool
|
|
340
|
+
call) → `convertible.handoff`. Metrics: `convertible.steps`, `convertible.tokens`,
|
|
341
|
+
`convertible.tool.latency`, `convertible.tool.calls`, `convertible.hook.denials`,
|
|
342
|
+
`convertible.drive.duration`.
|
|
343
|
+
|
|
344
|
+
**Config** (precedence: explicit > `CONVERTIBLE_OTEL_*` > standard `OTEL_*` >
|
|
345
|
+
default): `CONVERTIBLE_OTEL_ENABLED`, `CONVERTIBLE_OTEL_ENDPOINT` /
|
|
346
|
+
`OTEL_EXPORTER_OTLP_ENDPOINT`, `CONVERTIBLE_OTEL_SERVICE_NAME` /
|
|
347
|
+
`OTEL_SERVICE_NAME`. `OTEL_SDK_DISABLED=true` is honored as a kill-switch.
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
uv run convertible telemetry status # resolved config + whether the SDK is installed
|
|
351
|
+
uv run convertible telemetry overview # describe the surface
|
|
352
|
+
```
|
|
353
|
+
|
|
333
354
|
## Per-model instructions & skills
|
|
334
355
|
|
|
335
356
|
Convertible composes a model-specific **system prompt** for every drive from two
|
|
@@ -411,6 +432,8 @@ rely on a non-existent flag.
|
|
|
411
432
|
| `agents overview` | Describe the agents surface. |
|
|
412
433
|
| `skills list` | List resolved skill docs for a model. |
|
|
413
434
|
| `skills overview` | Describe the skills surface. |
|
|
435
|
+
| `telemetry status` | Show the resolved GPS / OpenTelemetry config + whether the SDK is installed. |
|
|
436
|
+
| `telemetry overview` | Describe the telemetry surface. |
|
|
414
437
|
| `session` | Open a foreground interactive palette. |
|
|
415
438
|
| `wheels list` | List discovered engine wheels (the garage). |
|
|
416
439
|
| `whoami` | Report this agent's nick, version, backend, and model. |
|
|
@@ -84,6 +84,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
84
84
|
from convertible.cli._commands import overview as _overview_cmd
|
|
85
85
|
from convertible.cli._commands import session as _session_cmd
|
|
86
86
|
from convertible.cli._commands import skills as _skills_group
|
|
87
|
+
from convertible.cli._commands import telemetry as _telemetry_group
|
|
87
88
|
from convertible.cli._commands import wheels as _wheels_group
|
|
88
89
|
from convertible.cli._commands import whoami as _whoami_cmd
|
|
89
90
|
|
|
@@ -115,6 +116,8 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
115
116
|
# Layered per-model config: AGENTS instructions + skills.
|
|
116
117
|
_agents_group.register(sub)
|
|
117
118
|
_skills_group.register(sub)
|
|
119
|
+
# GPS: OpenTelemetry traces + metrics (opt-in, optional [otel] extra).
|
|
120
|
+
_telemetry_group.register(sub)
|
|
118
121
|
# Interactive foreground palette (c28/R8).
|
|
119
122
|
_session_cmd.register(sub)
|
|
120
123
|
|
|
@@ -33,6 +33,7 @@ from convertible.commands import CommandError, expand_command
|
|
|
33
33
|
from convertible.config import EngineConfig
|
|
34
34
|
from convertible.contract import OK, Task, TaskResult
|
|
35
35
|
from convertible.handoff import HandoffError, handoff
|
|
36
|
+
from convertible.telemetry import load_telemetry
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
def _render(result: TaskResult, engine: str, artifact_path: Path) -> str:
|
|
@@ -103,39 +104,69 @@ def execute_drive(
|
|
|
103
104
|
EXIT_USER_ERROR, str(exc), "list engines with: convertible wheels list"
|
|
104
105
|
) from exc
|
|
105
106
|
|
|
107
|
+
# GPS: the root span wraps engine.drive() + handoff() + the artifact write, so
|
|
108
|
+
# the loop's tool spans nest under it. A no-op unless telemetry is enabled.
|
|
109
|
+
# The same shared path serves `drive` and `session`, so both are instrumented.
|
|
110
|
+
telemetry = load_telemetry()
|
|
106
111
|
try:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
) from exc
|
|
112
|
+
with telemetry.drive_span(
|
|
113
|
+
task_id=task.id,
|
|
114
|
+
engine=engine_name,
|
|
115
|
+
model=config.model,
|
|
116
|
+
max_steps=config.max_steps,
|
|
117
|
+
) as drive_span:
|
|
118
|
+
trace_id = telemetry.trace_id_hex()
|
|
119
|
+
if trace_id:
|
|
120
|
+
emit_diagnostic(f"trace: {trace_id}")
|
|
117
121
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
try:
|
|
123
|
+
result = engine.drive(task, config)
|
|
124
|
+
except Exception as exc: # noqa: BLE001 - any failure still writes an artifact (h5)
|
|
125
|
+
result = failed_result(task.id, f"{type(exc).__name__}: {exc}")
|
|
126
|
+
result.command = command_name
|
|
127
|
+
drive_span.set(status=result.status)
|
|
128
|
+
write(result, artifact_dir(repo))
|
|
129
|
+
raise CliError(
|
|
130
|
+
EXIT_ENV_ERROR,
|
|
131
|
+
f"engine '{engine_name}' failed: {exc}",
|
|
132
|
+
"check the engine config / vLLM server; a result artifact was still written",
|
|
133
|
+
) from exc
|
|
134
|
+
|
|
135
|
+
if result.status == OK:
|
|
136
|
+
with telemetry.handoff_span() as handoff_span:
|
|
137
|
+
try:
|
|
138
|
+
outcome = handoff(
|
|
139
|
+
repo,
|
|
140
|
+
task.id,
|
|
141
|
+
instruction=task.instruction,
|
|
142
|
+
open_pr=open_pr,
|
|
143
|
+
base_branch=base,
|
|
144
|
+
)
|
|
145
|
+
result.branch = outcome.branch
|
|
146
|
+
result.pr_url = outcome.pr_url
|
|
147
|
+
if not result.changed_files:
|
|
148
|
+
result.changed_files = outcome.changed_files
|
|
149
|
+
handoff_span.set(
|
|
150
|
+
branch=outcome.branch,
|
|
151
|
+
committed=outcome.committed,
|
|
152
|
+
pushed=outcome.pushed,
|
|
153
|
+
pr_url=outcome.pr_url,
|
|
154
|
+
)
|
|
155
|
+
if outcome.note:
|
|
156
|
+
emit_diagnostic(f"handoff: {outcome.note}")
|
|
157
|
+
except HandoffError as exc:
|
|
158
|
+
emit_diagnostic(f"handoff skipped: {exc}")
|
|
159
|
+
|
|
160
|
+
drive_span.set(
|
|
161
|
+
status=result.status,
|
|
162
|
+
step_count=len(result.steps),
|
|
163
|
+
pr_url=result.pr_url,
|
|
126
164
|
)
|
|
127
|
-
result.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
emit_diagnostic(f"handoff: {outcome.note}")
|
|
133
|
-
except HandoffError as exc:
|
|
134
|
-
emit_diagnostic(f"handoff skipped: {exc}")
|
|
135
|
-
|
|
136
|
-
result.command = command_name
|
|
137
|
-
artifact_path = write(result, artifact_dir(repo))
|
|
138
|
-
return result, artifact_path
|
|
165
|
+
result.command = command_name
|
|
166
|
+
artifact_path = write(result, artifact_dir(repo))
|
|
167
|
+
return result, artifact_path
|
|
168
|
+
finally:
|
|
169
|
+
telemetry.flush()
|
|
139
170
|
|
|
140
171
|
|
|
141
172
|
def cmd_drive(args: argparse.Namespace) -> int:
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""``convertible telemetry`` — inspect the GPS (OpenTelemetry) configuration.
|
|
2
|
+
|
|
3
|
+
``telemetry status`` reports the resolved :class:`~convertible.telemetry.TelemetryConfig`
|
|
4
|
+
(enabled flag, OTLP endpoint/protocol, service name, traces/metrics toggles) and
|
|
5
|
+
whether the optional ``[otel]`` extra is installed; ``telemetry overview``
|
|
6
|
+
describes the noun (satisfying the agent-first rubric: any noun with
|
|
7
|
+
action-verbs must also expose ``overview``).
|
|
8
|
+
|
|
9
|
+
This module imports only the **stdlib-clean** telemetry facade — never the SDK —
|
|
10
|
+
so ``convertible telemetry`` works (reporting ``sdk_installed: false``) even when
|
|
11
|
+
the extra is not installed.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
|
|
18
|
+
from convertible.cli._commands.overview import emit_overview
|
|
19
|
+
from convertible.cli._output import emit_result
|
|
20
|
+
from convertible.telemetry import TelemetryConfig, sdk_available
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _telemetry_sections() -> list[dict[str, object]]:
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
"title": "What it does",
|
|
27
|
+
"items": [
|
|
28
|
+
"GPS for a drive: OpenTelemetry traces + metrics over OTLP",
|
|
29
|
+
"Off by default; opt in with CONVERTIBLE_OTEL_ENABLED=1",
|
|
30
|
+
"Needs the optional extra: pip install 'convertible-cli[otel]'",
|
|
31
|
+
"Instrumented in the loop + shared drive path, so every engine emits it",
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"title": "Signals",
|
|
36
|
+
"items": [
|
|
37
|
+
"spans: convertible.drive -> convertible.tool.* (+ convertible.handoff)",
|
|
38
|
+
"metrics: convertible.steps, convertible.tokens, convertible.tool.latency,"
|
|
39
|
+
" convertible.tool.calls, convertible.hook.denials, convertible.drive.duration",
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"title": "Configuration (precedence: explicit > CONVERTIBLE_OTEL_* > OTEL_* > default)",
|
|
44
|
+
"items": [
|
|
45
|
+
"CONVERTIBLE_OTEL_ENABLED — turn telemetry on (default: off)",
|
|
46
|
+
"CONVERTIBLE_OTEL_ENDPOINT / OTEL_EXPORTER_OTLP_ENDPOINT — collector URL",
|
|
47
|
+
"CONVERTIBLE_OTEL_SERVICE_NAME / OTEL_SERVICE_NAME — resource service.name",
|
|
48
|
+
"OTEL_SDK_DISABLED=true — standard kill-switch, forces telemetry off",
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"title": "Verbs",
|
|
53
|
+
"items": [
|
|
54
|
+
"telemetry status [--json] — show the resolved telemetry config",
|
|
55
|
+
"telemetry overview — describe the telemetry surface (this command)",
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def cmd_telemetry_overview(args: argparse.Namespace) -> int:
|
|
62
|
+
emit_overview(
|
|
63
|
+
"convertible telemetry",
|
|
64
|
+
_telemetry_sections(),
|
|
65
|
+
json_mode=bool(getattr(args, "json", False)),
|
|
66
|
+
)
|
|
67
|
+
return 0
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def cmd_telemetry_status(args: argparse.Namespace) -> int:
|
|
71
|
+
json_mode = bool(getattr(args, "json", False))
|
|
72
|
+
cfg = TelemetryConfig.resolve()
|
|
73
|
+
installed = sdk_available()
|
|
74
|
+
|
|
75
|
+
if json_mode:
|
|
76
|
+
payload = cfg.to_dict()
|
|
77
|
+
payload["sdk_installed"] = installed
|
|
78
|
+
emit_result(payload, json_mode=True)
|
|
79
|
+
return 0
|
|
80
|
+
|
|
81
|
+
lines = [
|
|
82
|
+
f"enabled: {cfg.enabled}",
|
|
83
|
+
f"sdk_installed: {installed}",
|
|
84
|
+
f"service_name: {cfg.service_name}",
|
|
85
|
+
f"otlp_endpoint: {cfg.otlp_endpoint}",
|
|
86
|
+
f"otlp_protocol: {cfg.otlp_protocol}",
|
|
87
|
+
f"traces_enabled: {cfg.traces_enabled}",
|
|
88
|
+
f"metrics_enabled:{cfg.metrics_enabled}",
|
|
89
|
+
]
|
|
90
|
+
if cfg.enabled and not installed:
|
|
91
|
+
lines.append("note: enabled but the [otel] extra is not installed (no-op)")
|
|
92
|
+
emit_result("\n".join(lines), json_mode=False)
|
|
93
|
+
return 0
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _no_verb(args: argparse.Namespace) -> int:
|
|
97
|
+
return cmd_telemetry_overview(args)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def register(sub: argparse._SubParsersAction) -> None:
|
|
101
|
+
p = sub.add_parser(
|
|
102
|
+
"telemetry",
|
|
103
|
+
help="Inspect the GPS / OpenTelemetry config (see 'convertible telemetry overview').",
|
|
104
|
+
)
|
|
105
|
+
p.add_argument("--json", action="store_true", help="Emit structured JSON.")
|
|
106
|
+
p.set_defaults(func=_no_verb, json=False)
|
|
107
|
+
noun_sub = p.add_subparsers(dest="telemetry_command", parser_class=type(p))
|
|
108
|
+
|
|
109
|
+
st = noun_sub.add_parser("status", help="Show the resolved telemetry configuration.")
|
|
110
|
+
st.add_argument("--json", action="store_true", help="Emit structured JSON.")
|
|
111
|
+
st.set_defaults(func=cmd_telemetry_status)
|
|
112
|
+
|
|
113
|
+
ov = noun_sub.add_parser("overview", help="Describe the telemetry surface.")
|
|
114
|
+
ov.add_argument("--json", action="store_true", help="Emit structured JSON.")
|
|
115
|
+
ov.set_defaults(func=cmd_telemetry_overview)
|
|
@@ -326,6 +326,56 @@ scope); invokable skills are a tracked follow-up.
|
|
|
326
326
|
"""
|
|
327
327
|
|
|
328
328
|
|
|
329
|
+
_TELEMETRY = """\
|
|
330
|
+
# convertible telemetry
|
|
331
|
+
|
|
332
|
+
GPS for a drive: opt-in OpenTelemetry **traces + metrics** over OTLP. Telemetry
|
|
333
|
+
belongs to the chassis — it is instrumented once in the loop and the shared drive
|
|
334
|
+
path, so *every* engine emits identical signals (the all-engines rule), exactly
|
|
335
|
+
like lifecycle hooks.
|
|
336
|
+
|
|
337
|
+
Off by default. The OpenTelemetry SDK is an **optional extra** (the base install
|
|
338
|
+
keeps zero runtime dependencies); enable it with the env var and install the
|
|
339
|
+
extra:
|
|
340
|
+
|
|
341
|
+
pip install 'convertible-cli[otel]'
|
|
342
|
+
export CONVERTIBLE_OTEL_ENABLED=1
|
|
343
|
+
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 # OTLP/HTTP collector
|
|
344
|
+
|
|
345
|
+
When requested without the extra installed, convertible degrades to a no-op with
|
|
346
|
+
a one-line stderr notice — it never fails the drive.
|
|
347
|
+
|
|
348
|
+
## Signals
|
|
349
|
+
|
|
350
|
+
- spans: `convertible.drive` (root) -> `convertible.tool.*` (per tool call) plus
|
|
351
|
+
`convertible.handoff`.
|
|
352
|
+
- metrics: `convertible.steps`, `convertible.tokens` (attr `kind`),
|
|
353
|
+
`convertible.tool.latency`, `convertible.tool.calls`, `convertible.hook.denials`,
|
|
354
|
+
`convertible.drive.duration` (attr `status`).
|
|
355
|
+
|
|
356
|
+
## Configuration
|
|
357
|
+
|
|
358
|
+
Precedence (highest first): explicit > `CONVERTIBLE_OTEL_*` > standard `OTEL_*` >
|
|
359
|
+
default. `OTEL_SDK_DISABLED=true` is honored as a kill-switch.
|
|
360
|
+
|
|
361
|
+
- `CONVERTIBLE_OTEL_ENABLED` — turn telemetry on (default: off).
|
|
362
|
+
- `CONVERTIBLE_OTEL_ENDPOINT` / `OTEL_EXPORTER_OTLP_ENDPOINT` — collector URL.
|
|
363
|
+
- `CONVERTIBLE_OTEL_SERVICE_NAME` / `OTEL_SERVICE_NAME` — resource `service.name`.
|
|
364
|
+
- `CONVERTIBLE_OTEL_METRICS_ENABLED` — toggle metric emission (default: on).
|
|
365
|
+
|
|
366
|
+
## Usage
|
|
367
|
+
|
|
368
|
+
convertible telemetry status
|
|
369
|
+
convertible telemetry status --json
|
|
370
|
+
convertible telemetry overview
|
|
371
|
+
|
|
372
|
+
## See also
|
|
373
|
+
|
|
374
|
+
- `convertible explain drive`
|
|
375
|
+
- `convertible explain hooks`
|
|
376
|
+
"""
|
|
377
|
+
|
|
378
|
+
|
|
329
379
|
ENTRIES: dict[tuple[str, ...], str] = {
|
|
330
380
|
(): _ROOT,
|
|
331
381
|
("convertible",): _ROOT,
|
|
@@ -353,4 +403,7 @@ ENTRIES: dict[tuple[str, ...], str] = {
|
|
|
353
403
|
("skills",): _SKILLS,
|
|
354
404
|
("skills", "list"): _SKILLS,
|
|
355
405
|
("skills", "overview"): _SKILLS,
|
|
406
|
+
("telemetry",): _TELEMETRY,
|
|
407
|
+
("telemetry", "status"): _TELEMETRY,
|
|
408
|
+
("telemetry", "overview"): _TELEMETRY,
|
|
356
409
|
}
|