Records
Sinks receive batches that mix two dataclasses, ToolCallRecord and
EventRecord. Tell them apart with isinstance or record.record_type
("tool_call" or "event"). Both have to_row() (column values) and
to_dict() (JSON-friendly, timestamp as ISO 8601).
from fastmcp_feedback.instrumentation import EventRecord, ToolCallRecordToolCallRecord
Section titled “ToolCallRecord”One tool call, built after the tool returns or raises.
| Field | Type | Description |
|---|---|---|
id | str | Random UUID, chosen before the tool runs so events recorded inside it can carry it. Primary key of ffb_tool_calls. |
tool | str | Tool name as called. |
started_at | datetime | UTC, when the middleware saw the call. |
duration_ms | float | Wall time through the middleware chain below this one, including the tool. |
ok | bool | outcome == "ok". |
outcome | str | "ok", "soft_error" (a result the classifier flagged) or "error" (the tool raised). |
mode | str | "meta" or "full"; "meta" for tools in meta_only_tools. |
error_type | str | None | Exception class name (the original cause, not FastMCP’s ToolError wrapper), or "SoftError". |
error_message | str | None | Redacted, cut to max_error_chars. For soft errors, the classifier’s string, such as status=failed: disk full. |
session_id | str | None | The session key: HTTP mcp-session-id, a per-process key on stdio, else FastMCP’s ctx.session_id. |
request_id | str | None | The MCP request id. |
client_id | str | None | From the identity resolver, else FastMCP’s ctx.client_id. |
user_sub | str | None | From the identity resolver. |
caller_kind | str | None | From the identity resolver. |
identity | dict | Other identity resolver keys, redacted. |
server_version | str | None | From the enricher, else the middleware’s server_version. |
args_size | int | None | Bytes of the JSON-encoded arguments, before redaction. |
result_size | int | None | Bytes of the JSON-encoded result payload, before redaction. None when the tool raised. |
args | Any | Redacted arguments in full mode, else None. |
result | Any | Redacted result payload in full mode, else None. The payload is the structured content when present, otherwise the content items. |
extra | dict | Enricher output, redacted. |
sample_rate | float | None | The tool’s rate on sampled-in ok rows; None for everything recorded unconditionally. |
EventRecord
Section titled “EventRecord”One event from record_event or record_llm_call.
| Field | Type | Description |
|---|---|---|
id | str | Random UUID. Primary key of ffb_events. |
kind | str | Dotted name, at most 128 characters. |
occurred_at | datetime | UTC, when record_event was called. |
key | str | None | Join key, at most 255 characters. |
call_id | str | None | The running call’s id when recorded inside a tool call, or the call_id passed in. |
session_id | str | None | The running call’s session key, inside a call. |
user_sub | str | None | As passed to record_event. The identity resolver is not consulted. |
caller_kind | str | None | Not set by record_event; present for schema symmetry. |
client_id | str | None | Not set by record_event; present for schema symmetry. |
server_version | str | None | The middleware’s server_version. |
attrs | dict | Redacted and made JSON-safe: NaN and infinities become strings, datetimes ISO strings, unknown types str(value). |
Event kinds the package records
Section titled “Event kinds the package records”| Kind | Recorded by | key | attrs |
|---|---|---|---|
feedback.submitted | submit_feedback with instrumentation= | feedback id | type, title, description, submitter |
llm.call | record_llm_call | as passed | model, ok, and provider, duration_ms, input_tokens, output_tokens, error, error_type, prompt, completion when present |
CallLink
Section titled “CallLink”Returned by capture_recent_calls, one per linked call.
| Field | Type | Description |
|---|---|---|
call_id | str | The call’s id. |
rule | str | "session" or "user_window": why it matched. |
tool | str | Tool name. |
started_at | datetime | UTC. |
ok | bool | The call’s ok. |
position | int | Order within the link set, from 0, oldest first. |
JSON lines
Section titled “JSON lines”JsonLinesSink writes {"record": record_type, **record.to_dict()} per record.
A call line:
{"record": "tool_call", "tool": "render", "started_at": "2026-10-02T03:44:16.471791+00:00", "duration_ms": 0.51, "ok": false, "mode": "full", "outcome": "soft_error", "id": "25568541-cf28-472c-9537-3151d7d21be7", "error_type": "SoftError", "error_message": "status=failed: out of memory", "session_id": "fd6283b1-7dd9-411c-b3fb-838a9c38330a", "request_id": "4", "client_id": null, "user_sub": "dev", "caller_kind": null, "identity": {"team": "render"}, "server_version": null, "args_size": 16, "result_size": 46, "args": {"scene": "big"}, "result": {"status": "failed", "error": "out of memory"}, "extra": {}, "sample_rate": null}