The default assistant is a local recipe router, not a general-purpose model. It matches supported graph requests to the inspectable knowledge base in src/lib/assist/kb.ts, returns an explanation and proposed graph patches, and links to the corresponding recipes section. No provider key or external model is required for that path.

Using the assistant

Open the craft playground and activate Open flow assistant at the lower-right corner. The modal moves focus into its composer. Choose a quick prompt to fill the composer, then press Enter or Send; Shift+Enter inserts a line break.

The three quick prompts are branch on validation then fan out to two sinks, auto layout left to right, and validate my graph. Suggestions for an unknown prompt also fill the composer rather than submitting or editing the graph automatically.

Review Proposed patches before using Apply on an individual item or Apply all on the remaining proposal. Accepted items become Applied; rejected items retain their reason and offer Retry apply. Apply all becomes All accepted only when the reducer accepted every proposed patch. Undo last change and Share graph are separate explicit editor-action buttons and are disabled if the embedding editor did not supply those actions.

Escape or Close flow assistant closes the modal and returns focus to its trigger. Tab and Shift+Tab are constrained within the open modal. These are the authored interaction contracts; the launch gate must still exercise the real browser and keyboard behavior.

A proposal is not a mutation

flowchart LR
    Prompt["User prompt and current graph"] --> Router["Local recipe router"]
    Router --> Preview["Explanation and patch preview"]
    Preview --> Choice["Explicit user Apply action"]
    Choice --> Reducer["Shape and graph invariant checks"]
    Reducer --> Result["Applied patches and visible rejections"]
    Router --> Docs["Canonical recipe documentation"]

A response contains recipeId, title, message, confidence, suggestions, patches, and source. It can include a canonical documentation link and a separate action of undo or share. These editor actions are not hidden graph patches. An unsupported request returns a clarification with three recipe suggestions and no proposed patches rather than pretending to perform an arbitrary task.

Supported knowledge

The canonical recipe identifiers are:

  • Structure: add-node, connect-nodes, validation-branch, fan-out, branch-fan-out, and error-path.
  • Graph inspection and editing: auto-layout, validate-graph, and delete-selection.
  • Editor actions: undo and share.
  • Guided explanation: custom-node, scale, and theming.

Useful exact prompts from the knowledge base:

PromptExpected recipe
add a transform nodeadd-node
branch on validation then fan out to two sinksbranch-fan-out
add an error patherror-path
auto layout left to rightauto-layout
validate my graphvalidate-graph
how do I scale a large graphscale

Open recipes for each prompt’s prerequisites and boundaries. Documentation links use https://wiki.sflw.loca.zone/recipes#<id>; changing a KB identifier requires migrating its heading and callers together.

The scale recipe explains rendering and state boundaries; it does not secretly create thousands of nodes. The theming recipe explains existing theme controls and CSS variables; it does not silently change global settings. The custom-node recipe creates a configurable transform and explains its component/handle contract; it is not an arbitrary component compiler.

Deterministic routing

src/lib/assist/intent.ts normalizes case, punctuation, accents, common word variants, and stop words. It scores explicit aliases and matching keyword tokens, with a specific rule for the combined branch-and-fan-out request. Ties resolve in knowledge-base order. The routing threshold is 0.52; the displayed confidence is a deterministic heuristic score, not a calibrated probability of correctness.

The latest user message chooses the local recipe. Other supplied conversation turns do not create hidden local graph memory. The current graph supplies node identities and selection for proposals. A low-scoring prompt produces recipeId: 'unknown', the top three suggestions, and patches: [].

Patch contract

Every patch has a stable id and human-readable label, plus one of these operations:

OperationPayloadObservable boundary
add_nodenodeAdds a shaped node; conflicting existing kind/title is rejected
add_edgeedgeRequires both endpoints and a valid connection
set_layoutpositions keyed by node IDApplies finite positions only when all named nodes still exist
deletenodeIds, edgeIds, or bothRemoves the specified elements and incident edges; a missing specified element rejects that delete patch
validate_reportreportRecomputes validation for the graph at Apply time instead of trusting an old preview
explaintextReturns explanation text without changing graph structure

applyPatches in src/lib/assist/reducer.ts returns the resulting graph, accepted applied patches, rejected patches with reasons, reports, explanations, and a changed flag. It does not mutate the supplied graph or patch preview.

Application is ordered and per-patch, not an all-or-nothing transaction. A rejected patch does not undo earlier accepted patches, and later independent patches can still apply. Apply node additions before their dependent edges. Repeating an accepted addition does not create a second copy of the same identity/connection. A stale delete or layout preview is rejected when its named elements have disappeared; regenerate a proposal for the current graph.

A shape-valid graph may still have invalid wiring. This distinction lets the validation recipe explain graph errors instead of refusing every imperfect graph before inspection. New connections are checked against the shared graph invariants when applied. See concepts for node roles, handles, edge kinds, and validation rules.

HTTP contract

POST /api/assist accepts JSON shaped as:

{
  "messages": [
    { "role": "user", "content": "add a transform node" }
  ],
  "graph": { "nodes": [], "edges": [] }
}

Messages must contain 1–60 user/assistant turns, each with nonempty content of at most 8,000 JavaScript string code units, and at least one user message. The graph must pass portable-shape validation; structurally invalid wiring can still be inspected by the graph-validation recipe. The response is the direct AssistResponse object, not an additional response envelope. Successful responses carry Cache-Control: no-store.

The route streams a maximum 256 KiB input body and also checks a declared content length. Malformed JSON or invalid request shape returns HTTP 400 with invalid_request; an oversized request handled by the route returns HTTP 413 with request_too_large. The service’s adapter limit is also BODY_SIZE_LIMIT=256K, while nginx permits 1m, so the effective deployed input limit is 256 KiB. A transport-layer rejection can occur before the route produces its JSON error.

The browser gives the request 12 seconds. A failed HTTP response, unreadable assistant response, or browser request failure causes the bubble to run the same local router in-browser and show a local-fallback notice. That fallback still produces a proposal, never an automatic graph mutation.

Optional model boundary

The only assistant environment setting is SFLW_MODEL_URL. If it is unset or blank, the API returns local expertise without an outbound fetch. If it is set, the route first computes the local response and then posts { messages, graph } to that exact operator-configured endpoint.

The endpoint contract is specific:

  • It must be an HTTP or HTTPS URL with no username/password URL credentials. Redirects are rejected, and the request does not include cookies or an Authorization header.
  • It receives the supplied message history and graph. Enabling it is therefore an explicit decision to disclose those contents to that endpoint.
  • It must return a complete AssistResponse as JSON, or put that JSON string in choices[0].message.content. A stock provider endpoint is not automatically compatible: the app does not add a provider model name, API key, system prompt, or provider-specific payload.
  • The request has an eight-second deadline; the streamed response is capped at 256 KiB.
  • The returned shape and patches are checked. Proposed graph patches are rehearsed through the same reducer, and any rejection disqualifies the model proposal. This is not proof that a well-shaped proposal matches the user’s intent.
  • Provider failures, redirects, timeouts, oversized bodies, malformed JSON, invalid response shapes, and rejected patches all fall back to the previously computed local response. The resulting source remains local; accepted model proposals use source: 'model'.

The transcript distinguishes local recipe from optional model responses. Neither path is allowed to apply its own patches silently. Default operation does not require creating the optional /etc/sflw/sflw.env file.

Limits and privacy

The local assistant operates on the prompt and graph supplied by the editor. It is not an execution engine for the nodes: a sink does not send a network request, an error path does not install a production handler, and a displayed transform does not execute arbitrary code. Explanations and diagrams are not evidence that an external workflow ran.

The API handler does not persist conversations or graph bodies, and its provider fallback does not log message bodies, graphs, provider responses, or exception payloads. This is a property of the handler, not a retention promise about an optional external endpoint or host infrastructure.

Do not add credentials, provider configuration, or outbound calls just to enable ordinary recipe assistance. Operational configuration belongs in the optional environment file described by operations, never in the browser bundle or the wiki.

  • recipes for all fourteen canonical recipes.
  • concepts for the graph contract and validation boundaries.
  • demos for the actual showcase surfaces.
  • operations for release gates and optional environment handling.