picoflow.io Docs

Reference

Session document schema

Every field PicoFlow persists: the session envelope, the single flow object, step documents, memory namespaces, the execution sequence, token accounting, and the log arrays.

One session document contains exactly one flow envelope. The shape below is the Zod schema in pf/src/picoflow/types/flow-types.ts, which is the authority — not the abbreviated tree in the developer guide.

Operations use

This schema is deliberately application-readable: an operator can correlate runStatus, flow.currentStep, sequence, token totals, timestamps, and structured diagnostic arrays in one record. For safe production-to-development reproduction and dashboard design, see Operate and debug session documents.

{
  "id": "b3f0…",
  "revision": 7,
  "version": 1.5,
  "runStatus": "running",
  "createdOn": "2026-03-04T05:06:07.000Z",
  "saveOn": "2026-03-04T05:12:41.113Z",
  "flow": {
    "name": "HotelFlow",
    "model": { "provider": "openai", "name": "gpt-4o", "params": {}, "retryAttempts": 3 },
    "context": { "config": { "tenantId": "demo" } },
    "memory": { "hotel-explore": { "messages": [], "summary": "…" } },
    "steps": [{ "name": "ExploreStep", "state": {} }],
    "currentStep": "PresentStep",
    "sequence": [{ "level": 1, "stepName": "ExploreStep" }]
  },
  "tokens": { "inputTokens": 0, "outputTokens": 0, "totalTokens": 0 },
  "log": [],
  "error": [],
  "warn": [],
  "debug": [],
  "verbose": []
}

The envelope

Field Type Meaning
id non-empty string The session ID. A UUID v4 at creation
revision non-negative integer The compare-and-swap token. Incremented by the store on every successful write
version number The document schema version. Currently K.sessionDocVersion, 1.5
runStatus running, completed, aborted Lifecycle state
createdOn Date Creation timestamp
saveOn Date Last successful write; the baseline for any Flow-owned idle policy
flow object The one flow envelope. Never an array
tokens object Provider-neutral token accounting
log, error, warn, debug, verbose object arrays Structured session log entries
revision is not version

revision is the concurrency token the store compares and swaps on. version is the document schema version your migration code branches on. They change for entirely different reasons, and saveSession() stamps version on every write.

The session tree includes the diagnostic arrays and uses flow.currentStep as the durable cursor. There is no separate flow.start field.

Restore policy

Stores deliberately do not apply expiry — load() returns the stored document. Flow.onRestoreSessionDoc() accepts current-version documents by default. A Flow with a business-specific idle rule compares sessionIdleMs(doc) with a code constant and returns null to start a new session.

The flow object

Field Type Meaning
name non-empty string The registered flow name. Permanently bound to this session ID
model { provider, name, params, retryAttempts? } The resolved flow default, with sensitive params stripped
context object Session-wide configuration, seeded from the first request’s config
memory record of namespace to memory document Conversation history per namespace
steps array of step documents One entry per registered step
currentStep string or null The single durable cursor
sequence array of sequence items Ordered step activation history

The one-flow invariant is asserted on load, before every save, and inside nextSessionRevision(). A violation raises SessionFlowInvariantError with code SESSION_FLOW_INVARIANT and statusCode 409. A request that reuses a session ID under a different flowName raises SessionFlowMismatchError (SESSION_FLOW_MISMATCH, 409) before the restore hook can run.

Step documents

type StepType = {
  name: string;
  state: object;
  model?: { provider: string; name: string; params: Record<string, any>; retryAttempts?: number };
};

name is Step.id, which defaults to the class name — renaming the class is a schema migration. state is written with the _transient subtree removed, and carries a _saveOn timestamp stamped by saveState(). Transition-supplied prompts are stored here as _prompt.

model is present only when the step’s resolved selection differs from the flow’s. Logic steps never write one. Restoring a step document whose model has a blank provider throws.

Memory namespaces

type MemoryType = {
  messages: ChatMessageType[];
  summary?: string;
  summarizedThroughId?: string;
};

Namespace keys must match /^[A-Za-z][A-Za-z0-9_-]{0,127}$/ and may not be __proto__, constructor, or prototype. Each persisted message is:

Field Notes
id genMessageId() output — step name, timestamp, and a random suffix joined by pipes
type system, human, ai, or tool
content Flattened to text
timestamp Stamped at write time
name Tool name for tool messages, otherwise empty
tool_call_id Required on tool messages; a missing value fails restore
status success or error, on tool messages
tool_calls { name, args, id } entries on AI messages that requested tools
additional_kwargs Written only when non-empty; carries the direct and stopTool hints

summary and summarizedThroughId appear only for namespaces with compaction enabled. Pre-1.17 array-shaped memory documents are converted to this map by Memory.normalizeDoc().

The execution sequence

type FlowSequenceItemType = {
  level: number;   // integer, minimum 1
  stepName: string;
};

An entry is appended by Flow.goto() and by Flow.enterChild(). level is 1 for the top-level cursor and increments for each nesting depth opened by runStep() or runSteps(), so a trace of nested work stays readable.

Legacy string entries

Older documents stored bare step-name strings in sequence. Flow.bootstrap() normalises each one to { level: 1, stepName } — but only after onRestoreSessionDoc() has run. Migration code that walks the sequence must therefore handle both strings and objects, or leave string entries alone for the normaliser.

Token accounting

type TokenUsageType = {
  inputTokens: number;
  outputTokens: number;
  totalTokens: number;
  reasoningTokens: number;
  visibleOutputTokens: number;
  cachedInputTokens: number;
  cacheCreationInputTokens: number;
};

inputTokens, outputTokens, and totalTokens are the billable totals. The rest are breakdowns of those totals, retained because providers price them differently: reasoningTokens is a subset of output, visibleOutputTokens is output less reasoning, and the two cache fields are subsets of input.

Totals are accumulated by Flow.tallyToken() from both the LangChain-normalised usage_metadata and the provider-only breakdowns in response_metadata.usage. Gemini reports thoughts separately from candidate output, so PicoFlow adds them; OpenAI and Claude already include reasoning in their reported output total. Structured output and some adapters return no usage metadata at all, in which case nothing is added.

Log arrays

SessionLogger appends to log, error, warn, debug, or verbose. Each entry is { timestamp, level, message } merged with any extra JSON supplied at the call site.

The runtime writes here for hallucinated tools, missing tool handlers, model retry attempts, skipped memory compaction, batch progress, and aborted runs.

Date hydration

Stores revive createdOn and saveOn deliberately and normalise revision. They do not walk user data: an ISO-looking string inside flow.context or a step’s state comes back as a string. This is asserted by the store conformance suite. Do not assume dates inside your own state are Date objects after a restore.

See The session document for the conceptual view, Session stores for the persistence contract, and Session document migration for changing this shape safely.