picoflow.io Docs

Compare

Reliability and production gaps

A production decision should compare failure semantics, races, configuration collisions, growth limits, and external side effects—not only the successful transcript.

Neither hotel implementation should be treated as production-ready merely because its happy path works. Their risks are different: PicoFlow centralizes failure policy but the demo’s domain handlers are permissive; the direct graph validates the domain more carefully but owns a thin session runtime without conflict protection or operational records.

Session races

PicoFlow protects one session in two layers. FlowSession.withSessionLock() serializes turns inside one engine process. Every bundled store also compares the document’s revision before a write, protecting against stale writers in other processes. A loser receives a conflict rather than silently replacing newer state.

The direct graph performs this sequence:

store.get(id) -> hydrate -> graph.invoke -> store.set(document)

Memory uses Map.set, SQLite uses an unconditional upsert, and MongoDB uses unconditional replaceOne(..., { upsert: true }). There is no lock, version predicate, or retry. Two turns on one session can both read state N and then write N+1; the later write wins and the other user turn disappears.

Fix this before horizontal scaling or browser retry concurrency. Add an expected revision to the document and make the storage update conditional, or adopt a LangGraph checkpointer whose concurrency semantics match the deployment.

Configuration collisions in the combined demo

The two implementations share environment variable names but not always units or meaning:

Setting PicoFlow Direct graph
SESSION_STORE MEMORY, SQLITE, MONGO, or COSMO memory, sqlite, or mongodb
SQLite path SQLITE_PATH SQLITE_DB_PATH
Mongo settings MONGODB_URL, name, collection The same three names

PicoFlow Flow classes own their idle policy in onRestoreSessionDoc() rather than reading a shared expiry environment variable. The direct hotel graph also uses a code default (50 seconds). Document these independent policies at their respective endpoints rather than trying to control both with one variable.

If both select the same Mongo collection, framework session documents and graph-specific documents are mixed in one collection. Their random IDs make direct collision unlikely, but queries, retention rules, schema validation, and incident tooling now span two incompatible document shapes. Separate collections are cleaner.

Error semantics and observability

PicoFlow’s engine records structured errors in the session and attempts to persist an aborted status. It also carries logs, warnings, debug records, token totals, and model metadata. One known API problem remains: the failure envelope currently sets completed: true, so callers must branch on success, not completed alone.

The direct graph consistently returns completed: false on errors, but maps validation, provider, graph, and persistence failures to HTTP 400. It does not save the error, a failure status, token usage, the active failing node, or a stack-safe diagnostic identifier. A bad user request and a database outage are operationally indistinguishable at the HTTP status level.

That gap must be closed before production. LangSmith is the most integrated LangGraph option; without it, the team needs to add structured node/model/tool tracing, correlation IDs, durable failure records, metrics, redaction, retention, and an incident-query surface. PicoFlow’s session document supplies much of the diagnostic record by default, although platform-level metrics and distributed traces may still be appropriate around it.

Unbounded histories

PicoFlow enables compaction only for hotel-explore, keeping recent messages and a summary. PresentStep and CompareStep erase their isolated memory when entered from another step.

The direct graph appends to exploreMessages, presentMessages, and compareMessages forever. Re-entering a phase adds synthetic context but does not clear old messages. Long sessions grow the stored document and prompt, increase cost, and may eventually exceed model or database limits. Add phase-aware trimming or summarization before calling this production durable memory.

External effects

Both demos generate a confirmation number but do not call a booking system. In a real flow, booking is an irreversible or externally visible effect. Neither a PicoFlow compare-and-swap nor a LangGraph checkpoint can undo a reservation after a later session write fails.

Use an idempotency key tied to session and operation, write an intent/outbox record before the external call, and make retries return the original booking result. This requirement is framework-independent.

Other concrete gaps

  • The PicoFlow pricing helper has an impossible guard, !basePrices && basePrices.length > 0, and its chart helper pads all values with one column width. The direct copies fix/refactor these, which is why total helper line counts are not a clean framework comparison.
  • Both pricing engines reuse a 2025 holiday fixture by month and day for later years. Floating holidays can land on the wrong date in 2027.
  • PicoFlow stores the chosen hotel but not the generated confirmation number. The direct graph stores both, making support investigation easier.
  • The direct in-memory store returns stored objects without cloning. Treat it as a test adapter, not an isolation boundary.
  • Neither implementation demonstrates cancellation, per-tool timeouts, rate limits, PII redaction, authorization, or a production booking transaction.

The fair conclusion is mixed: the direct implementation is safer at its domain boundary; PicoFlow is safer at its shared session boundary. A production implementation should combine both strengths.