Files
synapbus/internal/storage/schema/019_harness.sql
T
Algis DumbrisandClaude Opus 4.6 574897df4d feat: harness-agnostic wrappers + OpenTelemetry integration
Introduces internal/harness — a minimal Harness interface inspired by
GoogleCloudPlatform/scion — plus four backends (k8sjob, subprocess,
webhook, stub) and an OTel-traced Registry that spans every dispatch
and injects W3C trace context into child processes via env vars.

Phases landed together on this branch:

  1. internal/harness scaffold: Harness/Capabilities/ExecRequest/
     ExecResult/Budget/Usage types, Registry with Resolve/Execute,
     in-memory stub backend.
  2. internal/harness/k8sjob: wraps existing k8s.JobRunner behind the
     Harness interface with a Waiter abstraction (real clientset +
     test fake). BuildHandler exports the per-agent config logic.
  3. internal/harness/subprocess: os/exec-based backend (Mac+Linux),
     per-run workdir, result.json handoff, bounded log capture,
     Budget-driven wall-clock timeout.
  4. internal/harness/webhook: synchronous HTTP POST with HMAC
     signing via internal/webhooks.ComputeHMACSignature, per-agent
     URL/secret/timeout read from harness_config_json.
  5. internal/observability: OTel tracer init via OTLP HTTP (opt-in
     via SYNAPBUS_OTEL_ENABLED), W3C propagator always installed;
     Registry.Execute starts a harness.execute span per dispatch and
     calls InjectTraceContext into req.Env so children inherit it.
  6. internal/harness/runs: SQLite-backed Observer that persists a
     harness_runs row per dispatch with status, usage, cost, duration,
     trace_id, session_id, and a bounded logs excerpt.

Schema: new migration 019_harness.sql adds agents.harness_name /
local_command / harness_config_json columns and the backend-agnostic
harness_runs table with indices on (agent, created_at), (status),
(trace_id), (run_id). internal/reactor/reactor_test.go inline schema
updated to match.

Deployment: deploy/kubic/otel-collector.yaml stands up an otel-collector
Deployment + ConfigMap + ClusterIP Service in the synapbus namespace on
kubic, receiving OTLP gRPC (4317) and HTTP (4318) and exporting debug
output until a Tempo/Jaeger backend lands.

Docs: docs/harness-otel-research.html compares scion and paperclip
side-by-side and maps the current synapbus executor surface; its
companion docs/harness-otel-design.md carries the phase plan, span
taxonomy, and migration schema verbatim.

The reactor currently still calls k8s.JobRunner directly — rewiring it
through the Registry is a follow-up, intentionally out of scope for
this branch to keep the refactor reversible. The new packages are
independently tested (~78 new tests across 7 packages) and the full
project test suite passes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:23:04 +03:00

48 lines
2.1 KiB
SQL

-- 019: Harness-agnostic wrappers
-- Adds per-agent harness configuration and a backend-agnostic harness_runs
-- table used by all harness implementations (k8sjob, subprocess, webhook).
-- See docs/harness-otel-design.md.
-- Per-agent harness configuration ----------------------------------------
-- Explicit harness name ("k8sjob", "subprocess", "webhook", "stub").
-- When NULL, Registry.Resolve picks a backend by fallback rules.
ALTER TABLE agents ADD COLUMN harness_name TEXT;
-- JSON-encoded argv for the subprocess backend, e.g.
-- ["claude", "--print", "--max-turns", "50"]
ALTER TABLE agents ADD COLUMN local_command TEXT;
-- Per-harness opaque config blob; parsed by the backend that owns it.
ALTER TABLE agents ADD COLUMN harness_config_json TEXT;
-- Backend-agnostic harness_runs table ------------------------------------
CREATE TABLE harness_runs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
run_id TEXT NOT NULL UNIQUE,
agent_name TEXT NOT NULL,
backend TEXT NOT NULL, -- 'k8sjob' | 'subprocess' | 'webhook' | 'stub'
message_id INTEGER, -- triggering message, if any
status TEXT NOT NULL, -- 'pending' | 'running' | 'success' | 'failed' | 'cancelled' | 'timeout'
exit_code INTEGER,
trace_id TEXT,
span_id TEXT,
session_id TEXT, -- backend session id for resume, if any
tokens_in INTEGER NOT NULL DEFAULT 0,
tokens_out INTEGER NOT NULL DEFAULT 0,
tokens_cached INTEGER NOT NULL DEFAULT 0,
cost_usd REAL NOT NULL DEFAULT 0,
duration_ms INTEGER,
result_json TEXT,
logs_excerpt TEXT, -- bounded; full logs live on disk
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
finished_at DATETIME
);
CREATE INDEX idx_harness_runs_agent ON harness_runs(agent_name, created_at DESC);
CREATE INDEX idx_harness_runs_status ON harness_runs(status, created_at DESC);
CREATE INDEX idx_harness_runs_trace ON harness_runs(trace_id);
CREATE INDEX idx_harness_runs_run_id ON harness_runs(run_id);