SOLWYN
Commands

record

Attribute a completed provider call, or a batch of them, that had no budget reservation

record reports one caller-made provider call that has already completed and for which no reservation was taken — a call made before the guardrail ran, an offline replay, a local model at $0. It never touches the budget reservation plane: nothing is reserved, nothing is settled. Costs are computed server-side from the quantities you report.

Do not record a call that budget confirm already settled; confirm records attribution itself under the same call id.

Terminal
solwyn record --model <name> --provider <provider> [--call-id <uuid>] [usage and attribution flags...]
solwyn record --batch <file>|-
OptionTypeDefaultPurpose
--modelstringrequiredModel that served the call.
--providerprovider enumrequiredProvider that served the call.
--call-idcanonical lowercase UUIDgeneratedYour identifier for the logical call. Reuse it when retrying the same recording.
--statussuccess | errorsuccessHow the call ended.
--latency-msfloat ≥ 00End-to-end latency.
--modalitytext | image | audio | video | embeddingtextThe call's modality.
--provider-region, --service-tierstring, enumnoneServing region and tier, when relevant.
--agent-run-id, --parent-run-id, --run-namestringSOLWYN_AGENT_RUN_ID for the run idRun attribution. See runs.
--tagkey=value, repeatablenoneAttribution tags; overrides SOLWYN_TAGS.
--is-estimatedbooleanfalseMark the token breakdown as estimated rather than provider-reported.
--batchpath or -noneA JSON array of event objects. Excludes every other flag.

The thirteen token counters and seven media quantities are the same as budget confirm takes.

Contract
Authproject key with full scope (a JWT is rejected)
Exit codes0 recorded or call-id replay · 1 rejection, collision, or API error · 2 usage · 3 auth
JSON schemasolwyn.record.v1
Terminal
$ solwyn record --model gpt-4o --provider openai --input-tokens 900 --output-tokens 210 --tag env=prod
Field       Value
Status      ingested
Ingested    1
Rejected    0
Duplicates  0
Call IDs    12345678-1234-1234-1234-123456789abc

Outcomes

Status tells you what happened, and the exit code follows it:

StatusMeaningExit
ingestedRecorded.0
deduplicatedThe same call id was already recorded — a clean idempotent replay.0
rejectedThe API refused the event; see the rejection code.1
failedA partial or ambiguous write.1
not_recordedRecognized capacity rejection with every attempt proven nonaccepting and no earlier completed chunk.1

Rejection codes are explained inline: unknown_model (use the exact model name), unknown_service_tier, invalid_tags, tag_cardinality_exceeded, unsupported_modality. A legacy_key duplicate — a collision on an older identity rather than the call id — is reported as failed with a warning, because it cannot prove the logical call is durable.

An API 400 or 422 exits 2, not 1, because it means the event you built is malformed. When a write fails partway, the CLI still prints the payload with the call ids it knows, then exits with the error, so a retry can reuse those ids instead of minting new ones.

Batch recording

Terminal
$ solwyn record --batch events.json
Recorded batch progress: 1000/2400 events processed.
Recorded batch progress: 2000/2400 events processed.
Recorded batch progress: 2400/2400 events processed.

The file is a JSON array of event objects using the wire field names (model, provider, input_tokens, tags, and so on). The CLI submits it in chunks of 1000, fills in a fresh SDK instance id for the whole invocation and a monotonically increasing timestamp per event, and applies SOLWYN_AGENT_RUN_ID and SOLWYN_TAGS to events that do not set their own. Do not supply cost_usd or lease_id. Any other flag alongside --batch is a usage error.

Identities, capacity and timing

Persist call_id before sending. Core deduplicates (project_id, call_id, attempt_index) while retained; this acknowledges a no-op and never updates an existing event. Batch events can preserve observed attempt indices. Generated IDs cannot recover a wholly lost response. Retain original call_ids, indices and known dispositions after partial or uncertain writes.

GET/ingest 503 retries use the shared bounded Retry-After allowance; that retry policy alone does not prove absence. Only the recognized ingest rejection rule establishes not_recorded. A later recognized rejection cannot erase earlier uncertainty. Do not mint new identities to force acknowledgement.

Budget and quota periods use server receive time; analytics use the stored event timestamp, within Core's clock and retention bounds. A backdated batch counts when received and appears on its original analytics day. It cannot bypass current-period limits.

  • budget — the check → confirm loop for spend you can reserve first
  • runs — the run a recorded call is attributed to
  • tags — the keys and values you have recorded
  • Agent integration — where record fits in an agent's guardrail loop

On this page