Deterministic Write-Safety For Hosted App Flow Conditions
Why the hosted app gates writeModule with evaluateFlowCondition while local no-runtime skills remain agent-interpreted
Decision
The hosted app uses a narrow deterministic helper, evaluateFlowCondition, to gate writeModule writes for modules with module.flow.condition.
This is not a full interview-flow resolver. The agent still decides turn order, explanation, prerequisite-asking, tone, and whether the user's message is asking to fill data or only to clarify the selected document, module, or block. The deterministic helper answers one question only: given the current saved document data envelope, is the target module applicable for a writeModule operation?
Plan: Agent Interview Progress.
Context
The schema already defines flow.condition on modules, fields, and blocks. Conditions are authored as DSL strings and may reference data across modules, including shared facts on data_pool:
data_pool.data.has_minor_children = TRUE()The hosted app is multi-turn and persistent. If the user selects a conditional module or block and asks the agent to fill it, an LLM-only interpretation of that condition is not safe enough for the write path. For example, a guardianship module should not be saved until saved data establishes that the will maker has minor children.
The target local model remains no-runtime execution: the end user can run a document skill with Claude Code, Codex, or another local agent without Node, Python, Convex, or an executable resolver. In that venue, the agent reads authored module files, dataSchema, flow.condition, module guidance, and final-review guidance directly.
Helper Contract
evaluateFlowCondition is pure and unit-testable:
type DocumentDataEnvelope = Record<
string,
{
data: Record<string, unknown>;
interviewStatus: "not_started" | "in_progress" | "complete" | "skipped";
}
>;
evaluateFlowCondition(
condition: string,
envelope: DocumentDataEnvelope,
): { applicable: boolean | "unknown"; missingPrereqs: string[] };The envelope is keyed by module handle so cross-module paths such as data_pool.data.has_minor_children resolve without guessing. Unknown tokens, malformed expressions, and unresolved paths return applicable: "unknown" with missingPrereqs. They never return true.
Initial evaluator scope covers the authored grammar currently needed by app write-safety: equality, boolean literals, dotted paths, and TRUE() / FALSE().
Runtime Use
The app evaluates write-safety in two places:
getInterviewContext({ selection })resolvesactiveModule, selected document/module/block focus,nextActionableModule, and ordered module summaries. For a selected module or selected block's parent module, it injects both rawflow.conditionand{ applicable, missingPrereqs }into the agent turn.- The
writeModuletool handler rebuilds the fresh document data envelope and re-runsevaluateFlowConditionat write time for the explicittargetModuleHandle.
The second gate is authoritative. Prompt instructions tell the agent not to write when applicable !== true, but prompt compliance is not the safety boundary. The writeModule handler rejects unsafe writes with a typed write-blocked result the agent can explain to the user.
What Stays Agent-Led
The helper does not decide:
- which module the agent should ask about next;
- whether the user's message is a question, a fill request, a change request, or a skip request;
- how to explain the raw condition in plain language;
- what prerequisite question to ask;
- whether a clearly non-applicable target module should be skipped through
writeModule.
The agent reasons over scalar document order, raw flow.condition, saved data, module status, selected focus, and nextActionableModule. The evaluator only removes non-determinism from hosted persistence.
What This Does Not Change
- No new schema field is introduced for flow resolution.
- No
agentCursor,flowMode,completedBlocks, or document-level interview cursor is introduced. - Field-level and block-level condition evaluation remain separate concerns unless a future plan expands deterministic evaluation beyond selected-module write-safety.
- Local no-runtime skill behavior remains agent-interpreted. The authored DSL and module guidance are the source of truth locally; the hosted app adds a persistence-floor guard where Convex is available.
Revisit When
- App writes need to evaluate the broader expression grammar beyond the initial equality/boolean/path subset.
- Field-level conditions need deterministic server-side gating before asking or saving individual fields.
- The authoring compiler and runtime evaluator need to share one parser package so author-time validation and runtime evaluation cannot drift.
The Three Selection Cells Are Not Duplicates
Why canvas selection is not collapsed to one owner — each cell holds a different thing, and emitSelection resolves rather than translates
One Share Dialog on Every Page
Owner rulings 2026-10-03 — a Project is assigned Libraries from its Workspace's list, starts with none, and sharing it shares only those. Every page's Share button opens the same dialog, Share | Library | Download, and each tab offers what fits the page. A Library's Library tab is Manage for its owner and Join for anyone else; a Workspace's or Project's lists the Libraries Goldie searches there. Download is Word, PDF, JSON or YAML on a Document, and the whole folio as one package on a Workspace, Project or owned Library. Replaces the Share-only dialog on Workspace, Project and Library pages (2026-10-01).