Data model
Every entity, field and enumerated value the daemon stores. Generated from the same specification the server, the OpenAPI document and all six SDKs are built from.
Generated file
Everything below this page's introduction is produced by tools/codegen from spec/mcpd.spec.json. Do not edit it by hand. Change the spec, run make gen, and the server, the SDKs and this page all move together. make check fails the build when any of them drift apart.
A hand-written reference describes the code as it was on the day somebody typed it out. This one is emitted from the specification the server's Rust types are compiled from, so a field can only appear here if it exists in the build you are running.
How to read it
There are 39 enumerations and 118 object shapes. The enumerations come first: closed sets of values, such as the 33 kinds a span can be, or the four values a span's status can take. Everything after them is an object.
- An object with a Stored in line is an entity: a record mcpd keeps, with the table it lives in, its primary key and its indexes.
- An object without one is a shape that travels in a request or a response, and is never stored under its own name.
- A field's type links to the type it names. A field marked required must be present; everything else may be omitted, and an omitted field is absent rather than empty.
Why one model, not six
Spans, logs, metrics, scores, feedback and aspect notes are stored separately, but every one of them carries the trace identifier of the run it belongs to, and most of them the span identifier of the step as well. That is the reason they are one model. Without those identifiers you have six separate stores: a log line nothing ties to the run that emitted it, a cost figure that leads back to no prompt, a low score with nothing to read underneath it, a sentence about a run you cannot open. Correlation is what makes the rest of this page worth having.
-
Spans
What ran, nested, with timings
-
Logs
Lines the run emitted
-
Metrics
Numbers, with exemplars back to a run
-
Scores
Machine evaluation
-
Feedback
What a person thought
-
Aspect notes
A sentence a model wrote about the run
Index
The document is long. This is a jump table into it.
mcpd stores five signals: spans, logs, metrics, scores and feedback. Every one of them is correlated by trace and span id. This page is generated from spec/mcpd.spec.json, the same file the server and all six SDKs are built from. If it says a field exists, it exists.
Enumerations
SpanKind
What an operation is. The kind drives how a span is rendered in the waterfall, which attributes are meaningful, and which metrics are auto-extracted from it.
| Value | Meaning |
|---|---|
agent_run |
One agent invocation. Usually the root of a trace. |
model_generation |
One logical generation: everything from the first request to the final answer, including every intermediate step. |
model_step |
One step inside a generation. A generation that calls tools twice has three steps. |
model_inference |
The provider round-trip alone, excluding processors and tool execution. Measure pure model latency here. |
model_chunk |
A single streamed chunk. Recorded as an event, not a duration. |
tool_call |
A tool your code executes. |
client_tool_call |
A tool the calling client executes. The server records the marker; the client returns the detail. |
provider_tool_call |
A tool the model provider executes server-side, such as hosted web search or code execution. |
mcp_tool_call |
A tool reached over the Model Context Protocol. |
workflow_run |
One workflow execution. Usually the root of a trace. |
workflow_step |
One step within a workflow. |
workflow_conditional |
A branch point, with the conditions it evaluated. |
workflow_conditional_eval |
One condition evaluated inside a branch point. |
workflow_parallel |
A fan-out of concurrent branches. |
workflow_loop |
A loop construct: foreach, do-while or do-until. |
workflow_sleep |
A deliberate pause. |
workflow_wait_event |
A wait for an external event, with its timeout. |
memory_operation |
A read from or write to agent memory. |
rag_ingestion |
One ingestion pipeline run: load, chunk, extract, embed, upsert. |
rag_embedding |
One embedding call, possibly batched. |
rag_vector_operation |
Vector store I/O: query, upsert, delete or fetch. |
rag_action |
A RAG-specific transform: chunk, extract metadata or rerank. |
graph_action |
A graph build, traverse, update or prune. |
processor_run |
An input or output processor. |
scorer_run |
One scorer evaluating a target. |
scorer_step |
One stage of a scorer’s pipeline. |
guardrail |
A safety or policy check, and whether it tripped. |
retrieval |
A generic retrieval that is not vector-store specific. |
mapping |
An inline data transform between pipeline stages. |
skill_resolution |
A dynamic resolver deciding which skills an agent may use. |
workspace_action |
A filesystem, sandbox, search or computer-use action. |
http_request |
An outbound HTTP call made on the agent’s behalf. |
generic |
Anything else. Always valid; never inferred. |
SpanStatus
The outcome of an operation.
| Value | Meaning |
|---|---|
unset |
Still running, or the producer never said. |
ok |
Completed successfully. |
error |
Failed. error carries the detail. |
cancelled |
Abandoned deliberately, by a caller or a timeout. |
SignalType
The six signals mcpd stores. Every one of them is correlated by trace id, most of them by span id as well, and every one of them passes through the same redaction pipeline.
| Value | Meaning |
|---|---|
span |
A unit of work with a start and, usually, an end. |
log |
A structured log record. |
metric |
A numeric observation. |
score |
A machine evaluation of a span or trace. |
feedback |
A human evaluation of a span or trace. |
aspect_note |
One sentence recorded against a trace along one aspect. Stored, redacted and retained like the other five, and erased with the subject it belongs to. |
LogLevel
Severity, ordered. Filtering is always >=.
| Value | Meaning |
|---|---|
trace |
Finer than debug. Off by default. |
debug |
Detail useful while diagnosing. |
info |
Normal operation. |
warn |
Recoverable, but worth reading. |
error |
The operation failed. |
fatal |
The process cannot continue. |
MetricKind
How a numeric observation aggregates.
| Value | Meaning |
|---|---|
counter |
Monotonically increasing. Aggregates by sum. |
gauge |
A point-in-time value. Aggregates by last, min, max or mean. |
histogram |
A distribution. Aggregates by bucket, and answers quantiles. |
SamplingKind
How mcpd decides which traces to keep.
| Value | Meaning |
|---|---|
always |
Keep everything. The default, and correct until volume says otherwise. |
never |
Keep nothing. Useful to disable a single project without redeploying. |
ratio |
Keep a fixed fraction, decided once at the root so a trace is never half-recorded. |
deterministic |
Keep a fixed fraction, decided by hashing the trace id. Every collector in a fleet makes the same decision without coordinating. |
tail |
Buffer the trace, then decide once it finishes. Keeps every error and every slow trace regardless of rate. |
RedactionMode
What happens to a value a redaction rule matched.
| Value | Meaning |
|---|---|
drop |
Remove the key entirely, as if it was never sent. |
mask |
Replace the whole value with a fixed token. |
partial |
Keep a documented prefix and suffix, mask the middle. Enough to recognise a value without disclosing it. |
hash |
Replace with a keyed SHA-256 digest. Equal inputs stay equal, so you can still group and join, but the original cannot be recovered. |
ScoreSource
Where a score came from. Kept separate from the scorer’s identity so the same scorer can run in more than one context.
| Value | Meaning |
|---|---|
live |
Produced online, as the traced run happened. |
offline |
Produced later, by scoring stored traces. |
experiment |
Produced by an experiment run against a dataset. |
manual |
Entered by a person, through the API or the UI. |
FeedbackKind
The shape of a human judgement.
| Value | Meaning |
|---|---|
thumbs |
Binary. value is up or down. |
rating |
Ordinal. value is a number inside the scale the producer declares. |
correction |
The right answer. value is text. |
tag |
A label chosen from a set. value is the label. |
comment |
Free prose, with no score attached. |
EntityKind
The named thing a span belongs to. Bounded and low-cardinality, so it is safe as a metric label.
| Value | Meaning |
|---|---|
agent |
An agent. |
workflow |
A workflow. |
tool |
A tool. |
model |
A model. |
scorer |
A scorer. |
processor |
A processor. |
pipeline |
A RAG or data pipeline. |
service |
A plain service with no richer identity. |
ExperimentStatus
Where an experiment run has got to.
| Value | Meaning |
|---|---|
pending |
Created, not started. |
running |
In progress. |
succeeded |
Every item completed. |
failed |
Aborted. error says why. |
cancelled |
Stopped by a person. |
AuditResult
Whether authorisation let the request through.
| Value | Meaning |
|---|---|
allowed |
The key carried the scope the endpoint needs, so mcpd went on to handle the request. |
refused |
Authorisation refused the request before it reached storage. Recording the attempt is the point of the row. |
RunStatus
Where a run is. Only running can move; the other four are terminal, and a terminal run holds no deadline, no wait and no live attempt.
| Value | Meaning |
|---|---|
running |
Making progress, or waiting for something that will move it: a lease, a timer or an event. |
succeeded |
Reached the end of the graph. output holds what the definition’s output template rendered, when it declares one. |
failed |
Ended on an error nothing handled: a step that failed under on_error: fail_run, a spent ceiling, or a cancelled child. error says which. |
timed_out |
Ended because the run’s own deadline passed. |
cancelled |
Ended because a caller asked, through the cancel endpoint or by cancelling an ancestor run. |
AttemptStatus
Where one attempt at one step is. Four states are live and four are terminal, and the daemon never shows a live attempt on an ended run.
| Value | Meaning |
|---|---|
pending |
On disk and offerable to a worker, once its retry timer, if any, has fired. |
leased |
Held by a worker that has not yet posted started. |
started |
Held by a worker that has posted started, so a body is running. |
orphaned |
The lease expired with no heartbeat. Not offerable while the reclaim window is open: a heartbeat inside the window restores the attempt, and a window that closes with no contact requeues it or fails the run, depending on effect and on whether the body had started. |
succeeded |
Finished with an output. |
failed |
Finished with an error, or marked failed because the run ended while the attempt was live. |
timed_out |
The step’s own deadline passed before the attempt ended. |
skipped |
Never ran, because the step’s when guard was false. |
StepKind
What a step attempt is an attempt at. task and agent are claimable. A wait row is written by the event that resumes it or the deadline that ends it, so a wait’s output and status read like any other step’s.
| Value | Meaning |
|---|---|
task |
Claimable work. The worker runs whatever code it registered under this step id. |
agent |
Claimable work whose body is the worker’s own agent loop. Identical on the wire to task; the kind is recorded so the trace and the interface can tell an agent turn from a plain step. |
wait |
A suspension the daemon resumes on an event or ends on a deadline. Never offered to a worker. |
ConstructKind
What a run’s cursor names: the construct at its current position in the definition’s steps. Every kind except end is a node kind a definition can declare.
| Value | Meaning |
|---|---|
task |
Claimable work. |
agent |
Claimable work whose body is the worker’s agent loop. |
sleep |
A relative delay, in milliseconds. No process is alive during it. |
sleep_until |
An absolute delay, to an RFC 3339 instant rendered from a template. |
wait_event |
A suspension addressed by an event name and a key rendered per run. |
parallel |
A fixed fan-out over named branches, each branch one claimable step. Done when every branch has ended. |
foreach |
A fan-out over the items of an array, each item one claimable step, with at most concurrency of them offerable at once. |
loop |
A bounded loop whose body is one claimable step per iteration, ended by until or by max_iterations. |
subworkflow |
A child run of another definition. Its output becomes this step’s output, and a cancel walks down into it. |
end |
The end of the graph. Never declared in a definition: the cursor reaches it when the last step has ended. |
Effect
What running a step body twice would cost, which is what the daemon branches on when a lease is lost. Default at_least_once.
| Value | Meaning |
|---|---|
at_least_once |
Running the body twice is acceptable. A lost lease requeues the attempt. The default. |
at_most_once |
Running the body twice is not acceptable. started must be posted before the body runs, and a started attempt whose lease is lost fails the run rather than running again. |
TimerKind
Every deadline the daemon keeps. Each one is a row the tick fires, and each firing is a transaction that moves the run.
| Value | Meaning |
|---|---|
sleep |
A sleep or sleep_until coming due. |
lease_expiry |
A lease that has not been heartbeated. Firing moves the attempt to orphaned and opens a reclaim window one lease length long. |
reclaim |
The close of the reclaim window an expired lease opened. Firing requeues the attempt or fails the run, depending on effect and on whether the body had started. |
run_timeout |
The whole run’s ceiling, from limits.run_timeout_ms. |
step_timeout |
One attempt’s ceiling, from the node’s step_timeout_ms. It bounds a worker that heartbeats for ever. |
wait_timeout |
A wait_event whose event has not arrived by timeout_ms. |
retry |
The backoff before a requeued attempt becomes claimable. Firing writes the next attempt as pending. |
RunEventKind
What a row in a run’s event log records. Every row is written in the transaction that made the change it describes, so a reader that has seen sequence number n has seen every change below it.
| Value | Meaning |
|---|---|
run_started |
The run was created. Payload: definition_id, definition_version. |
run_cursor |
The cursor moved. Payload: index, construct, and step_key when the construct has one. |
step_leased |
A worker claimed an attempt. Payload: step_key, attempt, worker_id. |
step_started |
A worker posted started. Payload: step_key, attempt. |
step_finished |
An attempt succeeded. Payload: step_key, attempt. |
step_failed |
An attempt failed. Payload: step_key, attempt, class, message, attempts_left. |
step_skipped |
A when guard was false. Payload: step_key. |
run_waiting |
The run reached a wait_event. Payload: step_key, event, key, expires_at. |
run_resumed |
An event resumed a waiting run. Payload: step_key, event_id. |
run_ended |
The run reached a terminal status. Payload: status, and error when there is one. |
DefinitionKind
What kind of thing a definition describes.
| Value | Meaning |
|---|---|
workflow |
A graph of steps the daemon walks. The only kind in version 1: agent definitions arrive with the in-process agent loop, which is not served yet. |
EventOutcome
What the daemon did with a published event.
| Value | Meaning |
|---|---|
delivered |
A waiting run took it and moved, in the same transaction. |
parked |
No run was waiting. The event is held for 24 hours for the next run that reaches a matching wait with accept_parked. |
duplicate |
This event id has been seen before, so nothing was written. The publish endpoint says when a derived id is treated this way. |
ConflictReason
Why a worker call about a lease answered 409. Each reason tells the worker something different to do next.
| Value | Meaning |
|---|---|
fenced |
A later attempt exists, or another lease terminated this attempt. Abandon the work and post nothing more for this lease. |
already_applied |
This lease’s own earlier complete or fail landed and its reply was lost. The step is done; attempt_status says how. Post nothing more. |
not_started |
complete or fail arrived for a leased attempt under at_most_once. A bug in the worker: post started first, and log it loudly. |
run_terminal |
The run has ended. Stop, and stop claiming for this run. |
StepErrorClass
How a step failed, which decides whether the daemon retries it.
| Value | Meaning |
|---|---|
retryable |
Worth another attempt. The daemon retries while attempts remain, at the node’s backoff, and honours retry_after_ms when it is larger. |
fatal |
Another attempt would fail the same way. No retry; the node’s on_error applies. |
invalid_input |
The step’s input cannot be acted on. No retry; the node’s on_error applies. |
unservable |
This worker does not serve the definition version, or does not recognise the step key. The attempt returns to pending after one lease length without spending the retry budget, so another worker can take it. |
cancelled |
The worker gave the attempt up on request. No retry; the node’s on_error applies. |
OnError
What happens to a run when a node fails after its retries. Default fail_run.
| Value | Meaning |
|---|---|
fail_run |
End the run failed, with the step’s error message as the run’s error. |
continue |
Mark the step failed and move on, so a later when guard can read steps.<id>.status and steps.<id>.error. |
Backoff
How the delay between retries grows.
| Value | Meaning |
|---|---|
fixed |
Every retry waits initial_ms. |
exponential |
Each retry waits twice as long as the last, starting at initial_ms and capped at max_ms. |
WaitTimeout
What a wait_event node does when its deadline passes with no event. Default fail.
| Value | Meaning |
|---|---|
fail |
The node fails. Its on_error decides what the run does next. |
continue |
The wait ends and the run moves on to the next node. |
ValueKind
The JSON type of a value. Used where mcpd reports the shape of something it holds without returning the thing itself.
| Value | Meaning |
|---|---|
null |
The value was JSON null. |
boolean |
The value was true or false. |
number |
The value was a JSON number. |
string |
The value was a JSON string. |
array |
The value was a JSON array. |
object |
The value was a JSON object. |
MonitorWatch
What a monitor measures over its window.
| Value | Meaning |
|---|---|
error_rate |
The fraction of runs that failed, 0 to 1. |
latency_p95 |
Run duration at the ninety-fifth percentile, in milliseconds. |
cost |
Total spend across the window. |
trace_count |
How many runs happened. Catches a pipeline that has gone silent. |
score_mean |
The mean of the scorer named by scorer_id. |
step_count_divergence |
How far a run strays from the usual shape of its own definition. This watch sets no threshold in steps, because a step count is never high in absolute terms, only high for that definition. It reads the recent completed runs of one definition version, takes the median step count as the usual shape, and reports a run whose modified z-score against the median absolute deviation exceeds the monitor’s bound, 3.5 by default. The median absolute deviation is used rather than a mean and a standard deviation because it tolerates up to half its sample being outliers, which is the case here: a runaway cohort must not be allowed to redefine normal. Fewer than twelve completed runs and the watch says nothing rather than guessing. A run that takes several times its usual number of steps trips this long before it reaches max_steps, and while its error rate is still zero. |
run_repetition |
How much a run repeats itself. For each run in the window, the highest number of attempts it made at any one node of its definition, counting a retry and a loop iteration of that node alike; the watch reads the mean of that number. One means no node ran twice. A loop that re-enters the same node, and a retry storm, both raise this before either reaches error_rate. |
MonitorCompare
Which way a monitor’s threshold is breached.
| Value | Meaning |
|---|---|
above |
Trip when the value rises above the threshold. |
below |
Trip when the value falls below it. This is the direction that catches a quality regression, and a silent pipeline. |
MonitorCondition
Where a monitor or an objective stands. A tripped condition latches: it stays tripped until the number recovers, and too little data never clears it, because silence is not recovery.
| Value | Meaning |
|---|---|
nominal |
Evaluated, and within its threshold. |
tripped |
Tripped, and not yet acknowledged. |
acknowledged |
Tripped, acknowledged, and still breaching. Returns to nominal on its own when the number recovers. |
insufficient |
Not enough data in the window to judge. Also the state before the first evaluation. |
TextSearch
What the q parameter matches on this deployment’s storage backend. Reported by GET /v1/api/capabilities so a search box can say what it searches instead of leaving a reader to infer it from a result set.
| Value | Meaning |
|---|---|
none |
q is not applied at all. No backend that ships declares this; a backend that did would be telling an interface to hide its search box rather than let somebody type into one that returns everything. |
substring |
q matches anywhere inside a word, so heck finds checkout. |
words |
q matches whole words only, which is what a person typing two words into a search box expects. There is no stemming: refund does not find refunds. |
Partitioning
How the storage backend cuts each signal table on its anchor time. It decides whether expiring a day of data is a metadata operation on one part or a row-by-row delete.
| Value | Meaning |
|---|---|
none |
One table, no partitions. Retention removes rows. |
day |
One partition per day. |
month |
One partition per month. |
Facet
A dimension a filter box can offer values for. The set is closed on purpose: each one is a column or a parsed field the daemon keeps a distinct-value list for as signals arrive, so answering is a read of a small table rather than a scan of every span in the retention window.
| Value | Meaning |
|---|---|
entity_kind |
The kind of thing a step was: an agent run, a tool call, a retrieval, and so on. |
entity_name |
The name of that thing, such as the tool that was called. |
service_name |
The service that reported the signal. |
environment |
The environment it came from, such as production. |
tag |
A tag carried on a run. |
metric_name |
A metric name, for the name box of a chart. |
metric_label_key |
A label key a metric point carries. Project-wide: these are every key seen in the project, not the keys of one named metric. |
metric_label_value |
A value one label key takes. Needs key, because the answer would otherwise be every value of every label mixed together. |
DeltaSignal
A signal that can be polled for what has arrived. Only the three that carry a server-set arrival time: a score and a piece of feedback are stamped with the caller’s own timestamp, so a watermark over them would either skip a late row or repeat every row for ever.
| Value | Meaning |
|---|---|
traces |
The run projection, which is what a run list refreshes from. |
spans |
Steps, for a waterfall that is still filling in. |
logs |
Log lines. |
AspectReadiness
How far the daemon has got with one aspect for one entity. It is computed from the rows that are stored and never recorded, so it cannot go stale, and it is per entity and per aspect rather than global: one agent can have goal themes to read while its behaviour notes are still collecting.
| Value | Meaning |
|---|---|
collecting |
Fewer notes than a clustering pass needs. The floor is 50 notes for one aspect on one entity, and below it the daemon writes no snapshot at all rather than sorting a handful of sentences into themes nobody should draw a conclusion from. |
processing |
Enough notes to cluster, and no snapshot covers this aspect yet. The next pass will write one. Nothing is wrong and nothing needs doing. |
ready |
A snapshot covers this aspect, so themes exist and every theme route answers. |
ThemeState
What happened to a theme between the previous snapshot and this one. The daemon matches themes across consecutive snapshots by their wording, so a theme keeps its id while its wording holds, and this records what that match found. It is computed from the two clusterings alone and never from a trace attribute.
| Value | Meaning |
|---|---|
birth |
The first snapshot this theme appears in. |
continue |
Present in the previous snapshot and in this one, matched to the same id. The ordinary state of a theme that is holding together. |
split |
The previous snapshot held one theme where this one holds several. The history point’s split_from names the theme it came out of. |
merge |
Several themes in the previous snapshot are one theme here. The history point’s merged_from names them. |
resurrection |
Present in an earlier snapshot, absent from the one immediately before this, and back now under its original id. A theme that comes and goes reads as one theme with gaps rather than as several short-lived ones. |
death |
The theme was in the previous snapshot and is in none of this one’s clusters. Reachable only through a theme’s history: a dead theme has no row in the snapshot it died in, so it is never a node in a flow. |
ThemeNodeKind
What one node of a theme flow stands for. A stage holds its themes, at most one noise node and at most one overflow node, and the three together account for every trace in the stage, which is what lets the shares add up to one.
| Value | Meaning |
|---|---|
theme |
One theme. It carries a theme_id, so it can be filtered on, opened and followed through its history. |
noise |
The notes in this stage that joined no theme. Noise is not a failure bucket: it holds rare requests, wording nothing else matched and patterns that have not recurred yet, and a large noise share means varied traffic or too few notes rather than something going wrong. Its notes are read from the examples route with noise as the theme id. |
other |
The themes past theme_limit_per_stage, folded into one node so the stage totals still add up. It carries no theme_id and cannot be selected, filtered on or opened, because it is a presentation fold and not a group of anything. |
SnapshotLandmarkReason
Why one snapshot is in a landmark selection. A timeline draws a couple of dozen points over a range that may hold thousands of passes, so the daemon chooses them and says why, rather than handing back the newest few and leaving the reader to assume that is the whole story.
| Value | Meaning |
|---|---|
range_start |
The oldest snapshot in the requested range. Always included, so a timeline starts where the range does. |
range_end |
The newest snapshot in the requested range. Always included, so a timeline ends at the current state. |
time_sample |
Chosen to spread the remaining points evenly over the range. Sampling is by time and not by ordinal, so an hour of frequent passes does not crowd out a quiet week. |
KeyScope
What an account API key may do. The same six scopes the daemon’s configured keys carry, under the same names as the auth value of each endpoint.
| Value | Meaning |
|---|---|
ingest |
Write signals, and read nothing. The key that lives in every process that emits a span. |
read |
Read stored signals: runs, spans, logs, metrics, scores, analytics and datasets. |
write |
Change stored data: scores, feedback, datasets, experiments and acknowledgements. |
admin |
Delete data and read the audit log. Also accepted where execute or worker is needed. |
execute |
Start, cancel and signal runs, and publish events to them. |
worker |
Claim steps and report their outcome. The key that lives in the process running step bodies. |
Entities
InputTokenDetails
Input tokens broken down by how the provider billed them. Every field is optional; absent means the provider did not report it, which is different from zero.
| Field | Type | Description |
|---|---|---|
text |
u64 |
Ordinary text tokens: not cached, not audio, not image. |
cache_read |
u64 |
Tokens served from a prompt cache. Usually billed at a large discount. |
cache_write |
u64 |
Tokens written into a prompt cache. Usually billed at a premium. |
cache_write_5m |
u64 |
Tokens written to a five-minute ephemeral cache, where the provider prices TTLs separately. |
cache_write_1h |
u64 |
Tokens written to a one-hour ephemeral cache. |
audio |
u64 |
Audio input tokens. |
image |
u64 |
Image input tokens, including rendered PDF pages. |
OutputTokenDetails
Output tokens broken down by kind.
| Field | Type | Description |
|---|---|---|
text |
u64 |
Ordinary completion tokens. |
reasoning |
u64 |
Hidden reasoning tokens. Billed, and usually invisible in the response. |
audio |
u64 |
Audio output tokens. |
image |
u64 |
Image output tokens. |
TokenUsage
Token counts for one model call. Totals are authoritative; the detail breakdowns explain them and are what cost is actually computed from.
| Field | Type | Description |
|---|---|---|
input_tokens |
u64 |
Total input tokens. |
output_tokens |
u64 |
Total output tokens. |
total_tokens |
u64 |
Input plus output, when the provider reports a total that differs from the sum. |
input_details |
InputTokenDetails |
How the input tokens were billed. |
output_details |
OutputTokenDetails |
How the output tokens were billed. |
Cost
What a span cost, in a single currency. mcpd computes this from a price book at ingest and stores the result, so a later price change never silently rewrites history.
| Field | Type | Description |
|---|---|---|
currency |
string required |
ISO 4217 code. USD unless the price book says otherwise. |
input_cost |
f64 |
Cost attributable to input tokens. |
output_cost |
f64 |
Cost attributable to output tokens. |
total_cost |
f64 |
The sum, including any meter that is neither input nor output. |
price_book |
string |
Identifier of the price book used, so a figure can be traced back to the rates that produced it. |
is_estimated |
bool required |
True when mcpd derived the cost from a price book, false when the producer reported it directly. |
ModelParameters
The sampling settings a caller chose for one model call. Recorded because a run nobody can reproduce is a run nobody can debug, and the seed and the sampling settings are what a replay has to match. Every field is what the caller asked for, never what the provider did with it. None of them carries prompt or completion text, so a producer running with payload capture off still records all of them.
| Field | Type | Description |
|---|---|---|
temperature |
f64 |
The sampling temperature the caller asked for. Absent when the caller left the provider’s default in place, which is not the same as sending zero. |
top_p |
f64 |
Nucleus sampling: the caller asked the sampler to consider the smallest set of tokens whose probabilities reach this fraction. Absent when the caller set none. |
top_k |
u32 |
The caller asked the sampler to consider only this many of the most likely tokens. Not every provider offers it. |
max_tokens |
u64 |
The ceiling the caller put on output length. An answer that stops in the middle of a sentence usually stopped here, and this is the first field to read when one does. |
seed |
i64 |
The sampling seed the caller passed. Nothing in mcpd reads it. It is recorded because without it a run cannot be reproduced at all, and a comparison between two runs that differ only in their seed is worth nothing. |
stop_sequences |
string[] |
The stop sequences the caller supplied. They are short strings the caller wrote, so they are settings rather than payload, and an answer that ends early often ends on one of them. Do not put prompt text here. |
has_response_format |
bool required |
True when the caller constrained the output to a fixed shape, such as a JSON schema or a grammar. The shape itself is not recorded: it can be large and it is caller-owned content, and what a reader needs is whether the constraint was on. |
SpanError
Why a span failed.
| Field | Type | Description |
|---|---|---|
message |
string required |
A human-readable message. Required: an error with no message is not actionable. |
kind |
string |
The error class or code, such as RateLimitError or ECONNRESET. |
stack |
string |
A stack trace, when the producer captured one. |
domain |
string |
Which subsystem raised it. |
retryable |
bool |
Whether the producer considered the failure worth retrying. |
details |
json |
Structured detail. Passes through redaction like any other payload. |
Correlation
The descriptive context a signal belongs to. Every field here is queryable and most are safe as metric labels; the high-cardinality ones (user_id, session_id, request_id) are queryable but blocked from metric labels by the cardinality filter.
| Field | Type | Description |
|---|---|---|
project |
string required |
Which project this belongs to. The primary tenancy key. |
environment |
string |
Deployment environment, such as production or staging. |
service_name |
string |
The emitting service. |
service_version |
string |
The emitting service’s version, so a regression can be pinned to a release. |
session_id |
string |
A user-facing session. Groups many traces into one conversation. |
thread_id |
string |
A conversation thread within a session. |
user_id |
string |
The end user. Hashed with a keyed digest at ingest, so runs can still be grouped by customer without storing who the customer is. Filtering on it hashes your query value the same way, so lookup keeps working. Rotating the key deliberately breaks the link. |
resource_id |
string |
A tenant or workspace inside the calling application. |
run_id |
string |
The producer’s own identifier for this run. |
request_id |
string |
The inbound request that caused this work. |
experiment_id |
string |
Set when the run was produced by an experiment. |
release |
string |
A release or git sha, for regression attribution. |
SpanLink
A reference from this span to another, in this trace or a different one. Links express causality that parentage cannot: a batch job caused by twenty requests has twenty links and one parent.
| Field | Type | Description |
|---|---|---|
trace_id |
string required |
The linked trace. |
span_id |
string |
The linked span, when the link points at one rather than a whole trace. |
kind |
string |
Why the link exists, such as follows_from, retry_of or replay_of. |
attributes |
json |
Anything else about the relationship. |
Page
Cursor pagination. Cursors are opaque and stable; never construct one by hand.
| Field | Type | Description |
|---|---|---|
next_cursor |
string |
Pass as cursor to fetch the next page. Absent on the last page. |
has_more |
bool required |
Whether another page exists. |
total |
u64 |
Total matching rows, when the backend could count them cheaply. |
IngestBatch
One batch of signals sent to the native ingest endpoint. Any combination of the six signals may appear in a single batch; mcpd sorts them out. Supplying ingest_key makes the whole batch idempotent, so a client that retries after a timeout never double-counts.
| Field | Type | Description |
|---|---|---|
spans |
Span[] |
Spans to record. |
logs |
LogRecord[] |
Log records to record. |
metrics |
MetricPoint[] |
Metric observations to record. |
scores |
Score[] |
Machine evaluations to attach. |
feedback |
Feedback[] |
Human evaluations to attach. |
aspect_notes |
AspectNote[] |
Aspect notes to record. A note is a sentence about a trace, so it arrives with that trace’s other signals, under the same idempotency key and through the same redactor. |
ingest_key |
string |
Idempotency key for this batch. Replaying a batch with a key mcpd has already accepted is a no-op that still returns 200. |
IngestError
Why one signal in a batch was refused.
| Field | Type | Description |
|---|---|---|
signal |
SignalType required |
Which signal kind the failure was in. |
index |
u32 required |
Position within that signal’s array, so a client can find the offending record. |
message |
string required |
What was wrong, in terms the sender can act on. |
IngestResult
What mcpd did with a batch. Counts are per signal, and rejected is never silently zero: a signal mcpd could not store is always reported with a reason.
| Field | Type | Description |
|---|---|---|
accepted |
u32 required |
How many signals were stored. |
rejected |
u32 required |
How many were refused. |
duplicate |
u32 required |
How many were recognised as a replay of an already-accepted batch and skipped. |
errors |
IngestError[] |
One entry per rejected signal, naming what was wrong and where. |
TraceListResponse
A page of traces.
| Field | Type | Description |
|---|---|---|
traces |
Trace[] required |
The matching traces, in the requested order. |
page |
Page required |
Cursor and totals. |
TraceDetail
One trace and everything attached to it.
| Field | Type | Description |
|---|---|---|
trace |
Trace required |
The trace projection. |
spans |
Span[] required |
Every span, ordered by start time then by depth, so a waterfall renders without sorting. |
logs |
LogRecord[] |
Logs, when include asked for them. |
scores |
Score[] |
Scores, when include asked for them. |
feedback |
Feedback[] |
Feedback, when include asked for them. |
SpanListResponse
A page of spans.
| Field | Type | Description |
|---|---|---|
spans |
Span[] required |
The matching spans. |
page |
Page required |
Cursor and totals. |
LogListResponse
A page of log records.
| Field | Type | Description |
|---|---|---|
logs |
LogRecord[] required |
The matching records. |
page |
Page required |
Cursor and totals. |
MetricSeriesPoint
One bucket of an aggregated series.
| Field | Type | Description |
|---|---|---|
timestamp |
timestamp required |
Start of the bucket. |
value |
f64 required |
The aggregated value. |
count |
u64 |
How many raw observations fell in this bucket. Distinguishes a genuine zero from no data. |
exemplar_trace_id |
string |
A trace that contributed to this bucket, so a spike on the chart opens the run that caused it. |
MetricSeries
One line on a chart: a single label combination over time.
| Field | Type | Description |
|---|---|---|
labels |
json |
The label values that identify this series. |
points |
MetricSeriesPoint[] required |
Bucketed values, oldest first. |
MetricSeriesResponse
An aggregated metric query result.
| Field | Type | Description |
|---|---|---|
name |
string required |
The metric queried. |
unit |
string |
UCUM unit, for axis labelling. |
bucket |
string required |
The bucket width actually used, which may differ from the request when mcpd chose it. |
series |
MetricSeries[] required |
One entry per label combination. |
ScoreListResponse
A page of scores.
| Field | Type | Description |
|---|---|---|
scores |
Score[] required |
The matching scores. |
page |
Page required |
Cursor and totals. |
FeedbackListResponse
A page of feedback.
| Field | Type | Description |
|---|---|---|
feedback |
Feedback[] required |
The matching feedback. |
page |
Page required |
Cursor and totals. |
LatencyStats
Latency distribution over a window, in milliseconds.
| Field | Type | Description |
|---|---|---|
p50 |
f64 |
Median. |
p90 |
f64 |
Ninetieth percentile. |
p95 |
f64 |
Ninety-fifth percentile. |
p99 |
f64 |
Ninety-ninth percentile. |
mean |
f64 |
Arithmetic mean. Reported alongside quantiles, never instead of them. |
max |
f64 |
Slowest observation in the window. |
GroupedStat
One row of a grouped breakdown.
| Field | Type | Description |
|---|---|---|
key |
string required |
The group’s value, such as a model id. |
count |
u64 required |
How many runs fell in this group. |
error_count |
u64 required |
How many of them failed. |
total_cost |
f64 |
Spend attributable to this group. |
input_tokens |
u64 |
Input tokens for this group. |
output_tokens |
u64 |
Output tokens for this group. |
latency |
LatencyStats |
Latency distribution within this group. |
AnalyticsSummary
Headline numbers for a window. One request backs the overview screen.
| Field | Type | Description |
|---|---|---|
from |
timestamp required |
Start of the window. |
to |
timestamp required |
End of the window. |
trace_count |
u64 required |
Runs started in the window. |
span_count |
u64 required |
Steps recorded in the window. |
error_count |
u64 required |
Runs that failed. |
error_rate |
f64 required |
Failures as a fraction of runs, between 0 and 1. |
latency |
LatencyStats required |
Run duration distribution. |
input_tokens |
u64 required |
Input tokens consumed. |
output_tokens |
u64 required |
Output tokens produced. |
total_cost |
f64 required |
Total spend in the window. |
currency |
string required |
Currency of total_cost. |
groups |
GroupedStat[] |
Per-group rows, when group_by was supplied. |
CostBucket
Spend in one time bucket for one group.
| Field | Type | Description |
|---|---|---|
timestamp |
timestamp required |
Start of the bucket. |
key |
string required |
The group this row belongs to. |
total_cost |
f64 required |
Spend in this bucket. |
input_tokens |
u64 |
Input tokens in this bucket. |
output_tokens |
u64 |
Output tokens in this bucket. |
CostBreakdown
Where the money went.
| Field | Type | Description |
|---|---|---|
from |
timestamp required |
Start of the window. |
to |
timestamp required |
End of the window. |
currency |
string required |
Currency of every figure in this response. |
total_cost |
f64 required |
Total spend across the window. |
groups |
GroupedStat[] required |
Totals per group, largest first. |
series |
CostBucket[] |
A time series, when bucket was supplied. |
DeleteResult
What a deletion removed.
| Field | Type | Description |
|---|---|---|
deleted |
u32 required |
How many records were removed, across every signal. |
HealthStatus
Whether the server is alive and able to serve.
| Field | Type | Description |
|---|---|---|
status |
string required |
ok or degraded. |
version |
string required |
The mcpd version serving this request. |
spec_version |
string required |
The data-model spec version this build implements. A client can refuse to talk to an incompatible server. |
uptime_seconds |
u64 required |
How long this process has been running. |
storage |
string |
Which storage backend is configured. |
detail |
string |
What is degraded, when something is. |
signup |
bool |
True when this daemon offers open sign-up: anyone may create an account at POST /v1/api/account/signup and log in at POST /v1/api/account/login. Absent when it does not, which is the default. The interface reads this to decide whether to show its sign-up and log-in screens. |
ScorerInfo
One scorer mcpd can run, as described to a caller.
| Field | Type | Description |
|---|---|---|
id |
string required |
Stable identifier, recorded on every score it produces. |
description |
string required |
What it measures, in one line. |
needs_config |
bool required |
Whether it needs settings to mean anything. A scorer that needs an expected answer cannot run without one. |
ScorerConfig
Settings shared by the scorers that need them. Every field is optional; a scorer that needs one and does not get it declines instead of guessing.
| Field | Type | Description |
|---|---|---|
needle |
string |
Text the answer must contain, for contains. |
expected |
string |
The expected answer, for exact_match. |
max_ms |
f64 |
A budget in milliseconds, for latency_budget. |
max_cost |
f64 |
A budget in currency units, for cost_budget. |
max_tool_calls |
u32 |
The most tool calls a healthy run should make, for tool_efficiency. |
span_kind |
SpanKind |
The kind of span that must be present in the run, for span_present. |
span_name |
string |
The span name that must be present in the run, for span_present. Matched exactly against Span.name. |
tool_name |
string |
The tool that must have run and ended ok, for tool_succeeded. Matched exactly against the name of a span whose kind is tool_call, client_tool_call, provider_tool_call or mcp_tool_call. |
case_sensitive |
bool required |
Case sensitivity for text comparisons. |
ScoreRunRequest
A request to run scorers over runs that are already stored.
Scoring reads stored runs rather than running inline, so an evaluation can never add latency to the request it is judging, and a scorer that fails can never fail a user’s request.
| Field | Type | Description |
|---|---|---|
project |
string |
Which project’s runs to score. Defaults to the server’s default project. |
trace_ids |
string[] required |
Score exactly these runs. Takes precedence over the window. |
from |
timestamp |
Otherwise, score runs started after this time. |
limit |
u32 |
Most runs to score in one request, newest first, when trace_ids is empty. Unset, it is 100 for deterministic scorers, and for a pass with a judged scorer it is as many runs as fit in the request deadline. A judged pass that names more runs than fit, through limit or trace_ids, is refused with the cap. |
scorers |
string[] required |
Which scorers to run, by id. Empty means every scorer that needs no configuration. |
config |
ScorerConfig |
Settings for the scorers that need them. |
source |
ScoreSource |
What to record as the origin of these scores. |
dry_run |
bool required |
Report what would happen and store nothing. |
SkippedScorer
A scorer that declined to answer on one run.
| Field | Type | Description |
|---|---|---|
scorer_id |
string required |
Which scorer declined. |
trace_id |
string required |
Which run it declined on. |
reason |
string required |
Why. Never silent: a scorer that cannot answer is more interesting than one that returned a number. |
ScoreRunResult
What one scoring pass did.
| Field | Type | Description |
|---|---|---|
traces_scored |
u32 required |
Runs looked at. |
scores_written |
u32 required |
Scores produced and stored. |
skipped |
SkippedScorer[] required |
Every scorer that declined, with its reason. |
scores |
Score[] required |
The scores themselves, so the caller sees them without a second read. |
ScorerDelta
How one scorer moved between two experiments.
| Field | Type | Description |
|---|---|---|
scorer_id |
string required |
Which scorer. |
baseline_mean |
f64 |
Mean on the baseline, when it ran there. |
baseline_n |
u64 required |
Number of baseline observations. |
candidate_mean |
f64 |
Mean on the candidate. |
candidate_n |
u64 required |
Number of candidate observations. |
delta |
f64 |
Candidate minus baseline. |
decidable |
bool required |
Whether the sample is large enough for the delta to mean anything. A deliberately blunt rule rather than a significance test: below twenty observations on either side, mcpd says the comparison is not decidable rather than dressing noise up as a finding. |
ExperimentComparison
Two experiments, compared.
The point of an experiment is the comparison, so this reports the delta per scorer beside the sample sizes. A mean that moved on four runs is noise, and printing the counts next to it is what stops it being read as a result.
| Field | Type | Description |
|---|---|---|
baseline |
string required |
The baseline’s identifier. |
candidate |
string required |
The candidate’s identifier. |
scorers |
ScorerDelta[] required |
One row per scorer present on either side. |
regressed |
bool required |
Whether any decidable scorer moved down. |
CaptureRequest
A request to turn stored runs into dataset cases.
| Field | Type | Description |
|---|---|---|
trace_ids |
string[] required |
The runs to capture. Each becomes one item, keeping its trace id so anyone can go back and read what actually happened. |
use_output_as_expected |
bool required |
Record each run’s own output as the expected answer. Leave this off for a run that went wrong: capturing a bad answer as the reference would enshrine the bug. |
CaptureResult
What a capture added to a dataset.
| Field | Type | Description |
|---|---|---|
captured |
u32 required |
Items added. |
requested |
u32 required |
Runs asked for. A gap means a run was not found. |
ItemsAdded
How many cases a dataset gained.
| Field | Type | Description |
|---|---|---|
added |
u32 required |
Items added. |
ErasureResult
What an erasure request removed, counted per signal. The subject is not echoed back: it has been erased, and repeating it in a response that may be logged on the way out would write it down again.
| Field | Type | Description |
|---|---|---|
project |
string required |
The project the erasure ran in. No other project was read or touched. |
traces |
u32 required |
How many runs were removed. |
spans |
u32 required |
How many spans were removed, with the prompts and completions they carried. |
logs |
u32 required |
How many log lines were removed. |
metrics |
u32 required |
How many metric points were removed. |
scores |
u32 required |
How many scores were removed. |
feedback |
u32 required |
How many pieces of feedback were removed. |
deleted |
u32 required |
The total across every signal, so a caller that wants one number does not add them up. |
runs_erased |
u32 |
How many durable runs were removed from the run store, each with its step attempts, event log, deadlines, waits, the events it consumed and any span still queued for the trace store. A run is matched on the user_id in its correlation, as it was started with it, and removed with the whole tree of subworkflow runs it belongs to. Not part of deleted, which counts the trace store. Absent when this daemon does not serve runs. |
runs_in_flight |
u32 |
How many runs attributed to this person were left untouched because a run in their tree has not ended. Removing a live run would corrupt the workflow it is part of, so these are counted rather than skipped in silence, and note names the root runs. Cancel them, or let them end, then send the erasure again. Absent when this daemon does not serve runs. |
aspect_notes_erased |
u32 |
How many aspect notes were removed, matched by the run they describe and by the person stamped on them. Not part of deleted. Absent only from a store that holds no notes. |
dataset_items_erased |
u32 |
How many dataset cases captured from an erased run were emptied in place. They are not deleted, because a case was kept on purpose, so they are not part of deleted. |
datasets |
string[] required |
The datasets those emptied cases sit in, so the reply can say where they are. |
deletes_pending |
bool |
True when the storage backend applies the deletes after this reply is sent, as ClickHouse does, so the counts say what matched rather than what has already left the disk. |
unmatchable_identifiers |
u32 |
How many stored identifiers this request could not have matched, because they were written in the other form, hashed or plain, from the one given. A number above zero means the counts may not be the whole answer. |
note |
string |
Why the numbers above may not be the whole answer, in sentences an operator can repeat. Absent when they are the whole answer. |
DefinitionDocument
A definition as you post it: the graph, and the id and version to store it under. The body is validated at write time against spec/definition.schema.json; a body the walker cannot run is refused with a 400 naming the field, and unknown fields are refused, so a guard that is spelled wrong is an error and not a run that skips its guard.
| Field | Type | Description |
|---|---|---|
id |
string required |
The definition’s identifier within its project. Matches ^[a-z][a-z0-9_]*$. |
kind |
DefinitionKind |
What kind of definition. Absent means workflow, the only kind version 1 serves. |
version |
u32 |
The version to store the body under. Absent asks the daemon to choose: the version whose stored body is identical, or one past the highest stored. A named version that is free is taken; one held by an identical body is confirmed; one held by a different body is a 409, because a stored version never changes. |
body |
json required |
The document itself: input_schema, limits, steps and output. The definition schema names every field. |
DefinitionVersion
Where a posted definition landed.
| Field | Type | Description |
|---|---|---|
id |
string required |
The definition’s identifier. |
version |
u32 required |
The version the body is stored under. |
assigned |
bool required |
True when the daemon chose the number because the body was new. False when the body already existed at this version, or you named the version yourself. |
RunCursor
The frontier of a run: which position in the definition’s steps it has reached, and what sits there.
| Field | Type | Description |
|---|---|---|
index |
u32 required |
The index into steps, or their count at the end of the graph. |
construct |
ConstructKind required |
What is at that position. |
step_key |
string |
The step this position belongs to, when the construct has one. A fan-out names its parent step; the branch and item keys are on the attempts. |
RunTimer
A deadline the daemon holds for a run.
| Field | Type | Description |
|---|---|---|
run_id |
string required |
Which run. |
step_key |
string |
The step the deadline belongs to. Absent for a run-level deadline. |
kind |
TimerKind required |
Which deadline. |
due_at |
timestamp required |
When it fires. |
RunWait
A suspension waiting for an event.
| Field | Type | Description |
|---|---|---|
run_id |
string required |
Which run. |
step_key |
string required |
The wait_event step that is waiting. |
event |
string required |
The event name an inbound event must match. |
key |
string required |
The rendered key that picks this run out of every run waiting on that event. |
expires_at |
timestamp required |
When waiting stops and the node’s on_timeout applies. |
RunDetail
One run and everything the daemon holds for it.
| Field | Type | Description |
|---|---|---|
run |
Run required |
The run. |
attempts |
StepAttempt[] required |
Every attempt, in the order they were written. |
timers |
RunTimer[] required |
The deadlines currently held. Empty once the run has ended. |
waits |
RunWait[] required |
The suspensions currently open. Empty once the run has ended. |
RunListResponse
A page of runs.
| Field | Type | Description |
|---|---|---|
runs |
Run[] required |
The matching runs, newest first. |
page |
Page required |
Cursor and totals. |
RunEventPage
A page of a run’s event log.
| Field | Type | Description |
|---|---|---|
events |
RunEvent[] required |
The rows after the requested sequence number, in order. |
has_more |
bool required |
Whether rows beyond the last one here existed when the page was read. A run that is still moving will write more. |
CauseLink
One edge of a cause chain: an ancestor step, and the template path through which the step below it read that ancestor’s output. An ancestor read through two paths appears twice, once per path.
| Field | Type | Description |
|---|---|---|
step_key |
string required |
The ancestor step, keyed the way the journal keys it: refund/issue, classify[3], poll[7]/classify. |
status |
AttemptStatus required |
Where the ancestor’s latest attempt ended. |
attempts |
u32 required |
How many attempts were made at the ancestor. More than one on a step that ended succeeded is worth reading: it got there, but not the first time. |
template_path |
string required |
The path as the definition writes it inside ${...}, such as steps.classify.output.kind. The definition declares this edge, so the chain is read rather than inferred from timing. |
consumed_by |
string required |
The step key that read this ancestor through template_path. On a direct edge it is the step the answer is about; deeper in the chain it is another ancestor. |
depth |
u32 required |
How far this ancestor sits from the step the answer is about. 1 is read directly, 2 is read by something the step read directly, and so on. |
silent_producer |
bool required |
True when this ancestor ended succeeded and every step between it and the failed step ended succeeded too. Nothing on that path is marked failed, so no status code points at it and the value it produced is the suspect. It is not the same as status being succeeded: an ancestor sitting behind a step that did fail is not a silent producer, because that failure already explains the run. |
value_kind |
ValueKind |
The JSON type template_path resolved to on the ancestor’s stored output. The value itself is never returned, only its shape. Absent when there is no output to read, which is the case for a step that never ran and for one that failed. |
value_empty |
bool |
True when that value was null, an empty string, an empty array or an empty object. A step that ended succeeded and handed the step below it nothing is the failure a status code cannot catch, and this is the field that shows it. Absent for the same reason value_kind is. |
StepCause
Why one step of a run failed, answered by walking backwards along the data dependencies its definition declares. The daemon reads the run journal and the pinned definition version, both of which it has to hold to render ${...} templates at all, so this answer is available with span payload capture off and with no model, prompt or completion involved.
| Field | Type | Description |
|---|---|---|
run_id |
string required |
The run this answer is about. |
step_key |
string required |
The step this answer is about, as it was asked for. |
status |
AttemptStatus required |
Where that step’s latest attempt ended. |
attempts |
u32 required |
How many attempts were made at it. |
error |
StepError |
Why the latest attempt failed, when it did. Absent on a step that did not fail, because the endpoint answers for any step, not only a failed one. |
chain |
CauseLink[] required |
The ancestors whose output this step consumed, directly or transitively. Ordered by depth ascending, then by the order the paths appear in the definition, so two calls against one run return the same list in the same order. Empty when the step reads nothing but the run input. |
truncated |
bool required |
True when the walk stopped at max_depth with ancestors left unvisited, so chain is a prefix of the answer rather than the whole of it. |
StartRunRequest
A request to start a run.
| Field | Type | Description |
|---|---|---|
project |
string |
Which project. A selector: refused with 403 on a mismatch with the key’s grant, filled in for a single-project key, and a 400 when a multi-project key names none. |
definition_id |
string required |
Which definition to run. |
version |
u32 |
Which version. Absent pins the highest stored version at this moment, and the run records it. |
input |
json |
The run’s input. Validated against the definition’s input_schema when it declares one; a failure is a 400. |
correlation |
Correlation |
Context to store on the run and copy onto every span the daemon writes for it. project and run_id inside it are ignored: the run’s own values win. |
idempotency_key |
string |
Makes the start idempotent per project for 24 hours: a repost returns the same run and starts nothing, and two simultaneous posts of one key produce one run. |
ClaimRequest
A worker asking for work.
| Field | Type | Description |
|---|---|---|
project |
string |
Which project. A selector: refused with 403 on a mismatch with the key’s grant, filled in for a single-project key, and a 400 when a multi-project key names none. |
worker_id |
string required |
Who is asking. Recorded on the attempt as lease_owner and in the run’s event log. Nothing else about the worker is stored. |
max |
u32 required |
The most leases to hand back, 1 to 100. |
wait_ms |
u32 required |
How long to hold the request open waiting for work, 0 to 60000 milliseconds. The daemon holds a claim for at most four fifths of its request timeout, 24000 at the default 30 seconds, so a longer wait is answered early rather than cut off with a 503. An empty answer means nothing became runnable in time: reissue the claim. |
run_id |
string |
Restrict the claim to one run, which is how a script drives its own run inline. |
StepLease
A step handed to a worker. Every later call about the attempt is addressed by lease_id.
| Field | Type | Description |
|---|---|---|
lease_id |
string required |
The address of every later call about this attempt. |
run_id |
string required |
Which run. |
project |
string required |
Which project the run belongs to. |
definition_id |
string required |
Which definition the run is executing. |
definition_version |
u32 required |
Which version. Fail the lease with class unservable if you do not serve it. |
step_key |
string required |
The step to run. A worker that does not recognise it fails the lease with class unservable rather than guessing. |
kind |
StepKind required |
Whether this is a plain task or an agent turn. |
attempt |
u32 required |
Which attempt this is. The fence: a call about this lease is refused once a later attempt exists. |
effect |
Effect required |
Under at_most_once, post started and wait for its 200 before running the body. |
expires_at |
timestamp required |
When the lease lapses. Heartbeat every heartbeat_ms to keep it. |
deadline_ms |
u64 |
The step’s own timeout, when the node declares one. It bounds the attempt whatever the heartbeats say. |
idempotency_key |
string required |
A key stable across every attempt of this step: the SHA-256 of run_id:step_key. Pass it to any downstream call that can deduplicate. |
input |
json |
The node’s input template rendered for this attempt, or the run’s input when the node declares none. |
prior |
json required |
The ended attempts of earlier steps, keyed by step id: { status, output?, error? } for the latest attempt of each, or { status: skipped }. Only ended attempts appear. |
state |
json |
The run’s state object as it stands. |
correlation |
Correlation required |
The run’s correlation, with run_id set. Put it on every span you record inside this step. |
trace_id |
string required |
The run’s trace. Set it on every span you record inside this step. |
span_id |
string required |
The span the daemon has already opened for this attempt. Set it as parent_span_id on your own spans, so a model call or a tool call nests under the step. |
parent_span_id |
string required |
The run’s root span, which this attempt’s span hangs off. |
ClaimResponse
What a claim answered.
| Field | Type | Description |
|---|---|---|
leases |
StepLease[] required |
Zero to max leases. Empty when nothing became runnable within wait_ms. |
LeaseState
What started and heartbeat answer.
| Field | Type | Description |
|---|---|---|
expires_at |
timestamp required |
The lease’s new expiry. |
reclaimed |
bool required |
True when this call found the attempt orphaned and restored it to the state it came from. |
StepUsage
Token usage a worker reports on complete. The daemon prices it from its price book and charges the attempt, the run and the root of the run tree.
| Field | Type | Description |
|---|---|---|
input_tokens |
u64 |
Total input tokens. |
output_tokens |
u64 |
Total output tokens. |
total_tokens |
u64 |
Input plus output, when the provider reports a total that differs from the sum. |
input_details |
InputTokenDetails |
How the input tokens were billed. |
output_details |
OutputTokenDetails |
How the output tokens were billed. |
model |
string |
The model that produced the usage, which the price book needs to price it. |
provider |
string |
The provider that served the model. |
StepResult
What a worker posts to complete a step.
| Field | Type | Description |
|---|---|---|
output |
json |
The step’s output. Later steps read it as steps.<id>.output. |
state |
json |
An object to merge shallowly into the run’s state. |
usage |
StepUsage |
Token usage to price and charge. |
StepError
Why a step failed, as the worker reports it.
| Field | Type | Description |
|---|---|---|
class |
StepErrorClass required |
What kind of failure. Decides whether the daemon retries. |
message |
string required |
What went wrong, for the person reading the run. |
details |
json |
Structured detail. Stored on the attempt. |
retry_after_ms |
u64 |
The least the daemon should wait before the next attempt, when the worker knows: a rate limit’s reset, say. Honoured when it is larger than the backoff. At most 315360000000 (3650 days), the bound on every duration in a definition; a larger value is a 400 naming this field. |
StepFailure
What a worker posts to fail a step.
| Field | Type | Description |
|---|---|---|
error |
StepError required |
Why it failed. |
StepAck
What complete and fail answer.
| Field | Type | Description |
|---|---|---|
run_status |
RunStatus required |
Where the run is after this call. |
next |
string required |
claim when more work is runnable now, idle otherwise. |
attempts_left |
u32 |
After a fail: how many attempts the node’s retry budget still allows. |
StepConflict
The body of a 409 from a worker call. reason says what is true and what the worker should do next.
| Field | Type | Description |
|---|---|---|
error |
string required |
Always conflict. |
reason |
ConflictReason required |
Which conflict. |
message |
string required |
The reason, in words. |
attempt |
u32 required |
The attempt the lease addressed. |
attempt_status |
AttemptStatus |
Where that attempt is, when it still exists. Under already_applied this says how the step ended. |
run_status |
RunStatus required |
Where the run is. |
PublishEventRequest
An event for whichever run is waiting for it.
| Field | Type | Description |
|---|---|---|
project |
string |
Which project. A selector: refused with 403 on a mismatch with the key’s grant, filled in for a single-project key, and a 400 when a multi-project key names none. |
event |
string required |
The event name, matched against open waits. |
key |
string required |
Matched against the rendered key of each waiting run. Delivered to the matching run whose wait expires soonest. |
payload |
json |
Becomes the waiting step’s output. |
event_id |
string |
Your own id for this event. A supplied id seen before answers duplicate and writes nothing, whether or not a run is waiting. Absent, the daemon derives one from project, event, key and payload, and a derived id counts as a duplicate only when no run is waiting, because two identical approvals for two runs on one key are two events. Send your own id from any handler that can be retried. |
EventReceipt
What publishing an event did.
| Field | Type | Description |
|---|---|---|
outcome |
EventOutcome required |
Delivered, parked or duplicate. |
event_id |
string required |
The id used, supplied or derived. |
SignalRequest
An event for one run, addressed by run id rather than by key. The daemon reads the run’s open wait on event, takes its key, and routes the event as a published one.
| Field | Type | Description |
|---|---|---|
event |
string required |
The event name. The run must hold an open wait on it. |
payload |
json |
Becomes the waiting step’s output. |
event_id |
string |
Your own id for this event, deduplicated as on a published event. |
WorkerRegistration
A worker announcing the definitions it serves.
| Field | Type | Description |
|---|---|---|
project |
string |
Which project. A selector: refused with 403 on a mismatch with the key’s grant, filled in for a single-project key, and a 400 when a multi-project key names none. |
worker_id |
string required |
Who is registering. Not stored: dispatch reads the runnable set, never a worker table. |
definitions |
DefinitionDocument[] required |
Each is stored under the versioning rule, exactly as if posted to the definitions endpoint. |
WorkerRegistered
What registering answered.
| Field | Type | Description |
|---|---|---|
worker_id |
string required |
The id you registered with. |
registered |
DefinitionVersion[] required |
Where each definition landed, in the order posted. |
lease_ttl_ms |
u64 required |
How long a lease lasts without a heartbeat. |
heartbeat_ms |
u64 required |
How often to heartbeat a held lease: a third of lease_ttl_ms. |
Monitor
A saved condition the daemon evaluates on a schedule. Monitors are configured in the daemon’s configuration file, not through the API.
| Field | Type | Description |
|---|---|---|
id |
string required |
Stable identifier. |
name |
string required |
What a person should read when it trips. |
project |
string required |
Which project it watches. |
watch |
MonitorWatch required |
What it measures. |
scorer_id |
string |
Which scorer, when watch is score_mean. |
compare |
MonitorCompare required |
Which direction trips it. |
threshold |
f64 required |
The threshold. |
window_seconds |
u32 required |
How far back each evaluation looks. |
min_samples |
u64 required |
The fewest runs the window must contain before the monitor will trip. Without it a single failed run in a quiet hour is a 100% error rate, and the first thing anyone does with an alert that cries wolf is mute it. |
webhook |
string |
Where to POST when it trips or clears. Set as a full URL in the config file; the API returns only its host, because a webhook URL is often its own credential. |
enabled |
bool required |
Whether it is evaluated at all. |
MonitorReading
One evaluation of a monitor.
| Field | Type | Description |
|---|---|---|
monitor_id |
string required |
Which monitor. |
at |
timestamp required |
When it was evaluated. |
value |
f64 |
The measured value, when there was enough data to measure one. |
samples |
u64 required |
How many runs the window held. |
condition |
MonitorCondition required |
The resulting condition. |
detail |
string required |
What a person should read. |
MonitorState
A monitor and the condition it is latched in.
| Field | Type | Description |
|---|---|---|
monitor |
Monitor required |
The monitor. |
condition |
MonitorCondition required |
Its current condition. |
last |
MonitorReading |
The most recent reading. Absent before the first evaluation. |
tripped_at |
timestamp |
When it first tripped, while it stays tripped. |
Slo
A service level objective: the fraction of runs that must succeed over a window. A monitor asks whether a number is over a line right now. An objective asks whether the promise is going to be missed and how fast the error budget is burning, over two windows at once, so a two minute blip and a slow leak get the different answers they need. Objectives are configured in the daemon’s configuration file, not through the API.
| Field | Type | Description |
|---|---|---|
id |
string required |
Stable identifier. |
name |
string required |
What a person should read. |
project |
string required |
Which project it covers. |
objective |
f64 required |
The fraction of runs that must succeed, between 0 and 1. 0.995 is ninety-nine and a half percent of runs. |
window_days |
u32 required |
How long the promise is measured over. |
min_samples |
u64 required |
The fewest runs a burn window must hold before it may trip. |
webhook |
string |
Where to POST on a transition. Set as a full URL in the config file; the API returns only its host, because a webhook URL is often its own credential. |
enabled |
bool required |
Whether it is evaluated at all. |
SloReading
One evaluation of an objective.
| Field | Type | Description |
|---|---|---|
slo_id |
string required |
Which objective. |
at |
timestamp required |
When it was evaluated. |
success_rate |
f64 |
The observed success rate over the whole window. |
budget_remaining |
f64 |
The fraction of the error budget still unspent. Negative once it is overspent, which says by how much the promise was missed. |
burn_fast |
f64 |
The burn rate over the one hour window. A rate of 1 spends the whole budget exactly over the whole window. |
burn_slow |
f64 |
The burn rate over the six hour window. Both windows must exceed their trip rate together before the objective trips, which is what stops a brief spike paging anyone. |
samples |
u64 required |
Runs in the whole window. |
condition |
MonitorCondition required |
The resulting condition. |
detail |
string required |
What a person should read. |
SloState
An objective and the condition it is latched in.
| Field | Type | Description |
|---|---|---|
slo |
Slo required |
The objective. |
condition |
MonitorCondition required |
Its current condition. |
last |
SloReading |
The most recent reading. Absent before the first evaluation. |
tripped_at |
timestamp |
When it first tripped, while it stays tripped. |
StoreCapabilities
What the storage backend behind this daemon can and cannot do. Three backends implement one storage trait and they are not equal: a reader on ClickHouse who searches for CAFE with an acute and finds nothing has no way to learn that this backend folds ASCII case and no more. Read this once at connect time and offer only what the deployment answers. Every field is named after the capability an endpoint names when it refuses with 501, so the two can be compared without translation.
| Field | Type | Description |
|---|---|---|
backend |
string required |
Which backend this describes: sqlite, postgres or clickhouse. The same string /healthz reports as storage. |
text_search |
TextSearch required |
What the q parameter matches. |
unicode_case_folding |
bool required |
Whether q folds case for letters outside ASCII. False means a search for CAFE with an acute does not find the lowercase form, which a reader otherwise meets as an empty result they cannot explain. |
immediate_deletes |
bool required |
Whether a delete is on disk when the call returns. False means the backend accepted the delete and applies it afterwards, so a count says what matched rather than what has already gone. |
discovery |
bool required |
Whether GET /v1/api/discovery answers. False means a filter box has to be filled from what is already on screen. |
discovery_label_values |
bool required |
Whether the metric_label_value facet is answerable, not only the keys. Separate because the values are the expensive half: a key set is small and bounded, and a value set is not. |
delta_polling |
bool required |
Whether GET /v1/api/deltas answers, so a list refreshes from a cursor rather than by running the whole query again. |
per_signal_retention |
bool required |
Whether retention can carry a different window per signal. False means one window covers all five. |
declarative_retention |
bool required |
Whether the window can be handed to the database engine, so nothing has to run a delete on a timer. |
partitioning |
Partitioning required |
How the signal tables are cut on their anchor time. |
partition_drop_retention |
bool required |
Whether a retention pass can drop a whole partition instead of deleting rows one at a time. |
idempotent_schema_init |
bool required |
Whether a start with nothing to apply issues no schema statement at all. False means every process replays the schema on every boot, which is harmless on one node and is metadata churn across a replicated deployment. |
theme_analysis |
bool required |
Whether this deployment answers the Intelligence endpoints. False means the store holds no note or theme tables and every one of those routes refuses with 501 naming this capability, so a client decides once whether to offer the feature rather than showing a reader an empty page they cannot account for. Every backend that answers gives the same themes: the clustering runs in the daemon over stored notes and not in SQL, so there is nothing here for a backend to do differently. |
DiscoveredValue
One value a filter dimension takes in a project.
| Field | Type | Description |
|---|---|---|
value |
string required |
The value, exactly as it is stored, so it can be sent straight back as a filter with no further mapping. |
last_seen |
timestamp required |
When mcpd last received a signal carrying it. This is arrival time and not the signal’s own timestamp, so a backfill of last year’s data does not make a value look current. Carried so a caller can rank down or grey out a value nothing has reported for a month. Values are ordered by value and not by this: a list somebody reads alphabetically is stable between polls, and one ordered by recency reshuffles under them as data arrives. |
DiscoveryResponse
The values one filter dimension takes, for a filter box. Answered from a distinct-value list the daemon maintains as signals arrive, not by scanning the signal tables, because the question is asked on every keystroke and the tables it would otherwise scan are the largest thing the deployment holds.
| Field | Type | Description |
|---|---|---|
facet |
Facet required |
Which dimension was asked for. |
key |
string |
The label key the values belong to, echoed back for metric_label_value. Absent for every other facet. |
values |
DiscoveredValue[] required |
The values, ordered by value, ascending. |
DeltaResponse
What has arrived since a cursor. A poll is not a page: paging walks a fixed result set backwards in the sort you chose, and polling walks forwards in arrival order and never ends, so the two cursors are different things and are deliberately not interchangeable. Rows come back oldest arrival first, so applying them in order to a list you already hold gives the same result as having listed afresh.
| Field | Type | Description |
|---|---|---|
signal |
DeltaSignal required |
Which signal these rows are. Exactly one of the three arrays below is present, and it is this one. |
cursor |
string required |
Where the next poll resumes. Always present, and unchanged from the request when nothing came back: moving it forward on an empty poll is what would drop a row that lands a moment later with an earlier arrival time. Treat it as meaningless, and send back the one you were given. |
has_more |
bool required |
True when the page filled and more is waiting, so a caller draining a backlog polls again at once rather than waiting for its next tick. |
traces |
Trace[] |
The runs that arrived, when signal is traces. |
spans |
Span[] |
The steps that arrived, when signal is spans. |
logs |
LogRecord[] |
The log lines that arrived, when signal is logs. |
AspectLimits
The bounds a clustering pass runs under. They are published because each one changes what a reader is looking at: a floor that has not been reached means no themes at all rather than an empty result, and a ceiling that has been reached means every share is a share of what was read. They are copied from the divergence watch, which is the one windowed statistic mcpd already ships. pass_armed is the first of them to read: the other three describe what a pass would do, and it says whether one runs.
| Field | Type | Description |
|---|---|---|
min_notes_to_cluster |
u64 required |
How many notes one aspect on one entity needs before a pass runs at all, 50 by default. Below it the aspect reads collecting and no snapshot is written. |
max_notes_per_pass |
u64 required |
The most notes one pass reads, 20,000 by default, taken newest first. A window holding more is clustered from the newest of them and its snapshot is marked capped. |
page_size |
u32 required |
How many notes the pass reads at a time while it fills a window, 500 by default. It bounds what the pass holds, not what it covers. |
max_aspects |
u32 required |
How many aspects one project may define, the four built-ins included. It is configuration and is published here so a client that lets an operator add an aspect knows when to stop instead of finding out from a refusal. |
pass_armed |
bool required |
Whether this deployment runs the clustering pass at all. False means no snapshot will ever be written however many notes arrive, so every aspect stays collecting or processing for ever and no theme route will have anything to answer with. It is configuration and not a fault, and it is not the same thing as the 501 the theme routes give: that says the storage backend cannot hold the tables, which nobody can switch on, while this says nobody has switched the pass on yet. It is published here rather than on every progress row because it is one fact about the deployment, and a client reads this route once to learn what the aspects are called. |
AspectCatalogue
Every aspect a project describes traces along, with the bounds the clustering pass runs under. The four built-ins are rows in the same table as anything you define, so there is no fallback list inside a client that can quietly disagree with this one.
| Field | Type | Description |
|---|---|---|
aspects |
Aspect[] required |
The aspects, in sort_order. Disabled aspects are included, because notes already recorded under one stay readable and a name that vanished from this list would make them look like a mistake. |
limits |
AspectLimits required |
The bounds a pass runs under in this deployment. |
EntityIndexRow
One entity that has aspect notes, with the roll-up a list needs to be worth reading. A row appears as soon as the first note is recorded, before any theme exists, because an empty screen with a count moving on it is a different thing from an empty screen and a reader has to be able to tell them apart.
| Field | Type | Description |
|---|---|---|
entity_kind |
EntityKind required |
The kind of thing this row is about. |
entity_name |
string required |
Its name. |
trace_count |
u64 required |
How many of its traces carry at least one note. This is not the entity’s trace count: writing a note lags ingestion, so a busy agent can show far fewer here. |
note_count |
u64 required |
How many notes are recorded about it. |
enabled_aspects |
u32 required |
How many aspects are switched on for this entity’s project. |
ready_aspects |
u32 required |
How many of those have themes to read. The two numbers read as 2 of 4 and are the honest answer to whether this row is worth opening yet. |
readiness |
AspectReadiness required |
The row’s overall state, which is the least advanced of its enabled aspects, so a row says processing while any one of them is still short of a snapshot. It never reports the best case. |
last_note_at |
timestamp required |
When the newest note about this entity was recorded, so a reader can tell a list that is filling from one that stopped. |
latest_snapshot_at |
timestamp |
The cutoff of this entity’s newest snapshot. Absent until a pass has written one, which is the ordinary state of a row that is still collecting. |
EntityIndexResponse
A page of the entities that have aspect notes.
| Field | Type | Description |
|---|---|---|
entities |
EntityIndexRow[] required |
The matching entities, most recently written to first. |
page |
Page required |
Cursor and totals. |
AspectProgressRow
How far one aspect has got for one entity. Counted from stored notes, so it is what has happened and not a projection of what will.
| Field | Type | Description |
|---|---|---|
aspect |
string required |
Which aspect this row is about. |
readiness |
AspectReadiness required |
The state this aspect is in for this entity. |
note_count |
u64 required |
How many notes are recorded along it. |
trace_count |
u64 required |
How many distinct traces those notes were written about. |
notes_needed |
u64 required |
How many more notes this aspect needs before a pass will run. Zero once the floor is reached, which is what turns a wait into a number a reader can watch. |
last_note_at |
timestamp |
When the newest note along this aspect was recorded. Absent when there are none. |
AspectProgress
What the daemon has recorded for one entity, aspect by aspect. This is the answer to why a themes screen is empty, and it separates the three cases that look identical on screen: nothing has been recorded, enough has been recorded and no pass has run yet, or there are themes to read and the reader is looking at the wrong aspect.
| Field | Type | Description |
|---|---|---|
entity_kind |
EntityKind required |
The kind of entity this describes. |
entity_name |
string required |
Its name. |
readiness |
AspectReadiness required |
The entity’s overall state, which is the least advanced of its enabled aspects. |
trace_count |
u64 required |
How many of this entity’s traces carry at least one note. |
aspects |
AspectProgressRow[] required |
One row per enabled aspect, in catalogue order. |
latest_snapshot |
ThemeSnapshot |
The newest snapshot for this entity, when a pass has written one. Absent while every aspect is still collecting. |
SnapshotLandmark
One snapshot in a timeline, with the reason it was chosen.
| Field | Type | Description |
|---|---|---|
snapshot |
ThemeSnapshot required |
The snapshot. |
reason |
SnapshotLandmarkReason |
Why this snapshot is in the selection. Present for presentation=landmarks only; an inventory returns consecutive snapshots and chooses nothing, so there is nothing to report. |
SnapshotListResponse
A selection of one entity’s snapshots, oldest first, so a timeline can be drawn without reordering anything.
| Field | Type | Description |
|---|---|---|
snapshots |
SnapshotLandmark[] required |
The selected snapshots, oldest first. |
total |
u64 required |
How many snapshots the entity has in the requested range. This is the number the selection was drawn from and not the number returned, so a timeline can say what it is standing in for. |
page |
Page required |
Cursor and totals. |
ThemeNode
One node of one stage of a flow: a theme, the stage’s noise, or the overflow the stage folded away.
| Field | Type | Description |
|---|---|---|
node_id |
string required |
Identifies this node inside this flow, and is what a link names at either end. It is not a theme id: the noise and overflow nodes have one of these and no theme id. |
kind |
ThemeNodeKind required |
Whether this node is a theme, the noise or the overflow. |
theme_id |
string |
The theme this node stands for. Present only when kind is theme, which is what makes the other two impossible to drill into by accident. |
label |
string required |
What to show for this node: a theme’s own label, or the words for noise or overflow. |
trace_count |
u64 required |
How many traces this node accounts for. |
coverage |
f64 required |
This node’s share of the stage’s traces, between 0 and 1. A stage’s nodes cover all of it, so its shares add to 1. |
ThemeStage
One aspect’s column in a flow. Stages come back in the order the aspects were asked for, and links only ever join neighbouring stages, so the order decides which co-occurrences are visible and is not a display preference.
| Field | Type | Description |
|---|---|---|
aspect |
string required |
Which aspect this stage is. |
trace_count |
u64 required |
How many traces this stage accounts for. |
nodes |
ThemeNode[] required |
The stage’s nodes, largest first, with the noise and overflow nodes last. |
ThemeLink
A ribbon between one node of a stage and one node of the next. It says the two describe the same traces and nothing more: a link between a goal theme and an outcome theme means they were written about the same runs, not that one caused the other.
| Field | Type | Description |
|---|---|---|
source_node_id |
string required |
The node this link leaves, in the earlier stage. |
target_node_id |
string required |
The node it reaches, in the next stage. |
trace_count |
u64 required |
How many traces are assigned to both ends. |
source_share |
f64 required |
That count as a fraction of the source node’s traces, between 0 and 1. |
target_share |
f64 required |
That count as a fraction of the target node’s traces, between 0 and 1. The two shares differ whenever the nodes are different sizes, and reading only one of them is how a thin ribbon gets mistaken for a weak relationship. |
ThemeFlow
One snapshot read across two to four aspects at once: a stage per aspect, and the links between neighbouring stages. Association, never causation.
| Field | Type | Description |
|---|---|---|
snapshot |
ThemeSnapshot required |
The snapshot this flow was computed from, so every count on the page can be read against the window it came from. |
stages |
ThemeStage[] required |
One stage per aspect, in the order they were asked for. |
links |
ThemeLink[] required |
The links between neighbouring stages. There are none between stages that are not neighbours, which is why the order of the aspects matters. |
ThemeDetail
One theme in one snapshot, or the answer that this snapshot did not hold it.
| Field | Type | Description |
|---|---|---|
snapshot |
ThemeSnapshot required |
The snapshot that was read, with its own counts, so a caller can write 28 of 70 traces in this window without a second request. |
theme |
Theme |
The theme, when this snapshot holds it. Absent is an answer and not an error: the theme did not exist in that window, which is what scrubbing a timeline back past a theme’s birth looks like. A theme id this entity has never had is a 404 instead. |
ThemeExample
One note from a theme, with the run it was written about. The two ids are different things: note_id identifies the sentence and trace_id identifies the run it describes.
| Field | Type | Description |
|---|---|---|
note_id |
string required |
The note this example is. |
trace_id |
string required |
The trace the note was written about. |
text |
string required |
The sentence, after redaction. This is the wording that put the trace in this theme, so a reader can see why it landed here rather than take it on trust. |
similarity |
u32 required |
How much this note’s wording overlaps the theme’s label, counted out of 64. It is a count of shared sketch positions and not a percentage, and it is an integer on purpose: no part of the clustering uses floating-point arithmetic, so two replicas cannot order the same notes differently. |
trace_started_at |
timestamp required |
When the run began, so examples can be read against the period they came from rather than against when the note was written. |
ThemeExampleListResponse
A page of the notes behind one theme.
| Field | Type | Description |
|---|---|---|
examples |
ThemeExample[] required |
The notes, most like the theme’s label first. |
page |
Page required |
Cursor and totals. |
ThemeHistoryPoint
One theme in one snapshot of its life. Every count belongs to that snapshot alone, and windows overlap, so a run of these is read as a series and never added up.
| Field | Type | Description |
|---|---|---|
snapshot_id |
string required |
The snapshot this point comes from. |
cutoff_at |
timestamp required |
When that snapshot became the current state. |
window_from |
timestamp required |
Start of that snapshot’s window, inclusive. |
window_to |
timestamp required |
End of that snapshot’s window, exclusive. |
state |
ThemeState required |
What had happened to the theme by this point. |
note_count |
u64 required |
How many notes were in the theme here. |
trace_count |
u64 required |
How many distinct traces those notes were written about. |
coverage |
f64 required |
The theme’s share of the notes that snapshot read for this aspect, between 0 and 1. Coverage and raw count can move in opposite directions, and coverage is the one that says whether a theme is gaining ground. |
split_from |
string[] required |
The themes this one came out of at this point. Empty unless state is split. |
split_into |
string[] required |
The themes that came out of this one at this point. Both directions are stored, so a caller never has to read a second theme’s history to learn what happened to this one. |
merged_from |
string[] required |
The themes that became this one at this point. Empty unless state is merge. |
merged_into |
string[] required |
The theme this one became at this point, when it stopped being its own theme. |
ThemeHistory
One theme across every stored snapshot that holds it, oldest first. This is the only theme response that names no single snapshot, because the series is the answer.
| Field | Type | Description |
|---|---|---|
theme_id |
string required |
The theme this history is of. |
aspect |
string required |
The aspect it was clustered along. |
label |
string required |
The theme’s label at its newest point. A label can change between snapshots, because the note most like the others can change while the theme holds together. |
points |
ThemeHistoryPoint[] required |
The series, oldest first, so nothing has to be reordered before it is drawn or differenced. |
page |
Page required |
Cursor and totals. |
TraceAspectNotes
Every aspect note recorded about one trace, which is at most one per aspect. Read it beside the trace itself: a note is the sentence that put this run in each of its themes, so it is the shortest route from a theme back to what actually happened.
| Field | Type | Description |
|---|---|---|
trace_id |
string required |
The trace these notes are about. |
notes |
AspectNote[] required |
The notes, in catalogue order. Empty when nothing has written one yet, which is ordinary for a recent trace. |
AccountCredentials
An email address and a password, for sign-up and for log-in.
| Field | Type | Description |
|---|---|---|
email |
string required |
The account’s email address. Compared without regard to case, and with surrounding spaces removed. |
password |
string required |
At least 10 characters and at most 1024 bytes. Stored only as an argon2id hash. |
AccountSession
A logged-in session. Send token as Authorization: Bearer <token>.
| Field | Type | Description |
|---|---|---|
token |
string required |
The session token. It grants every scope on the account’s own project and nothing else. It is shown once: the daemon keeps only its SHA-256 digest. |
expires_at |
timestamp required |
When the session stops working. Log in again after this. |
email |
string required |
The account’s email address, as stored. |
project |
string required |
The account’s project. Every signal an account key writes lands here, and every read is limited to it. |
Account
The account a session belongs to.
| Field | Type | Description |
|---|---|---|
email |
string required |
The account’s email address, as stored. |
project |
string required |
The account’s project, which is the only project its session and its keys reach. |
created_at |
timestamp required |
When the account was created. |
AccountKey
One API key an account holds. The secret is not part of it: the daemon keeps only a digest.
| Field | Type | Description |
|---|---|---|
id |
string required |
The key’s identifier, for revoking it. Not a secret. |
scope |
KeyScope required |
What the key may do. |
name |
string |
The label given when the key was created. |
prefix |
string required |
The first characters of the secret, so you can tell which key a process holds without the daemon storing the secret. |
created_at |
timestamp required |
When the key was created. |
CreateAccountKeyRequest
A request for a new API key.
| Field | Type | Description |
|---|---|---|
scope |
KeyScope required |
What the key may do. One scope per key. |
name |
string |
A label for the key, such as the service that will hold it. At most 100 characters. |
CreatedAccountKey
A new API key, with its secret. This is the only time the secret is shown.
| Field | Type | Description |
|---|---|---|
key |
AccountKey required |
The key as it is listed from now on. |
secret |
string required |
The key itself. Send it as Authorization: Bearer <secret>. Copy it now: the daemon keeps only its SHA-256 digest and cannot show it again. |
Span
One unit of work. Spans are the backbone of mcpd: logs, metrics, scores and feedback all hang off a span or a trace.
Stored in the spans table.
| Field | Type | Description |
|---|---|---|
trace_id |
string required |
128-bit trace id, 32 lowercase hex characters. OpenTelemetry-compatible. |
span_id |
string required |
64-bit span id, 16 lowercase hex characters. Unique within the trace. |
parent_span_id |
string |
The parent span within this trace. Absent on the root. |
external_parent_span_id |
string |
A parent from a foreign tracing system that mcpd did not create. Carried for correlation, never used for mcpd’s own tree. |
name |
string required |
What this operation is called. Keep it low-cardinality: search_docs, not search_docs(query=hello). |
kind |
SpanKind required |
What kind of operation this is. |
status |
SpanStatus required |
The outcome. |
start_time |
timestamp required |
When the operation began, in UTC with nanosecond precision. |
end_time |
timestamp |
When it finished. Absent while the span is still running. |
duration_ns |
u64 |
Derived from the timestamps at ingest, stored so the database never has to compute it to sort. |
is_root |
bool required |
True when this span has no parent inside the trace. Materialised so the trace list never scans children. |
is_event |
bool required |
True for a point in time rather than a duration. Event spans have no end. |
entity_kind |
EntityKind |
The kind of thing that produced this span. |
entity_id |
string |
Stable identifier of the producing entity. |
entity_name |
string |
Display name of the producing entity. |
input |
json |
What went in. For a model call this is the full prompt, and it is the single most valuable field mcpd stores. |
output |
json |
What came out. For a model call this is the completion. |
attributes |
json |
Kind-specific structured detail. See the attribute reference for what each kind carries. |
metadata |
json |
Anything you want to attach. mcpd never interprets this. |
usage |
TokenUsage |
Token counts, on model and embedding spans. |
cost |
Cost |
Computed cost, on spans that consumed tokens. |
model_parameters |
ModelParameters |
How the model was called: the sampling settings the caller chose. Absent on a span that made no model call, and on one whose producer did not record them. |
error |
SpanError |
Failure detail, when status is error. |
links |
SpanLink[] |
Causal references to other spans or traces. |
tags |
string[] |
Free labels for filtering. Only meaningful on a root span, where they label the whole trace. |
correlation |
Correlation required |
Which project, environment, session, thread and user this belongs to. |
model |
string |
Model identifier, denormalised out of attributes so it can be indexed and grouped. |
provider |
string |
Model provider, denormalised for the same reason. |
received_at |
timestamp required |
When mcpd accepted this span. Differs from start_time for buffered or replayed data, and is what retention is measured against. |
ingest_key |
string |
Producer-supplied idempotency key. A retried batch with the same key is accepted once. |
Primary key: trace_id, span_id
Indexes
correlation.project, start_time: The trace list, newest first, scoped to a project.correlation.project, is_root, start_time: Root-only projection for the trace list.correlation.session_id: Every trace in a conversation.model, start_time: Cost and latency grouped by model.status, start_time: Failures, newest first.
Trace
A whole run, projected from its spans. mcpd materialises this at ingest so the list view is one indexed read rather than an aggregation over every span. One per project and trace id: a trace id crosses service boundaries, so two projects that call each other each hold their own run under it, built from their own spans.
Stored in the traces table.
| Field | Type | Description |
|---|---|---|
trace_id |
string required |
The trace this summarises. |
root_span_id |
string |
The root span, when one has arrived. |
name |
string required |
The root span’s name. |
kind |
SpanKind required |
The root span’s kind. |
status |
SpanStatus required |
error when any span in the trace failed, otherwise the root’s status. |
start_time |
timestamp required |
Earliest start across the trace. |
end_time |
timestamp |
Latest end across the trace. Absent while anything is still running. |
duration_ns |
u64 |
Wall-clock span of the whole trace. |
span_count |
u32 required |
How many spans have arrived. |
error_count |
u32 required |
How many of them failed. |
usage |
TokenUsage |
Token usage summed across every model span in the trace. |
cost |
Cost |
Cost summed across the trace. This is the number a bill is reconciled against. |
model_count |
u32 required |
How many model calls the run made. |
tool_count |
u32 required |
How many tool calls the run made. |
tags |
string[] |
Tags from the root span. |
correlation |
Correlation required |
Context inherited from the root span. |
score_summary |
json |
Mean score per scorer id, maintained as scores arrive, so the list can sort by quality without a join. |
feedback_summary |
json |
Counts per feedback kind and value. |
is_complete |
bool required |
True once the root span has ended and no span is still open. |
received_at |
timestamp required |
When the first span of this trace arrived. |
Primary key: project, trace_id
Indexes
correlation.project, start_time: The default list ordering.correlation.project, status, start_time: Failed runs.correlation.project, cost.total_cost: Most expensive runs.correlation.project, duration_ns: Slowest runs.
LogRecord
A structured log line, correlated to the span that produced it. Logs are stored beside traces so ‘show me the logs for this run’ is one query, not a context switch to another tool.
Stored in the logs table.
| Field | Type | Description |
|---|---|---|
log_id |
string required |
Unique id for this record. |
trace_id |
string |
The trace this line belongs to, when it was emitted inside one. |
span_id |
string |
The span this line belongs to. |
timestamp |
timestamp required |
When the line was emitted. |
level |
LogLevel required |
Severity. |
message |
string required |
The rendered message. |
fields |
json |
Structured key/value context. Redacted like any payload. |
logger |
string |
Which logger emitted it. |
correlation |
Correlation required |
Project, environment, session and user. |
received_at |
timestamp required |
When mcpd accepted it. |
Primary key: log_id
Indexes
trace_id, timestamp: All logs for a run, in order.correlation.project, level, timestamp: Severity-filtered log search.
MetricPoint
One numeric observation. mcpd aggregates these into time buckets on read, and keeps raw points only as long as the raw retention class allows.
Stored in the metrics table.
| Field | Type | Description |
|---|---|---|
metric_id |
string required |
Unique id for this observation. |
name |
string required |
Metric name, such as mcpd.model.duration. |
kind |
MetricKind required |
How it aggregates. |
value |
f64 required |
The observed value. |
unit |
string |
UCUM unit, such as ms, By or {token}. |
timestamp |
timestamp required |
When it was observed. |
labels |
json |
Dimensions. Passed through the cardinality filter, which rejects unbounded keys. |
buckets |
json |
Histogram bucket boundaries and counts, on histogram points. |
trace_id |
string |
Exemplar link: the trace that produced this observation. |
span_id |
string |
Exemplar link: the span that produced it. This is what turns a spike on a chart into a specific run you can open. |
correlation |
Correlation required |
Project and environment. |
received_at |
timestamp required |
When mcpd accepted it. |
Primary key: metric_id
Indexes
correlation.project, name, timestamp: Time series read path.
Score
A machine evaluation of a span or a trace.
Stored in the scores table.
| Field | Type | Description |
|---|---|---|
score_id |
string required |
Unique id. |
trace_id |
string |
The scored trace. |
span_id |
string |
The scored span, when the score is about one step rather than the whole run. |
scorer_id |
string required |
Which scorer produced this, such as answer_relevance. |
scorer_version |
string |
The scorer’s version. A score is only comparable to another score from the same version. |
source |
ScoreSource required |
Live, offline, experiment or manual. |
value |
f64 required |
The numeric result. |
min |
f64 |
Lower bound of the scale. Without it, a bare number is not interpretable. |
max |
f64 |
Upper bound of the scale. |
label |
string |
A categorical result, when the scorer produces one alongside the number. |
reason |
string |
Why. For an LLM judge this is the judge’s explanation, and it is what makes a score arguable rather than magic. |
score_trace_id |
string |
The trace of the scoring run itself, so a suspicious score can be debugged like any other run. |
cost |
Cost |
What producing this score cost. Judged evaluation is not free and mcpd refuses to hide that. |
metadata |
json |
Anything else. |
timestamp |
timestamp required |
When the score was produced. |
correlation |
Correlation required |
Inherited from the target. |
Primary key: score_id
Indexes
trace_id: Every score for a run.correlation.project, scorer_id, timestamp: A scorer’s values over time.
Feedback
A human evaluation. Kept distinct from Score because the two answer different questions and must never be averaged together.
Stored in the feedback table.
| Field | Type | Description |
|---|---|---|
feedback_id |
string required |
Unique id. |
trace_id |
string |
The trace being judged. |
span_id |
string |
The span being judged. |
kind |
FeedbackKind required |
Thumbs, rating, correction, tag or comment. |
value |
json required |
The judgement. Its shape follows kind. |
comment |
string |
Free prose the person added. |
author_id |
string |
Who gave it. Hashed by default. |
author_kind |
string |
Whether the author was an end user, a reviewer or an operator. |
timestamp |
timestamp required |
When it was given. |
metadata |
json |
Anything else. |
correlation |
Correlation required |
Inherited from the target. |
Primary key: feedback_id
Indexes
trace_id: Every judgement on a run.
Project
The tenancy boundary. Every signal belongs to exactly one project; retention, redaction, sampling, budgets and access are all configured per project.
Stored in the projects table.
| Field | Type | Description |
|---|---|---|
project_id |
string required |
Stable identifier, used in API paths and ingest keys. |
name |
string required |
Display name. |
created_at |
timestamp required |
When it was created. |
retention_days |
u32 |
How long to keep signals. Absent means the server default. |
sampling |
json |
The sampling policy for this project. |
redaction |
json |
Extra redaction rules, applied on top of the server defaults. |
budget |
json |
Cost budget and what happens when it is exceeded. |
metadata |
json |
Anything else. |
Primary key: project_id
Dataset
A fixed collection of inputs, with optional expected outputs, that an experiment runs against. Datasets are how a change is proved not to regress.
Stored in the datasets table.
| Field | Type | Description |
|---|---|---|
dataset_id |
string required |
Stable identifier. |
name |
string required |
Display name. |
description |
string |
What this dataset is for. |
item_count |
u32 required |
How many items it holds. |
created_at |
timestamp required |
When it was created. |
correlation |
Correlation required |
Which project owns it. |
Primary key: dataset_id
DatasetItem
One case in a dataset.
Stored in the dataset_items table.
| Field | Type | Description |
|---|---|---|
item_id |
string required |
Stable identifier. |
dataset_id |
string required |
Which dataset this belongs to. |
input |
json required |
What to feed the system under test. |
expected |
json |
The reference answer, when there is one. Absent for open-ended cases judged by a scorer. |
metadata |
json |
Anything else, such as a difficulty label or a source citation. |
source_trace_id |
string |
The production trace this case was captured from, when it was. This is how a bug report becomes a permanent regression test. |
Primary key: item_id
Experiment
One evaluation run of a system against a dataset. Comparing two experiments is how a change is accepted or rejected.
Stored in the experiments table.
| Field | Type | Description |
|---|---|---|
experiment_id |
string required |
Stable identifier. |
name |
string required |
Display name. |
dataset_id |
string required |
What it ran against. |
status |
ExperimentStatus required |
Where it got to. |
started_at |
timestamp |
When it began. |
finished_at |
timestamp |
When it ended. |
item_count |
u32 required |
Cases attempted. |
summary |
json |
Aggregate score per scorer, with mean, standard deviation and count. |
baseline_id |
string |
The experiment this one is compared against. |
cost |
Cost |
What the whole run cost. |
error |
string |
Why it failed, when it did. |
metadata |
json |
The configuration under test: model, prompt version, git sha. |
correlation |
Correlation required |
Which project owns it. |
Primary key: experiment_id
AuditEvent
One recorded attempt to read stored payloads, or to delete them. Written by the handlers that hand prompts and completions back out of mcpd, so the question “who read this run” has an answer. The audit log is not trimmed by the retention pass: a log that deleted itself on the same schedule as the data it describes could not say who read the data that has since gone.
Stored in the audit_log table.
| Field | Type | Description |
|---|---|---|
audit_id |
string required |
Unique id. |
timestamp |
timestamp required |
When the request was handled. |
principal |
string required |
Which key acted, as a short hex digest of it. The key itself is never written here. anonymous when the request carried no key, which is what an unprotected daemon looks like. |
subject |
string required |
The end identity a proxy asserted for this request. Always empty in this build. The field exists from the first release so that proxy identity can be added later without a migration. |
action |
string required |
What was done, named as the endpoint id: get_trace, list_spans, delete_trace. |
project |
string required |
The project the request was made against. |
resource |
string |
The trace or dataset the request named, when it named one. |
result |
AuditResult required |
Whether authorisation allowed the request or refused it. |
Primary key: audit_id
Indexes
project, timestamp: One project’s audit log, newest first.
Definition
A stored definition. Immutable per version: a run pins the version it started against, and that version never changes underneath it.
Stored in the definitions table.
| Field | Type | Description |
|---|---|---|
project |
string required |
Which project owns it. |
id |
string required |
Its identifier within the project. |
version |
u32 required |
Its version. A run started without one pins the highest stored version at that moment. |
kind |
DefinitionKind required |
What kind of definition it is. |
body |
json required |
The document as stored, minus version. |
created_at |
timestamp required |
When this version was stored. |
Primary key: project, id, version
Run
One execution of one definition version. The daemon owns the run between steps: its journal, its deadlines, its waits, its cancel and its trace. Your own process owns everything inside a step.
Stored in the runs table.
| Field | Type | Description |
|---|---|---|
run_id |
string required |
Its identifier, minted at start. |
project |
string required |
Which project owns it. |
definition_id |
string required |
Which definition it runs. |
definition_version |
u32 required |
Which version. Pinned at start and never re-resolved. |
status |
RunStatus required |
Where it is. |
cursor |
RunCursor required |
The frontier: where in the graph the run has reached. |
input |
json |
What it was started with, after validation against the definition’s input_schema. |
output |
json |
What the definition’s output template rendered when the run reached the end of the graph. |
state |
json |
A JSON object merged shallowly on every complete that sends one. Later steps read it through the lease. |
error |
string |
Why it failed, when it did: the failing step’s message, or the ceiling it spent. |
correlation |
Correlation required |
The context stored at start and copied onto every span the daemon writes for the run, with run_id set to this run’s id. |
trace_id |
string required |
The trace every span of this run belongs to. 32 lowercase hex characters, minted at start. |
steps_used |
u32 required |
How many steps this run has completed. Counted against max_steps on the root of the run tree. |
max_steps |
u32 |
The step ceiling from the definition’s limits, when it declares one. On a root it bounds the whole tree. |
cost_usd |
f64 required |
What the run’s steps have cost so far, priced from the usage workers reported. |
max_cost_usd |
f64 |
The spend ceiling from limits, when declared. A claim refused for a spent ceiling ends the run failed. |
deadline_at |
timestamp |
When limits.run_timeout_ms passes. Absent when the definition declares no timeout, and once the run has ended. |
idempotency_key |
string |
The key the start request carried, when it carried one. A repost of it within 24 hours returns this run. |
root_run_id |
string required |
The root of the run tree, which ceilings are enforced against. Equal to run_id on a root. |
parent_run_id |
string |
The run whose subworkflow step started this one. |
parent_step_key |
string |
The step in the parent that this run is the body of. |
depth |
u32 required |
How many parents lie between this run and the root. Zero on a root. |
created_at |
timestamp required |
When it started. |
updated_at |
timestamp required |
When it last moved. |
ended_at |
timestamp |
When it reached a terminal status. |
Primary key: run_id
StepAttempt
One attempt at one step of a run. A run’s journal is these rows, append only: a retry is a new row, never a rewrite of the old one.
Stored in the step_attempts table.
| Field | Type | Description |
|---|---|---|
run_id |
string required |
Which run. |
step_key |
string required |
Which step, with any enclosing branch, item or iteration folded into the key: refund/issue, classify[3], poll[7]/classify. |
attempt |
u32 required |
Which attempt this is. The fence: a lease for an earlier attempt is refused once a later one exists. |
kind |
StepKind required |
What kind of step. |
effect |
Effect required |
What a duplicated body would cost. |
status |
AttemptStatus required |
Where the attempt is. |
input |
json |
The rendered input handed to the worker, on a claimable step. |
output |
json |
What the worker returned on complete, or the event payload on a resumed wait. |
error |
StepError |
Why it failed, when it did. |
usage |
StepUsage |
The token usage the worker reported on complete. |
cost_usd |
f64 |
What the daemon priced that usage at. |
span_id |
string required |
The span the daemon opened for this attempt. 16 lowercase hex characters, minted when the row was written. A worker’s own spans nest under it. |
lease_owner |
string |
The worker_id holding the lease, while one does. |
lease_until |
timestamp |
When the lease lapses without a heartbeat. |
started_at |
timestamp |
When the worker posted started. Absent when it never did, which is what the reclaim rule reads. |
orphaned_at |
timestamp |
When the lease lapsed. |
reclaim_until |
timestamp |
When the reclaim window closes. |
attempts_left |
u32 |
How many attempts the node’s retry budget still allows after this one. |
ended_at |
timestamp |
When it reached a terminal status. |
created_at |
timestamp required |
When the row was written. |
Primary key: run_id, step_key, attempt
RunEvent
One row of a run’s event log. Written in the transaction that made the change it describes, with seq allocated from the run header, so a reader that has seen a row has seen every row below it.
Stored in the run_events table.
| Field | Type | Description |
|---|---|---|
run_id |
string required |
Which run. |
seq |
u64 required |
Its position in this run’s stream, from zero and without gaps. Pass it as after to reattach. |
kind |
RunEventKind required |
What happened. |
payload |
json |
The detail, shaped by kind. |
at |
timestamp required |
When it was written. |
Primary key: run_id, seq
Aspect
An axis that traces are described along. An aspect is a definition and not a measurement: it carries the name a note is filed under, the words an operator reads on screen and, where a model writes the sentence, the instruction handed to whatever produces it. Four ship with the daemon as ordinary rows rather than as a list compiled into a client, so there is one source of truth for what exists: goal, what the user wanted; sentiment, the tone they expressed; behaviour, what the agent did about it; and outcome, how the interaction ended. A stored version never changes, so a note recorded a month ago still names the definition it was written against.
Stored in the aspects table.
| Field | Type | Description |
|---|---|---|
project |
string required |
Which project owns it. Aspects are project-scoped, like every other tenancy boundary here. |
name |
string required |
The aspect’s name, lowercase with underscores, such as goal or refund_reason. This is what a note is filed under and what every query parameter names. |
label |
string required |
The display name, such as Goal. Stored rather than derived from the name, so an aspect reads the same in every screen and every language of SDK. |
description |
string required |
One line saying what this axis is about, shown beside the label so a reader does not have to infer the axis from its name. |
instruction |
string |
The text handed to whatever writes a note along this aspect, at most 2000 bytes. The daemon stores it and hands it out; what reads it and writes the sentence sits outside the daemon. Absent when derived is true, because a derived aspect needs no instruction. |
derived |
bool required |
Whether the daemon writes this aspect’s notes itself, with no model anywhere. outcome ships derived, written from the run’s status, the step error class, the silent producer flag on the cause report and the divergence watch, so a deployment with nothing configured still fills one aspect and can see the whole pipeline work before a model is wired to anything. |
built_in |
bool required |
Whether the daemon seeded this aspect. A built-in is a row like any other and can be switched off, but it cannot be deleted, because notes already name it. |
enabled |
bool required |
Whether notes are written along this aspect in this project. It is configuration and read-only here. Switching an aspect on affects notes recorded afterwards and never rewrites what is stored. |
sort_order |
u32 required |
Where this aspect sits in every list and in the default column order, ascending. Stored, so the order an operator learns is the order they keep. |
version |
u32 required |
This definition’s version. Editing an aspect stores a new version rather than changing this one, so analysis already recorded is never rewritten under a reader. |
created_at |
timestamp required |
When this version was stored. |
Primary key: project, name, version
Indexes
project, sort_order: The catalogue, in the order it is shown.
AspectNote
One sentence recorded against one trace along one aspect. A note proves one thing: that this sentence was recorded against that trace by the definition named in aspect, at the version in definition_version. It does not prove the sentence is true. Nothing here is checked against the trace it describes, so a note is evidence of what a definition said and not evidence of what happened, and the trace is what settles the difference. The daemon also keeps a small sketch of the wording, which is what clustering compares, and that sketch is deliberately not a field on this entity: it is a detail of how the pass is stored, a caller can do nothing with it, and publishing it would fix the hashing where it stands today. The text is payload. It passes through the same redactor as every other signal on the way in, retention sweeps it, and erasing a subject erases it.
Stored in the aspect_notes table.
| Field | Type | Description |
|---|---|---|
note_id |
string required |
Unique id of the note. This identifies the sentence and never the run: a note id and a trace id are different things and conflating them is the mistake this separate name exists to prevent. |
trace_id |
string required |
The trace this sentence is about. |
aspect |
string required |
Which aspect this note is filed under, matching Aspect.name. |
text |
string required |
The sentence, after redaction. It is the only text the read API returns for a note, and it is what the clustering pass reads. |
definition_version |
u32 required |
The version of the aspect definition this note was written against. Editing a definition mints a new version, so an older note keeps naming what was actually asked for at the time. |
model |
string |
Which model wrote the sentence, when one did. Absent for a derived aspect, where the daemon wrote the note itself. |
created_at |
timestamp required |
When the note was recorded. Writing a note lags the trace it describes, sometimes by a long way, so a trace can be visible for hours before it carries one. |
correlation |
Correlation required |
Inherited from the trace, so a note is scoped, filtered, retained and erased exactly as its trace is. |
Primary key: note_id
Indexes
trace_id: Every note about one run.correlation.project, aspect, created_at: One aspect’s notes newest first, which is the order a clustering pass reads them in.
ThemeSnapshot
One clustering pass over one entity, covering a moving window of notes. A snapshot is what turns a clustering into a series: every theme belongs to exactly one, and every theme query names one. Windows overlap, so counts from two snapshots must never be added together. A trace whose note falls inside three windows is counted once in each of them, and summing the three counts it three times. Compare snapshots or read one; do not sum them. Reads never recompute either: a pass writes a snapshot on its own timer and every read is a lookup of what it wrote, so refreshing a screen shows the same numbers rather than a fresh clustering.
Stored in the theme_snapshots table.
| Field | Type | Description |
|---|---|---|
snapshot_id |
string required |
Opaque id, echoed back unchanged. Never build one by hand and never read meaning into its shape. |
project |
string required |
Which project this pass covered. |
entity_kind |
EntityKind required |
The kind of thing this pass covered. |
entity_name |
string required |
The name of that thing. An entity is the pair of a kind and a name, which spans already carry, so nothing new is assigned to identify one. |
aspects |
string[] required |
The aspects this pass covered, in catalogue order. An aspect with too few notes when the pass ran is absent, so a missing name means it is still collecting and not that anything failed. |
ordinal |
u64 required |
Where this snapshot falls in the entity’s series, counting from one at the oldest snapshot still stored. |
cutoff_at |
timestamp required |
When this snapshot became the current state. A timeline is ordered by this and not by the window bounds, because two passes can cover overlapping windows. |
window_from |
timestamp required |
Start of the window of notes read, inclusive. |
window_to |
timestamp required |
End of the window of notes read, exclusive. |
trace_count |
u64 required |
How many distinct traces the notes read belong to. |
note_count |
u64 required |
How many notes the pass actually read. Every coverage figure in this snapshot is a share of this number and never of the number of notes in the window, which is what makes capped worth reading. |
capped |
bool required |
Whether the pass reached its ceiling of 20,000 notes and clustered the newest of them rather than all of them. True means the window holds more notes than were read, so the denominator of every share here is note_count and not the window. |
Primary key: snapshot_id
Indexes
project, entity_kind, entity_name, cutoff_at: One entity’s series of passes, newest first.
Theme
A cluster of aspect notes that share their wording, in one snapshot along one aspect. A theme groups aspect notes that share their wording, which is why mcpd needs no model and no embedding to build one. It does not read the sentences, so two notes that mean the same thing in different words land in separate themes, and a theme’s label is the note most like the others rather than a summary written for it. The clustering is a pure function of the notes it reads, with no floating-point arithmetic in the comparison, so two replicas of one deployment reading the same notes write the same themes and cannot disagree about where a boundary falls.
Stored in the themes table.
| Field | Type | Description |
|---|---|---|
theme_id |
string required |
Opaque id, stable across snapshots, which is what makes a history possible. A theme keeps its id while its wording holds, so the same id in two snapshots is the same theme. Nothing is encoded in the shape of it. |
snapshot_id |
string required |
The snapshot this theme belongs to. |
aspect |
string required |
Which aspect it was clustered along. Clustering runs once per aspect, so one trace can sit in a different theme in each of them. |
entity_kind |
EntityKind required |
The kind of entity this theme was built for. |
entity_name |
string required |
The name of that entity. |
label |
string required |
The theme’s label, which is the text of one of its own notes: the note most like the rest of them. Nothing wrote it for the screen, so it reads as one real sentence about one real trace rather than as a description of the group, and it can change between snapshots as the group does. |
label_note_id |
string required |
The note the label is taken from. |
label_trace_id |
string required |
The trace that note was written about, so a reader can open the run behind the label instead of taking the label’s word for it. |
state |
ThemeState required |
What happened to this theme between the previous snapshot and this one. |
note_count |
u64 required |
How many notes are in this theme. |
trace_count |
u64 required |
How many distinct traces those notes were written about. |
coverage |
f64 required |
This theme’s share of the notes the snapshot read for this aspect, between 0 and 1. The denominator is the snapshot’s note_count, so in a capped snapshot it is a share of what was read. |
Primary key: snapshot_id, aspect, theme_id
Indexes
theme_id, aspect: One theme across every snapshot that holds it, which is what a history reads.