State Portability¶
Treat OpenAI, Anthropic, Google, Microsoft Foundry, and local model servers as execution backends, not as owners of state.
State portability is the architectural principle that makes the Portability pillar possible. The control plane owns conversation, memory, tool, and runtime state. Provider adapters render that state into each vendor's request format. The persisted state is always Joch-native.
Layers of state¶
A mid-conversation provider switch must preserve five distinct layers, not just the chat transcript:
1. Message history what was said
2. Tool-call history what was done
3. Working memory scratchpad / task state / active goals
4. Long-term memory vector / RAG / preferences / project knowledge
5. Runtime state current step / retries / pending approvals / locks
Migrating only chat messages destroys the other four layers. Joch keeps all five durable.
Canonical message format¶
The internal format is intentionally simple:
type AgentMessage = {
role: "system" | "user" | "assistant" | "tool";
content: ContentBlock[];
toolCalls?: ToolCall[];
toolResults?: ToolResult[];
vendorMetadata?: Record<string, unknown>;
};
type ContentBlock =
| { type: "text"; text: string }
| { type: "image"; uri: string; mimeType: string }
| { type: "file"; uri: string; mimeType: string }
| { type: "reasoning_summary"; text: string };
type ToolCall = {
id: string;
name: string;
args: Record<string, unknown>;
};
type ToolResult = {
callId: string;
status: "success" | "error";
content: ContentBlock[];
};
vendorMetadata is preserved but never depended on:
vendorMetadata:
openai:
response_id: resp_abc
model: gpt-5.5-thinking
anthropic:
message_id: msg_xyz
This preserves debug fidelity without coupling the canonical state to a vendor.
Tool-call state¶
Tool calls are the single biggest hazard during a switch. Joch persists every tool call as an event:
event: tool.call.requested
tool: github.search_issues
args: { repo: "org/joch", query: "memory migration" }
event: tool.call.completed
result_ref: artifact://tool-results/abc123
status: success
side_effects: read_only
When a switch happens, the new provider does not replay tool calls. It receives a compact summary:
Earlier in the task, the agent called github.search_issues with query X.
The result was stored as artifact://tool-results/abc123.
Side-effecting calls are flagged so the new provider cannot accidentally repeat them:
toolCall:
id: call_123
idempotencyKey: zendesk-create-12345
sideEffects:
level: external_write
idempotent: false
Migration checkpoint¶
A provider switch creates a deterministic StateCheckpoint:
apiVersion: joch.dev/v1alpha1
kind: StateCheckpoint
metadata:
name: chk-456
spec:
conversationRef: { name: conv-123 }
fromBackend: openai:gpt-5-thinking
toBackend: anthropic:claude-sonnet
summary:
userGoal: "Design state persistence for Joch"
currentTask: "Explain migration strategy"
decisions:
- "Use vendor-neutral state as the source of truth"
- "Persist tool calls as event log"
- "Avoid replaying side-effectful tools"
openQuestions:
- "How much vendor metadata to retain"
activeArtifacts:
- artifact://design/state-persistence-draft
relevantMemoryRefs:
- mem://project/joch/state-model
The new backend then receives:
system / personality / policy config
+ migration checkpoint
+ recent message window
+ relevant memory snippets
+ tool registry
Not the entire raw transcript.
Tiered context reconstruction¶
Different providers have different context window sizes, tokenizers, and modality support. Joch reconstructs context in tiers:
1. Always include system / policy / personality config
2. Include the migration checkpoint
3. Include the active task state
4. Include the last N turns
5. Retrieve relevant long-term memory
6. Attach artifacts by reference
7. Include older transcript only if budget allows and needed
This gives the target model continuity without forcing a brittle one-to-one transcript transfer.
Capability validation¶
Provider switches are not always safe. The target backend may lack vision, structured output, computer use, long context, JSON-strict mode, or specific tool-calling semantics. Joch validates capabilities before committing:
Compatibility check:
✓ Text conversation supported
✓ MCP tools supported
✓ 200k context sufficient
⚠ JSON schema strict mode differs (loose-parse fallback)
⚠ Previous turn used image input; image format will be transformed
✗ Computer-use tool unavailable; tool will be removed for this conversation
Policy decides whether warnings or failures gate the switch. See Model Router.
Storage model¶
Conversation state is event-sourced:
ConversationEvents append-only message + tool events
StateCheckpoints materialized summaries used by migrations
MemoryReferences pointers to memory store items
ArtifactReferences pointers to artifact store items
ProviderMetadata vendor-specific debug data, never source-of-truth
A simplified relational schema:
conversations (
id,
agent_id,
current_backend,
created_at
);
conversation_events (
id,
conversation_id,
sequence_number,
event_type,
payload_json,
provider,
model,
created_at
);
state_checkpoints (
id,
conversation_id,
summary,
active_goals_json,
memory_refs_json,
artifact_refs_json,
created_at
);
tool_events (
id,
conversation_id,
tool_name,
call_id,
args_json,
result_ref,
idempotency_key,
side_effect_level,
created_at
);
Event sourcing lets Joch reconstruct state for any backend and audit exactly what happened.
The central abstraction¶
interface AgentBackendAdapter {
renderPrompt(state: CanonicalAgentState): ProviderRequest;
parseResponse(response: ProviderResponse): CanonicalAgentEvent[];
supports(capability: Capability): boolean;
}
OpenAI, Anthropic, Google, Microsoft, and local models become adapters. The persisted state is always Joch-native, never OpenAI-native, Claude-native, or LangGraph-native.
The largest design rule¶
Never migrate state by asking one model to summarize itself without validation.
It is tempting and dangerous. The correct flow is:
1. Deterministic extraction from the event log
2. Optional model-generated summary
3. Schema validation
4. Auditable checkpoint
5. Target-provider reconstruction
The model can help compress state. The control plane owns the truth.