Documentation
¶
Overview ¶
Package common is the memcode PROTOCOL contract: the wire types shared by the CLI (the client) and the api gateway (the server), the sdk/agent ↔ CLI stream-json envelope, the abstract Intent, shared error sentinels, and the model/pricing metadata both ledgers price against.
It is the single source of truth for everything that crosses a memcode wire. Both the cli and api modules redeclared these types independently once, and the drift caused real outages (tool input_schema, lane bypass) — this package exists so that can never happen again.
Invariant: common holds the PROTOCOL only and is stdlib-only (zero third-party dependencies). It contains no prompt doctrine, no provider keys, no routing logic, and no capability interfaces — those live at their consuming boundary (the cli and api each declare the structural interface they need; a client implementation merely satisfies them). A guard test enforces the stdlib-only rule.
Index ¶
- Constants
- Variables
- type AskRequestData
- type AskResponseData
- type AssistantDeltaData
- type Block
- type CacheControl
- type Effort
- type Envelope
- type ErrorData
- type InitializeData
- type InitializedData
- type Intent
- type MediaSource
- type Message
- type PermissionRequestData
- type PermissionResponseData
- type Request
- type Response
- type ResultData
- type SessionStateData
- type StreamHandler
- type ToolCallData
- type ToolDef
- type ToolResultData
- type UsageData
- type UserTurnData
Constants ¶
const ( // client → CLI (control) MsgInitialize = "initialize" // first message; configure the session MsgUserTurn = "user_turn" // submit a user prompt (starts a turn) MsgPermissionResponse = "permission_response" // answer a permission_request MsgAskResponse = "ask_response" // answer an ask_request MsgCancel = "cancel" // interrupt the running turn // CLI → client (events) MsgInitialized = "initialized" // session ready; carries session id MsgAssistantDelta = "assistant_delta" // a chunk of assistant text MsgToolCall = "tool_call" // a tool is running MsgToolResult = "tool_result" // a tool finished (summary) MsgPermissionRequest = "permission_request" // the turn is blocked awaiting approval MsgAskRequest = "ask_request" // the turn is blocked awaiting a user choice MsgSessionState = "session_state" // busy/idle + room/mode telemetry MsgUsage = "usage" // running token counts MsgResult = "result" // a turn finished MsgError = "error" // a turn errored )
Message families. The direction comments are from the wrapper's point of view.
const CodeAccountLocked = "account_locked"
CodeAccountLocked is the gateway's machine-readable error code for an org locked by a negative balance (HTTP 402), mapped to ErrAccountLocked.
const CodeByokKeyFailed = "byok_key_failed"
CodeByokKeyFailed is the gateway's machine-readable error code for a turn killed by the user's own provider key (HTTP 422 body and the SSE error event), mapped to ErrByokKeyFailed.
const CodeContextOverflow = "context_overflow"
CodeContextOverflow is the gateway's machine-readable error code for a context-window overflow — carried on the HTTP 413 body and the SSE error event, mapped to ErrContextOverflow by the client.
const CodeInsufficientCredit = "insufficient_credits"
CodeInsufficientCredit is the gateway's machine-readable error code for an exhausted credit wallet — carried on the HTTP 402 body and the SSE error event, mapped to ErrInsufficientCredit by the client.
const CodeSubscriptionRequired = "subscription_required"
CodeSubscriptionRequired is the gateway's machine-readable error code for an org with no active subscription (HTTP 402), mapped to ErrSubscriptionRequired.
const StreamJSONVersion = "1"
StreamJSONVersion is the protocol version stamped on every Envelope. Bump on a breaking change to the message families or payloads.
Variables ¶
var ErrAccountLocked = errors.New("your balance is negative — hosted models are paused; settle it at memcode.ai/account/billing (your own API keys keep working via /apikeys)")
ErrAccountLocked is returned when the org's balance went negative from a post-hoc debit overrun — the gateway returns 402 with code "account_locked". Everything is refused, BYOK included, until credits are added. Never retried.
var ErrByokKeyFailed = errors.New("your API key was rejected — fix or remove it with /apikeys")
ErrByokKeyFailed is returned when the turn died on the USER's own provider key (fail-the-turn doctrine: a failing BYOK key is never absorbed onto memcode's keys) — the gateway returns 422 with code "byok_key_failed". Never retried: the fix is /apikeys, not another attempt.
var ErrContextOverflow = errors.New("context window overflow")
ErrContextOverflow is returned when a turn's prompt exceeds the served context window on EVERY available backend (the gateway already absorbs vLLM overflow up to Anthropic; this fires only when even Anthropic's window is exceeded). The CLI runtime watches for it with errors.Is and compacts-then-retries the turn rather than surfacing a raw error — the reactive end of the routing ladder. On the wire it is signalled by HTTP 413 with code "context_overflow" and by an SSE error event carrying the same code.
var ErrInsufficientCredit = errors.New("credits exhausted — add your own API keys with /apikeys, or manage your plan and credits at memcode.ai/account/billing")
ErrInsufficientCredit is returned when the org's prepaid credit balance is exhausted — the gateway returns 402 with code "insufficient_credits". The CLI surfaces a friendly message and stops the turn (no retry: it's a user action, not a transient failure). Keys lead the message: they're the fix on every plan, while "add credits" doesn't exist on the BYOK-only Free plan.
var ErrStreamIncomplete = errors.New("gateway stream ended without a response")
ErrStreamIncomplete is returned when an SSE stream ends WITHOUT a terminal response event (a mid-stream read error, or the connection closing early — e.g. a Cloud Run request-timeout cutting off a long model call). It is a transient TRANSPORT failure, not a model/logic error: the failed call appended nothing, so the runtime can retry it from the same history. Watched with errors.Is and retried (bounded) rather than dying mid-turn with work half-done.
var ErrSubscriptionRequired = errors.New("subscription required — choose a plan at memcode.ai/account/billing")
ErrSubscriptionRequired is returned when the org has no active subscription — the gateway returns 402 with code "subscription_required". Subscription is mandatory for every LLM call, BYOK included (2026-07-26). Never retried: choosing a plan is a user action.
ErrUnauthorized is returned when the gateway rejects the bearer token (HTTP 401): expired, revoked, or the retired legacy static token. It means the SESSION is signed out, not that the turn transiently failed — hosts watch with errors.Is, flip to their signed-out state, and prompt for /login instead of surfacing a raw HTTP error. Never retried.
Functions ¶
This section is empty.
Types ¶
type AskRequestData ¶
type AskRequestData struct {
Question string `json:"question"`
Options []string `json:"options,omitempty"`
}
AskRequestData / AskResponseData are the ask_user human-in-the-loop round-trip.
type AskResponseData ¶
type AskResponseData struct {
Answer string `json:"answer"`
}
type AssistantDeltaData ¶
type AssistantDeltaData struct {
Text string `json:"text"`
}
AssistantDeltaData is a chunk of streamed assistant text (CLI → client).
type Block ¶
type Block struct {
Type string `json:"type"` // "text" | "image" | "document" | "tool_use" | "tool_result"
Text string `json:"text,omitempty"`
// image / document
Source *MediaSource `json:"source,omitempty"`
// tool_use
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Input json.RawMessage `json:"input,omitempty"`
// tool_result
ToolUseID string `json:"tool_use_id,omitempty"`
Content string `json:"content,omitempty"`
IsError bool `json:"is_error,omitempty"`
// ContentBlocks carries STRUCTURED content for a tool_result — one or more
// content blocks (text and/or image) instead of the flat Content string. This
// is the path for tool results that include vision (e.g. a browser screenshot
// returned to the model as an image block). When non-empty, providers emit the
// structured content union (Anthropic: text+image parts in the tool_result;
// OpenAI: multi-part tool message). When empty, Content (flat string) is used
// — backwards compatible with every text-only tool result.
ContentBlocks []Block `json:"content_blocks,omitempty"`
// thinking (extended/adaptive reasoning). These MUST round-trip UNMODIFIED: the
// API requires the assistant's thinking blocks to be passed back verbatim on the
// next tool-use turn (it verifies them via Signature) or it rejects the request.
// So we parse them off responses and re-send them; we just don't display them.
// Data carries a redacted_thinking block's opaque payload, if one ever appears.
Thinking string `json:"thinking,omitempty"`
Signature string `json:"signature,omitempty"`
Data string `json:"data,omitempty"`
// prompt caching (set on the wire only, never on shared history — see the gateway)
CacheControl *CacheControl `json:"cache_control,omitempty"`
}
Block is one content block in a message (text, image/document, a tool call, or a tool result, or a thinking block).
func DocumentBlock ¶
DocumentBlock builds a document block from raw file bytes (application/pdf is the interoperable case: Anthropic document source, OpenAI Responses input_file, Gemini inline blob). Models without native document input absorb the turn to a capable tier — the catalog's pdf flag gates that in the router.
func ImageBlock ¶
ImageBlock builds a vision block from raw image bytes.
func ToolResultBlocks ¶
ToolResultBlocks builds a tool_result block carrying structured content blocks (text and/or image) instead of a flat string. This is the path for tool results that include vision — e.g. a browser screenshot returned as an image block. When blocks is empty, falls back to an empty text result.
func (Block) MarshalJSON ¶
MarshalJSON serializes a Block for the wire. Thinking blocks MUST carry the "thinking" field even when its text is EMPTY: adaptive thinking frequently returns a block that is just a signature (the reasoning text omitted), and on the next tool-use turn Anthropic rejects a thinking block without the field — "thinking.thinking: Field required". The struct tag is `omitempty` (correct for every OTHER block type, which must NOT carry a stray empty "thinking"), so we special-case thinking / redacted_ thinking here and let the alias handle the rest with default tags.
type CacheControl ¶
type CacheControl struct {
Type string `json:"type"` // "ephemeral"
// TTL is the cache lifetime: "" (default 5m) or "1h". The long TTL is used for the
// STABLE doctrine+tools prefix, which is byte-identical across turns even as the
// volatile facts (room/personality) change — so it survives interactive gaps >5min.
TTL string `json:"ttl,omitempty"`
}
CacheControl marks a prompt-cache breakpoint. Everything from the start of the prompt up to and including the marked block is cached (5-min ephemeral TTL); later requests sharing that prefix read it instead of re-paying input tokens, and cache reads are excluded from the input-tokens/min rate limit.
type Effort ¶
type Effort string
Effort is an abstract reasoning-depth setting. It exists so call sites express INTENT ("this is a judgment call, think harder") without hardcoding a provider shape that differs by model — e.g. Opus 5 rejects manual budget_tokens and requires adaptive thinking + an effort hint, while Opus 4.5 wants budget_tokens.
type Envelope ¶
type Envelope struct {
Version string `json:"version"`
Type string `json:"type"`
ID string `json:"id,omitempty"` // correlation id (e.g. a permission/ask request↔response)
TurnID string `json:"turn_id,omitempty"` // the turn this message belongs to
Data json.RawMessage `json:"data,omitempty"`
}
Envelope is the single wire frame. Data is a type-specific payload (below).
type ErrorData ¶
type ErrorData struct {
Message string `json:"message"`
}
ErrorData reports a turn-level error (CLI → client).
type InitializeData ¶
type InitializeData struct {
Cwd string `json:"cwd,omitempty"` // working directory / repo root
Mode string `json:"mode,omitempty"` // permission mode: ask | auto | allow-all
Pin string `json:"pin,omitempty"` // the session's model label
}
InitializeData configures the session (client → CLI).
type InitializedData ¶
type InitializedData struct {
SessionID string `json:"session_id"`
Protocol string `json:"protocol"` // echoes the version for handshake validation
}
InitializedData announces a ready session (CLI → client).
type Intent ¶
type Intent struct {
// Purpose is what this turn is FOR (ledger attribution + the utility split):
// "main_loop" | "explore" | "classify" | "compact" | …
Purpose string `json:"purpose,omitempty"`
// Mode names the doctrine the turn runs under ("chat" | "exec" | "plan" | …).
Mode string `json:"mode,omitempty"`
// Reasoning is the abstract reasoning depth the turn warrants (maps to
// Effort). Effort is a genuinely separate axis from model choice and
// survives the ladder: a tricky single-file bug can want deep thinking on
// the same model an easy one uses.
Reasoning Effort `json:"reasoning,omitempty"`
// Interactive is true when a live user is present (vs a batch/background
// job) — available to the policy for latency-vs-cost trade-offs.
Interactive bool `json:"interactive,omitempty"`
// Pin is the session's model: a catalog LABEL ("sol", "sonnet", "glm-5p2",
// …), never a raw provider id — raw ids stay at the serving edge. The pin
// resolver settles it once at session start from the
// session -> workspace -> user -> default_model chain, so it is always set
// for a real session; selection refuses rather than inventing one if it
// isn't.
//
// Every real request serves this model. Internal plumbing
// (classify/compact/shrinkwrap) rides the catalog's utility_model instead,
// and capability gaps fail the turn visibly rather than rerouting it.
Pin string `json:"pin,omitempty"`
}
Intent is the abstract selection contract: the session layer expresses WHAT a turn is, and the CLI's selection policy (internal/llm/resolve.go) maps it to a concrete catalog label. It is a client-internal type — it never rides a wire.
It used to carry the Automatic ladder's inputs (Difficulty, Risk, Vendor), and resolving them into a model was a per-turn decision. That is gone: there is exactly one model per session, the user's pin, and Intent now only says what KIND of turn this is so that internal plumbing can be told apart from the user's own work.
type MediaSource ¶
type MediaSource struct {
Type string `json:"type"` // "base64"
MediaType string `json:"media_type"` // e.g. image/png, application/pdf
Data string `json:"data"`
}
MediaSource carries base64 image/document data for vision blocks.
type Message ¶
type Message struct {
Role string `json:"role"` // "user" | "assistant"
Blocks []Block `json:"content"`
}
Message is one turn (a role plus its content blocks).
type PermissionRequestData ¶
type PermissionRequestData struct {
Title string `json:"title"`
Label string `json:"label,omitempty"`
Detail string `json:"detail,omitempty"`
Command string `json:"command,omitempty"`
Cwd string `json:"cwd,omitempty"`
Risk string `json:"risk,omitempty"`
Editable bool `json:"editable,omitempty"`
}
PermissionRequestData asks the client to approve an action (CLI → client). The client answers with a PermissionResponseData carrying the same Envelope.ID.
type PermissionResponseData ¶
type Request ¶
type Request struct {
Model string `json:"model,omitempty"` // resolved id at the serving edge; clients carry their choice in Pin
System string `json:"system,omitempty"`
Messages []Message `json:"messages"`
// SystemVolatile is the per-turn-variable doctrine suffix (room/personality/
// extra-mile/nudge + the turn-scoped extra) split OUT of System so it sits
// OUTSIDE the cached prefix. The CLI's doctrine composer fills it (directly,
// or via the transport's compose hook from Mode+Facts); adapters place it
// cache-safely — Anthropic as a second, uncached system block, chat/
// completions as a trailing system message, so the stable prefix still
// auto-caches.
SystemVolatile string `json:"system_volatile,omitempty"`
Tools []ToolDef `json:"tools,omitempty"`
MaxTokens int `json:"max_tokens,omitempty"`
// ToolChoice, when set to a tool name, FORCES the model to call that tool — the
// reliable cross-provider path to structured output (Anthropic tool_choice / OpenAI
// tool_choice). Used by the plan reviewer's verdict so the JSON comes back in a tool_use
// block instead of best-effort prose-JSON. "" = the model chooses (the common case).
ToolChoice string `json:"tool_choice,omitempty"`
// Mode + Facts select a doctrine prompt: the transport's compose hook
// (cli/internal/doctrine) renders the mode's doctrine with these gathered
// facts (root, platform, shell, overview/pack, room, nudge) into System/
// SystemVolatile just before encoding. Mode "" = raw transport (no
// composition; System passes through as-is).
Mode string `json:"mode,omitempty"`
Facts map[string]string `json:"facts,omitempty"`
// Effort is the ABSTRACT reasoning-depth knob. The provider maps it to whatever
// thinking control the target model actually supports (adaptive+effort, manual
// budget_tokens, or nothing). Zero value (EffortOff) means no extended thinking.
Effort Effort `json:"effort,omitempty"`
// Purpose + Session are transport-local labels (json:"-": never marshaled on
// this type). Purpose labels the call for the client ledger and the lane
// leak-log. Session rides the compat wire as the standard `user` field for
// serving affinity (prefix-cache locality) and gateway telemetry. Stamped by
// the CLI's llm.Runner; never set by hand.
Purpose string `json:"-"`
Session string `json:"-"`
// Pin is the CLI-side carrier for the session's model choice: the resolved
// catalog LABEL the transport puts in the wire `model` field. Under
// all-policy-client-side the CLI's selection policy stamps it on EVERY
// call (Automatic is client behavior); "" is only ever seen by test fakes.
Pin string `json:"-"`
// BillingLane is the requested billing lane: "" / "byok_preferred" |
// "byok_only" | "credits". Client-side it is set by policy (the consented
// credits retry after a BYOK key failure) and rides the wire as the
// memcode_billing extension; server-side the compat handler re-stamps it
// from that extension and the gateway ENFORCES it — it never silently
// reroutes between the user's keys and credits. NOT marshaled on this type.
BillingLane string `json:"-"`
// LaneBypass forces a turn OFF its family lane after an explicit,
// consented exhaustion choice: "gateway" serves it on the hosted base.
// Client-side routing state only — never serialized to any wire.
LaneBypass string `json:"-"`
}
Request is a model completion request — the provider-neutral shape every adapter encodes from. Model selection is the CLI's job (llm/lane.go + llm/resolve.go): the Runner's policy resolves a catalog label into Pin, and the transport writes it into the wire `model` field. Server-side, Model carries the gateway's raw provider id after the label gate resolves it.
type Response ¶
type Response struct {
StopReason string `json:"stop_reason"` // "end_turn" | "tool_use" | "max_tokens"
Blocks []Block `json:"blocks"` // assistant content (text and/or tool_use)
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
// prompt-cache telemetry (tokens written to / read from the cache this call)
CacheWriteTokens int `json:"cache_write_tokens,omitempty"`
CacheReadTokens int `json:"cache_read_tokens,omitempty"`
// SearchCount is the number of native server-side web searches the serving
// vendor billed on this call (Anthropic usage.server_tool_use.web_search_requests;
// OpenAI/Grok Responses web_search_call output items). Searches carry a
// PER-REQUEST fee on top of tokens — the gateway prices it via
// common.SearchFeeUSD(Backend, SearchCount) when it emits cost_usd.
SearchCount int `json:"search_count,omitempty"`
// ToolOrigin records how a tool call was obtained: "structured" (the model's native
// tool_calls — the intended path) or "salvaged_*" (the gateway recovered it from
// text a brittle parser missed). Surfaced so salvage stays OBSERVABLE, never silently
// the main contract.
ToolOrigin string `json:"tool_origin,omitempty"`
// Serving telemetry — the ledger's ground truth for WHO served this call.
// Model is the model that actually ran (a fallback-chain hop can differ from
// the requested label). Backend is the serving vendor: "cheap" (the hosted
// cheap lane, vendor-neutral — the inference vendor never rides the wire) |
// "anthropic" | "openai" | "gemini" | "grok" | "unknown" (label missing from
// the client catalog); "vllm" is the legacy cheap-lane tag an un-redeployed
// gateway sends (clients accept both). FallbackReason names why the served
// model differs from the primary choice: a client-side absorb ("vision" |
// "pdf" | "window") or a recovery hop ("model_error: …").
Model string `json:"model,omitempty"`
Backend string `json:"backend,omitempty"`
FallbackReason string `json:"fallback_reason,omitempty"`
// RequestedModel is the label the selection policy chose BEFORE any absorb/
// fallback — the basis for the ledger's counterfactual "what the primary
// would have cost". The CLI's finalize stamps it (the gateway echoes the
// requested id on hosted turns); it equals Model when nothing intervened.
RequestedModel string `json:"requested_model,omitempty"`
// ContextWindow is the serving backend's real input window in tokens, when
// the backend reports one (0 when the client's catalog already knows it).
ContextWindow int `json:"context_window,omitempty"`
// InputBudget is the serving lane's usable INPUT budget (window − output reserve −
// safety margin) — the size a prompt must fit under to land on THIS lane. The CLI
// learns the cheap lane's budget from this and aims compaction at it. 0 when not
// lane-served.
InputBudget int `json:"input_budget,omitempty"`
// Pool is the cheap lane's served-model short label ("glm-5p2" | "kimi-k3"),
// the CLI's ⇄ ServedBy tag for lane serves. EstimatedPromptTokens is the
// server's pre-call size estimate (stamped per serve so the usage log's
// EstimateRatio can calibrate the estimator against real InputTokens).
Pool string `json:"pool,omitempty"`
EstimatedPromptTokens int `json:"estimated_prompt_tokens,omitempty"`
// BYOK telemetry. BYOK (whether this call ran on the user's OWN provider
// key) rides the wire — it is the key owner's own information, shown by the
// CLI footer strictly per-turn. BYOKVendor stays SERVER-INTERNAL (json:"-"):
// the cheap lane's inference vendor never leaves the server (SanitizeResponse
// zeroes it defensively). The gateway reads both for metering (usage line +
// zero-debit policy) before sanitizing.
BYOK bool `json:"byok,omitempty"`
BYOKVendor string `json:"-"`
}
Response is a model completion result, plus the serving telemetry the footer/ ledger read. On the hosted backend the gateway stamps the serving facts (via the memcode response extension); in direct-endpoint mode the CLI's recovery policy stamps them itself (finalize) — same fields, one reader.
type ResultData ¶
type ResultData struct {
Text string `json:"text,omitempty"` // final assistant text, if captured
Completed bool `json:"completed"`
}
ResultData ends a turn (CLI → client).
type SessionStateData ¶
type SessionStateData struct {
Busy bool `json:"busy"`
Mode string `json:"mode,omitempty"` // room mode (normal/repair/replan), when known
}
SessionStateData reports busy/idle + light room telemetry (CLI → client).
type StreamHandler ¶
StreamHandler receives incremental events during a streamed completion. Both callbacks are optional. Text fires for each chunk of assistant text as it arrives; Usage fires when the API reports token counts (input at the start, authoritative output near the end — NOT per delta, so a live "tokens so far" display must estimate from streamed text between Usage calls and reconcile when Usage fires).
This is a data type (a struct of callbacks), part of the wire contract's shape. The capability INTERFACES (ModelProvider, Streamer, WebSearcher, WebFetcher, Advisor) are intentionally NOT declared here — each consumer declares the structural interface it needs, referencing these common types; an implementation merely satisfies it.
type ToolCallData ¶
type ToolCallData struct {
Name string `json:"name"`
Target string `json:"target,omitempty"`
Detail string `json:"detail,omitempty"`
}
ToolCallData / ToolResultData report tool activity (CLI → client).
type ToolDef ¶
type ToolDef struct {
Name string `json:"name"`
Description string `json:"description"`
InputSchema map[string]any `json:"input_schema"`
// prompt caching — set on the LAST tool only, on the wire (see the gateway)
CacheControl *CacheControl `json:"cache_control,omitempty"`
}
ToolDef describes a tool the model may call.
type ToolResultData ¶
type UsageData ¶
type UsageData struct {
OutputTokens int `json:"output_tokens"`
}
UsageData reports running token counts (CLI → client).
type UserTurnData ¶
type UserTurnData struct {
Text string `json:"text"`
}
UserTurnData submits a prompt (client → CLI).