Skip to content

Latest commit

 

History

History
166 lines (139 loc) · 7.82 KB

persistence.md

File metadata and controls

166 lines (139 loc) · 7.82 KB

Request persistence

Sigma request persistence uses the public sigma.Request JSON shape. Store a request with sigma.MarshalRequest, load it with sigma.UnmarshalRequest, and pass the decoded value to Client.Complete or Client.Stream.

data, err := sigma.MarshalRequest(req)
if err != nil {
	return err
}

req, err = sigma.UnmarshalRequest(data)
if err != nil {
	return err
}

final, err := client.Complete(ctx, model, req)

No envelope or schema version is added today. The JSON field names on Request, Message, ContentBlock, and Tool are part of sigma's public API stability policy. If a future format change needs an envelope, it should be added as an opt-in format rather than changing what MarshalRequest emits.

UnmarshalRequest rejects unknown struct fields and then validates replay invariants:

  • every message has a supported role
  • content blocks have a supported type for their role
  • image blocks use supported base64 or url sources
  • tool results refer to an earlier assistant tool call
  • assistant tool-call IDs are unique
  • assistant provenance metadata is attached only to assistant messages
  • assistant stop reasons are known

Open-ended JSON maps are intentionally preserved. ProviderMetadata, ToolArguments, and tool schemas may contain provider-specific fields that sigma does not interpret but that may be required to continue a conversation. Opaque provider signatures on thinking, tool-call, and assistant text blocks are also preserved. Empty assistant text is valid only when Signature or ProviderSignature is non-empty, or it anchors Anthropic hosted-result metadata. Other unsigned empty text remains invalid. Persistence does not interpret signatures. Provider replay validates their format and exact provider/API/model provenance before sending them. Responses serializers omit signatures and encrypted content when any provenance component is missing or mismatched, without erasing them from saved blocks. They send a thinking block as a reasoning item only when it carries replayable encrypted content, or when the request sets the store provider option and the block keeps its provider item ID; OpenAI cannot resolve other reasoning items, so they are omitted from the request but kept in saved blocks. Chat Completions applies the same nonempty provenance requirement to both ordered openai_reasoning_details and legacy tool-call reasoning_details. Eligible modern metadata takes precedence; validated legacy metadata is a fallback only for the same source. Chat Completions thinking records the field it streamed in (ProviderMetadata["reasoning_field"]), and same-model replay without reasoning details sends the thinking back in that field. Existing histories remain readable without migration, but histories lacking provenance no longer replay reasoning details. Ordinary text, calls, and results remain available.

Responses request serialization reserves existing native item IDs before allocating distinct synthetic IDs for messages, reasoning, and tool calls. A deterministic request-local mapping preserves call/result associations when wire IDs require normalization, including grammar and deferred tools. Persist and match the original history IDs; normalized wire IDs are not a migration of the conversation.

Provider-authored numbers in tool arguments are json.Number, including values retained from partial or canceled streams. Persistence preserves their exact JSON numeric spelling. See numeric tool arguments for migration and explicit conversion guidance.

Anthropic hosted-tool results use an anthropic_hosted_replay entry inside a preceding block's ProviderMetadata. Preserve the entire content block and its metadata, including ordered results_after entries and source provider/API/model. Compatible replay validates these associations locally; incompatible or missing provenance omits hosted calls and results. Old histories remain readable, but results discarded before this metadata was captured cannot be reconstructed.

Persistence retains failed and aborted messages as saved. At provider dispatch, non-Responses adapters keep only their nonblank visible text and remove their reasoning, tool calls, associated results, and deferred-tool markers. Responses omits the whole failed turn. Neither path changes the stored request. Anthropic also omits blank text after cleaning, including foreign signature-only blocks; valid signed thinking and empty tool results remain serializable.

Appending assistant turns

Synthetic tool-call IDs are opaque and distinct across generated turns. Persist the final assistant IDs and use those exact IDs for matching tool results. Conversation-wide duplicate validation remains in place. Existing histories containing duplicate tool-call IDs are not automatically migrated; applications must repair ambiguous calls and their matching results before persisting them.

AssistantMessage is output metadata. Persist conversation history by converting the final assistant response back into a Message:

history = append(history, sigma.Message{
	Role:                  sigma.RoleAssistant,
	Content:               final.Content,
	Provider:              final.Provider,
	API:                   model.API,
	Model:                 final.Model,
	StopReason:            final.StopReason,
	ProviderThinkingLevel: final.ProviderThinkingLevel,
	Usage:                 final.Usage,
})

For tool loops, append the assistant message first, then append one sigma.ToolResult or sigma.ToolError for each tool call you executed. See Tools.

When a caller-owned tool execution has its own token usage, attach it to the tool-result message before persisting it:

result := sigma.ToolResult(call.ID, output)
result.Usage = &sigma.Usage{
	InputTokens:  toolInputTokens,
	OutputTokens: toolOutputTokens,
	Provider:     toolProvider,
	Model:        toolModel,
}
history = append(history, result)

Tool-result usage is execution-local metadata. Sigma preserves it through persistence and model handoff but does not send it to providers or include it in request-token estimates, model-turn cost accounting, or evaluation usage totals. It is distinct from Usage.ToolUseInputTokens, which records provider-reported tool or connector input tokens on an assistant turn.

For canceled streams, only persist the aborted assistant message if the partial content was visible to the user or needed for a later continue request. See Cancellation.

Persisting assistant usage is optional. When present on the latest successful assistant message, sigma.EstimateRequestTokens uses it as an anchor and only estimates messages that follow it. Tool-result usage is preserved but never used as an anchor. Usage on user and developer messages is rejected by ValidateRequest.

Storage concerns

Persisted requests can become large. Base64 image blocks expand binary data and should usually be stored out-of-band with only a stable reference in your application data, unless the provider requires inline image bytes for replay.

Do not store credentials in persisted requests. API keys, OAuth access tokens, session cookies, and Authorization headers belong in an AuthResolver, request options, or your application's secret store. MarshalRequest only covers conversation context and tool definitions; it does not include client options or credential callbacks.

Apply your own redaction policy before storing user messages, tool results, and provider metadata. Tool outputs often contain secrets from external systems, and provider metadata may include opaque continuation handles or signatures. Those values can be necessary for provider continuity, but they should be treated as sensitive application data.

This package does not implement a database layer and does not encrypt persisted data. Choose storage, retention, redaction, access control, and encryption at the application boundary.