The plugin’s high‑level architecture is documented in architecture-tree.md. Below is the core diagram extracted for quick reference:
┌─────────────────────────────────────────────────────────────────────────┐
│ OpenCode Core (Host) │
├─────────────────────────────────────────────────────────────────────────┤
│ Hooks Registered by AdaptivePlugin: │
│ • tool.execute.after ←─ captures every tool execution │
│ • chat.message ←─ session context & abandonment detection │
│ • flush ←─ cleanup on shutdown │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ AdaptivePlugin (Entry) │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ State (per plugin instance): │ │
│ │ • db: Database (SQLite WAL) │ │
│ │ • sessionCtx: Map<sessionID, {ctx, ts, lastRecordId, toolStats}>│ │
│ │ • recentOps: RecentOpsCache (5s sliding window) │ │
│ │ • duplicateWarningTimestamps: Map<key, ts> (rate‑limit) │ │
│ │ • timers: TTL cleanup, dup cleanup, recentOps cleanup │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│
┌──────────────┴──────────────┐
▼ ▼
┌─────────────────────┐ ┌─────────────────────────┐
│ chat.message │ │ tool.execute.after │
│ (Session Context) │ │ (Auto‑Observer) │
└─────────────────────┘ └─────────────────────────┘
│ │
│ stores/updates │ computes confidence
▼ ▼
┌─────────────────────┐ ┌─────────────────────────┐
│ sessionCtx Map │ │ observe() │
│ + abandonment │ │ • computeBaseConf() │
│ penalty update │ │ • writeRecord() │
└─────────────────────┘ │ • queueMicrotask() │
│ (validation update) │
└─────────────────────────┘
│
▼
┌─────────────────────┐
│ writeRecord() │
│ (db.ts) │
└─────────────────────┘
│
▼
┌─────────────────────┐
│ SQLite │
│ telemetry_v2 │
└─────────────────────┘
The Adaptive Plugin is a passive observer for OpenCode that automatically harvests implicit signals from tool executions and stores them in a local SQLite database. It provides:
- Automatic Confidence Scoring — Computes base confidence from exit code and duration, then refines via output validation
- Trend Analysis — Detects improving/declining confidence patterns by agent, tool, or model
- Abandonment Detection — Penalizes prior session confidence when user switches contexts
- Deduplication — Sliding window (5s) prevents duplicate recordings of rapid tool calls
- Telemetry Export — JSON/CSV export, status readout, and reset capability
- Internal Metrics — Counters for ops visibility (
adaptive_metricstool) - Tool Enrichment (experimental) — Appends reliability stats to tool descriptions (
tool.definitionhook) - Cross-Session Hints (experimental) — Injects failing tool patterns from past sessions into new session prompts
- Session Compaction Context (experimental) — Preserves tool failure awareness across conversation compaction
No state.json, no event system dependency, no chat.params modification — purely passive data collection.
Scope: This plugin provides telemetry, trend analysis, confidence scoring, and experimental enrichment hooks. Advisory routing & model recommendations are deferred to v2.2.
| Category | Benefit |
|---|---|
| Non-Intrusive | Zero interference with host orchestration; hooks are read-only observers |
| Performance | Sync INSERT <0.5ms; async validation via microtasks; composite index makes trends queries ~2ms @ 5k rows |
| Memory Safety | Bounded caches with TTL eviction (sessionCtx: 2h, recentOps: 5s, dup warnings: 1h); burst-limited cleanup (≤500/tick) |
| Security | Parameterized queries only; validated groupBy; rate-limited warnings; debug-gated logs |
| Observability | Built-in adaptive_metrics tool exposes counters for ops monitoring |
| Testability | 23 unit tests covering core paths + experimental hooks; isolated in-memory SQLite per test |
| Simplicity | Single-file DB layer, clear separation: db.ts (storage), observer.ts (logic), adaptive.ts (hooks) |
| Portability | Pure SQLite WAL; no external services; DB file in working directory |
- Long-running sessions where you want to track agent/tool effectiveness over time
- Multi-agent workflows where abandonment detection helps identify context switching
- Compliance/audit needs (exportable JSON/CSV of all telemetry)
- Performance tuning (trend analysis shows which models/tools degrade)
- Debugging flaky tools (output validation catches truncated/parse failures)
| Issue | Impact | Mitigation | Target Fix |
|---|---|---|---|
| Experimental hooks require host support | tool.definition, experimental.chat.system.transform, and experimental.session.compacting rely on hooks that may not exist in older OpenCode versions |
Gate behind experimentalActive: true config flag; degrade gracefully |
— |
| ToolStatsCache cold start | First tool.definition call for an unseen tool always hits DB |
Cache now populates with zero stats on miss | ✅ Fixed |
| No cross-session dedup | Duplicate detection only within 5s sliding window per session | By design; global dedup would require shared state | — |
| No token counting | tokens_in/tokens_out always 0 (not available in tool.execute.after) |
Would require LLM instrumentation at a different layer | — |
| Single DB file | All telemetry stored in .opencode_telemetry.db in working directory |
User can configure custom dbPath via plugin options |
— |
| No retention policy | DB grows indefinitely; only manual adaptive_reset clears it |
Consider adding auto-rollover (e.g., keep last 100k rows) | Backlog |
| Confidence model is linear | Simple additive weights may not capture complex user preferences | Could be extended with per-user calibration or ML layer | Backlog |
- WAL mode means
-waland-shmfiles accompany the DB; ensure cleanup on reset - No connection pooling — single connection per plugin instance (fine for single-process OpenCode)
- Metrics are in-memory only — reset on plugin reload; not persisted
- No encryption — DB is plain SQLite; if sensitive, rely on filesystem encryption
| Decision | Rationale | Alternative Considered |
|---|---|---|
| Passive observer only | Zero risk of breaking host logic; easy to disable | Active routing (rejected — too invasive) |
| Sync INSERT + async UPDATE | Fast path <0.5ms; validation doesn't block | Fully async INSERT (rejected — adds await overhead) |
| In-memory caches + TTL | Simplicity, no external dependencies | Redis/Memcached (rejected — overkill, external dep) |
| SQLite over JSON file | Query performance, ACID, WAL, indexes | JSON lines (rejected — slow scans, no indexes) |
| Composite index on (agent,tool,confidence,timestamp) | Covers both GROUP BY and ORDER BY for trends | Separate indexes (rejected — N+1 query pattern) |
| No state.json | Avoids file contention, simplifies recovery | State file (rejected — duplicate source of truth) |
✅ Use it if:
- You want automatic, zero-config telemetry for OpenCode sessions
- You care about agent/tool performance trends over time
- You need exportable audit logs for compliance
- You want abandonment detection for multi-session workflows
- You prefer local SQLite storage over external services
❌ Don't use it if:
- You need real-time metrics streaming (use external APM instead)
- You require per-token cost tracking (not implemented)
- You need cross-machine aggregation (SQLite is local-only)
- You have strict data retention policies (no auto-rollover yet)
- You need sub-millisecond INSERT latency (SQLite may not suffice at extreme QPS)
- Retention policy (auto-delete >N days or >M rows)
- Token counting integration (if exposed by host)
- Optional Prometheus exporter endpoint
- Per-agent confidence calibration
- Cross-session global dedup (optional, opt-in)
- Encrypted DB mode (SQLCipher)
- Flush improvement: drain microtasks before DB close
Bottom line: A lightweight, production-ready telemetry plugin that adds valuable observability with negligible overhead. Experimental hooks extend the plugin from passive observer to active enrichment while maintaining the same zero-risk design.