Volkom Mesh — Telemetry API Reference (for implementers)
Self-contained technical document for a developer or an AI assistant (Claude Code, Cursor) instrumenting an agent. Two channels, one API, one database per tenant. Contracts verified against the code.
Mental model#
- Tenant = the customer, an isolated ClickHouse database (
volkom_<slug>). - Agent =
agent_name(a stable slug, e.g.sales-monitor). - Run = every event sharing the same
run_id(UUID). Sequence:started→ N×in_progress→completed|failed. - Execution channel → table
telemetry_events. Business channel → tablebusiness_metrics.
Authentication#
Every telemetry endpoint uses the agent's Bearer token:
Authorization: Bearer <token>
The token is generated in the portal (Connect your agents → workspace key,
which works for the whole fleet; or a per-agent key from the agent's page).
A workspace token can report any agent_name; a per-agent key only its own
(anti-spoofing: 403 Key bound to agent '<x>').
Rate limit: 100 req/60s per token. If your fleet exceeds it, use
POST /telemetry/batch(N events per request) before asking for more quota.
Base URL: https://api.volkom.ai/api/v1. Interactive Swagger: https://api.volkom.ai/docs.
1) Register the agent — POST /api/v1/agents/register#
Idempotent (upsert). Declares description + business KPIs → populates the
catalog (label/unit/agg) the frontend uses as its dictionary. Status:
200. Requires Authorization: Bearer.
Body (AgentRegister):
{
"agent_name": "sales-monitor",
"agent_version": "1.0.0",
"description": "Monitors daily sales.",
"telemetry_doc": "Free text describing what it reports.",
"business_kpis": [
{"key": "orders", "label": "Orders", "unit": "count", "agg": "sum"},
{"key": "revenue", "label": "Revenue", "unit": "money:USD", "agg": "sum"}
]
}
business_kpis is [] for agents with no business channel. Fields of each
KPI (BusinessKPIDecl): key (required), label (default ""), unit
(default "count"), agg (default "sum").
2) Execution event — POST /api/v1/telemetry#
One POST per event (started, each step, close). Status: 202.
Body (TelemetryEventIn):
{
"agent_name": "sales-monitor",
"agent_version": "1.0.0",
"client_id": "shopflow",
"run_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"event": "in_progress",
"timestamp": "2026-05-30T14:32:05Z",
"message": "Read orders",
"level": "info",
"category": "Ingest",
"source": "store/read.py:88",
"seq": 2,
"metrics": {"orders": 42.0},
"links": ["https://oms.internal/batch/abc"],
"metadata": {"region": "US"},
"duration_ms": null,
"records_processed": null,
"error_type": null,
"error_message": null,
"output": null,
"workflow_run_id": null
}
Fields:
| Field | Type | Notes |
|-------|------|-------|
| agent_name | str req | |
| agent_version | str req | |
| client_id | str req | = tenant slug |
| run_id | UUID req | the same for every event of the run |
| event | enum req | started | in_progress | completed | failed |
| timestamp | ISO-8601 req | UTC recommended |
| message | str | human description (key in in_progress) |
| level | str | debug|info|warning|error|success (default info) |
| category | str | grouping/phase; the frontend groups steps by it |
| source | str | file:line |
| seq | int | order within the run (default 0) |
| metrics | maplinks | listmetadata | mapduration_ms | int? | usually on close |
| records_processed | int? | usually on close |
| error_type / error_message | str? | on failed |
| output | obj? | rich nested payload |
| workflow_run_id | str? | if the run is part of a Temporal workflow |
Pattern of a run (3+ POSTs with the same run_id):
POST /telemetry {event:"started", run_id:R, timestamp:t0}
POST /telemetry {event:"in_progress", run_id:R, seq:1, message:"…", category:"…", metrics:{…}}
POST /telemetry {event:"in_progress", run_id:R, seq:2, message:"…", level:"warning"}
POST /telemetry {event:"completed", run_id:R, duration_ms:16000, records_processed:42}
3) Business KPIs — POST /api/v1/telemetry/business#
One POST per run (that run's KPIs). Status: 202. Separate channel;
creates/extends columns on demand in business_metrics.
Body (BusinessTelemetryIn):
{
"agent_name": "sales-monitor",
"agent_version": "1.0.0",
"client_id": "shopflow",
"run_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"timestamp": "2026-05-30T14:32:20Z",
"metrics": {"orders": 42, "revenue": 9500},
"workflow_run_id": null
}
metrics: map of key (must match a KPI declared at register) → value
(number|string). run_id can be the same as the execution run to correlate
them. agent_version/client_id are optional but recommended.
Via the carcass (volkom-core) — equivalent, no HTTP#
If the agent is Python, subclassing BaseAgent does the 3 POSTs for you and
handles run_id/started/close/timings automatically.
from volkom_core import BaseAgent, AgentResult, KPI, VolkomConfig, TelemetryExporter
class SalesMonitor(BaseAgent):
agent_name = "sales-monitor"
agent_version = "1.0.0"
description = "Monitors daily sales."
telemetry_doc = "Orders and revenue per run."
business_kpis = [
KPI("orders", "Orders", "count", "sum"),
KPI("revenue", "Revenue", "money:USD", "sum"),
]
def run(self) -> AgentResult:
self.event("Connected to the store", category="Ingest", source="store/conn.py:20")
self.event("Read orders", category="Ingest", metrics={"orders": 42})
self.warn("3 orders without address", category="Validation", metrics={"incomplete": 3})
self.event("Loaded into the dashboard", category="Close",
links=["https://oms.internal/batch/abc"])
self.kpi("orders", 42)
self.kpi("revenue", 9500)
return AgentResult(records_processed=42)
Carcass API:
- event(message, *, level, category, attributes, metrics, links, source, kind)
— emits an in_progress (or start/finish via kind). attributes → metadata.
- step / info / warn / error (message, ...) — per-level wrappers.
- kpi(key, value) — business KPI (must be in business_kpis).
- emit_metric(key, value) — free metadata on the closing event (NOT a KPI).
- started/completed/failed, run_id, duration_ms → automatic in execute().
Registration (once, when the client starts):
exporter = TelemetryExporter(cfg) # cfg = VolkomConfig(client_id, control_plane_url, auth_token)
exporter.register_agent(
agent_name=SalesMonitor.agent_name,
agent_version=SalesMonitor.agent_version,
description=SalesMonitor.description,
telemetry_doc=SalesMonitor.telemetry_doc,
business_kpis=[{"key": k.key, "label": k.label, "unit": k.unit, "agg": k.agg}
for k in SalesMonitor.business_kpis],
)
# run once (generates run_id + started + close):
SalesMonitor(cfg, exporter).execute(run_id=<uuid|None>, workflow_run_id=<str|None>)
KPI vocabulary#
unit: count · money:<ISO> (e.g. money:USD, money:ARS, money:EUR) ·
percent · duration (value in seconds).
agg (how it totals over the dashboard period):
sum (cumulative: sales, invoices) · last (states: stock, MRR, % occupancy) ·
avg (averages: wait time) · max (peaks).
Picking the wrong
aggproduces wrong totals:sumof a state inflates the number;lastof something cumulative understates it.
Reading (what the frontends consume — reference)#
Same API, same DB; the portal and the admin panel read from here:
- GET /api/v1/tenants/{slug}/dashboard/runs?include_steps=<bool>&since=&until=&limit=
— runs; with include_steps=true it includes the in_progress events.
- GET /api/v1/tenants/{slug}/runs/{run_id}/events — full narrative of one run.
- GET /api/v1/tenants/{slug}/business/catalog — declared KPIs (label/unit/agg).
- GET /api/v1/tenants/{slug}/business/summary?time_range= — KPIs aggregated by agg.
- GET /api/v1/tenants/{slug}/business/series?agent_name=&key=&time_range=&bucket= — trend.
Implementation checklist#
- [ ]
POST /agents/registerwith declaredbusiness_kpis(idempotent). - [ ] Per run: one unique UUID
run_id, reused across all its events. - [ ]
startedat the beginning;in_progressfor each step (withcategory,metrics,levelas appropriate);completed/failedat the end withduration_ms. - [ ] Business KPIs via
POST /telemetry/business(keys = the declared ones). - [ ] Header
Authorization: Bearer <token>on everything. - [ ] ALWAYS close the run (no close → perpetual "in progress").
- [ ] Errors with
level:"error"/event:"failed"+error_type/error_message.
Errors and gotchas#
- Different
run_idper event → every step shows as a separate run. Reuse it. - Run without a close → shows as "in progress" forever.
- Undeclared KPI → the value gets in but without catalog metadata (unit/agg).
- Rate limit 429 → respect 100 req/60s per token; for bursts,
POST /telemetry/batch. - Anti-spoofing 403 → a per-agent key can only report its own
agent_name.