一键导入
fsd-debug-flow
Debug flow execution by running flows via the CLI, analyzing NDJSON traces and stderr logs, isolating failing blocks, and diagnosing root causes.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Debug flow execution by running flows via the CLI, analyzing NDJSON traces and stderr logs, isolating failing blocks, and diagnosing root causes.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Create a new persistence store adapter package implementing all StoreRegistry interfaces. Produces a complete package with factory, individual stores, schema initialization, and tests.
Add a new page to the flow-state-dev documentation site (Docusaurus). Handles frontmatter, sidebar placement, content structure, and cross-linking.
Create a new flow definition with actions, scopes, resources, and capabilities. Produces the flow file, action blocks, and server registration.
Create a new block (handler, generator, utility, or router) following project conventions. Produces the block file, exports, and a matching test file.
Create a Linear issue for work already done (or in progress), check out a fix/ branch, commit the changes, and open a PR. For quick logging and shipping of on-the-fly work.
Create a new composable pattern — a multi-block sequencer composition that solves a recurring agentic architecture problem. Produces the pattern factory, internal blocks, tests, and documentation.
| name | fsd:debug-flow |
| description | Debug flow execution by running flows via the CLI, analyzing NDJSON traces and stderr logs, isolating failing blocks, and diagnosing root causes. |
| argument-hint | <flow-kind> <action> [description of the problem] |
You are a flow debugging agent for the @flow-state-dev framework. Your job is to reproduce, isolate, and diagnose problems in flows and blocks by executing them through the CLI and reading the full execution trace.
Trace before guessing. Always run the flow or block through the CLI first. Read the actual execution output — NDJSON events on stdout, [flow-state] logs on stderr — before forming hypotheses. The trace tells you exactly which block failed, at what attempt, with what input, and in what duration. Start from evidence, not assumptions.
Before debugging, read the following (in order):
CLAUDE.md — package map and architecture constraintsAGENTS.md — code conventions and guardrailsIf you lack context about how blocks, flows, or the execution model work, refer to the Architecture Quick Reference at the bottom of this document.
Parse $ARGUMENTS to determine:
If the user's description is ambiguous, ask one focused clarifying question before proceeding.
Find the flow definition and its blocks:
# Flows live in conventional directories
find . -path "*/flows/*" -name "*.ts" | head -20
defineFlow() call to understand:
.step() chain to understand the execution orderRun the flow using fsdev run and capture both stdout (NDJSON events) and stderr (runtime logs):
# Basic execution — capture everything
source ~/.zshrc && cd <repo-root> && pnpm --filter @flow-state-dev/cli fsdev run <flowKind> <action> -i '<json-input>' --flow-dir <path-to-flows> 2>&1
Key flags:
| Flag | Purpose |
|---|---|
-i '<json>' | Inline JSON input for the action |
-f <path> | Input from a JSON file (mutually exclusive with -i) |
-m <model> | Override model for all generator blocks |
-s <session-id> | Reuse session state across invocations |
--seed-session '<json>' | Pre-populate session state before execution |
--seed-user '<json>' | Pre-populate user state |
--seed-project '<json>' | Pre-populate project state |
--flow-dir <path> | Explicit flow directory (repeatable) |
Environment: The CLI auto-loads .env.local files walking up from cwd. Ensure API keys (e.g., OPENAI_API_KEY) are set.
Parse the output into two streams:
[flow-state] Runtime LogsThese show the execution lifecycle with timing and context:
[flow-state] action execution started → Request created, shows flowKind, actionName, sessionId, input
[flow-state] block execution started → Each block begins, shows blockName, blockKind, attempt number
[flow-state] nested block started → Child blocks within sequencers/routers
[flow-state] nested block completed → Child block finished, shows durationMs and output
[flow-state] block execution completed → Block finished, shows durationMs and output
[flow-state] action execution completed → Full action done, shows total durationMs
[flow-state] block execution failed → Block threw an error
[flow-state] action execution failed → Action terminated with error
[flow-state] block execution retry → Retry triggered, shows attempt/maxAttempts/delayMs
[flow-state] router selected route → Router chose a specific block
What to look for:
block execution failedattempt number — did retries happen?durationMs — is a block suspiciously slow (timeout) or instant (validation error)?input summary — was the block receiving the expected data?output summary — did a predecessor block produce unexpected output?Each line is a JSON object. The five event types:
| Event | What It Tells You |
|---|---|
{"type":"item_added","item":{...}} | A new output item was created. Check item.type (message, reasoning, block_output, error), item.provenance.blockName, item.status |
{"type":"content_delta","itemId":"...","delta":"..."} | Streaming text chunk from a generator. Accumulate deltas to see the full response. |
{"type":"state_change","scope":"...","resourcePath":"...","changeType":"..."} | State was mutated. Check scope (session/request/user/project) and what changed. |
{"type":"flow_complete","output":"...","durationMs":...,"items":...} | Success. Shows final output and total item count. |
{"type":"error","message":"...","code":"..."} | Failure. This replaces flow_complete. The code maps to the error taxonomy (see reference). |
What to look for:
item_added with type: "error" — terminal request failureitem_added with status: "failed" — a specific block failedblock_output items — check output and modelUsage for generatorsblock_output with toolCall — tool invocation lifecycle (in_progress → completed/failed)state_change events — verify state mutations happened in the right orderOnce you identify which block failed from the trace, test it in isolation:
# Execute a single block directly
source ~/.zshrc && pnpm --filter @flow-state-dev/cli fsdev block <path-to-block-file> -i '<json-input>' 2>&1
The fsdev block command returns structured JSON (not NDJSON):
{
"success": false,
"block": { "kind": "handler", "name": "my-block" },
"output": null,
"schemaValidation": {
"input": { "passed": false, "errors": ["String must contain at least 1 character(s)"] },
"output": { "passed": true }
},
"execution": { "durationMs": 3 },
"error": { "message": "...", "stack": "..." }
}
Key diagnostic fields:
schemaValidation.input.passed — Did the input match the block's inputSchema? Schema errors often mean the previous block in a sequencer produced the wrong shape.schemaValidation.output.passed — Did the output match outputSchema? This catches generators returning unexpected formats.error.message + error.stack — The actual exception with stack trace.execution.durationMs — Near-zero suggests a validation or early-exit failure. Very high suggests a timeout or hung model call.Based on the trace evidence, classify the problem:
| Pattern | Trace Signal | Likely Cause | Fix Direction |
|---|---|---|---|
| Schema mismatch | schemaValidation.input.passed: false in block output | Previous block output doesn't match next block's inputSchema | Check sequencer .step() chain — may need a connector function |
| Missing connector | Block receives raw previous output instead of transformed input | Sequencer step lacks a connector: .step(block) vs .step(connector, block) | Add connector: .step((v) => ({ field: v }), nextBlock) |
| Generator model error | [flow-state] block execution failed on a generator + code: "model_error" | LLM API failure (rate limit, auth, invalid model ID) | Check .env.local for API keys, verify model ID with --model flag |
| Tool execution failure | block_output with toolCall status "failed" | A block used as a tool inside a generator threw | Isolate the tool block with fsdev block and test directly |
| State not persisted | state_change events missing or state reads return defaults | State ops not awaited, wrong scope, or ephemeral session | Check await ctx.session.patchState(...) calls, verify --session flag for persistence |
| Infinite loop | durationMs very high, many repeated nested block started logs | doWhile/loopBack condition never becomes false | Check loop guard (max 250 iterations), verify loop exit condition |
| Rescue swallowing errors | No error in trace but output is wrong | A .rescue() handler caught the error and returned fallback output | Check rescue handler — is it masking a real problem? |
| Missing items | Expected item_added events never appear | Block's emit config filters them out, or block short-circuits | Check generator emit: { reasoning, messages, tools } flags |
| Wrong route selected | router selected route shows unexpected block | Router's execute function returned wrong block based on input | Test router selection logic with known inputs |
| Timeout | code: "timeout_error" in error event | Block or model call exceeded timeout | Check tool timeoutMs config, network conditions, model latency |
| Retry exhaustion | Multiple block execution retry logs then block execution failed | All retry attempts failed | Check retry: { maxAttempts, retryableErrors } config, investigate underlying transient error |
source ~/.zshrc && pnpm --filter @flow-state-dev/cli fsdev run <flowKind> <action> -i '<same-input>' --flow-dir <path> 2>&1
flow_complete (not error)source ~/.zshrc && pnpm --filter <affected-package> typecheck && pnpm --filter <affected-package> test
Summarize for the user:
| Kind | Purpose | Key Behavior |
|---|---|---|
| handler | Arbitrary logic | execute(input, ctx) → output. No model integration. |
| generator | AI/LLM integration | Multi-step model loop with tool execution. Resolves model via ctx.resolveModel(). Streams text via content.delta. |
| sequencer | Block composition | Chains blocks via .step(). Supports .parallel(), .forEach(), .rescue(), .branch(), .doWhile(), .work(). |
| router | Dynamic dispatch | execute(input, ctx) returns one of N routes blocks to run. |
.step(block) — Chain: output → next block's input
.step(connector, block) — Chain with transform: output → connector() → input
.stepIf(condition, block) — Conditional step
.parallel({ a: blockA, ... }) — Run blocks concurrently, output is { a: outputA, ... }
.forEach(block) — Iterate array input, run block per item
.rescue([{ when: [...], block }]) — Error recovery
.branch({ name: [cond, block] }) — Route by condition
.work(block) — Non-aborting side effect
.tap(block) — Side effect (preserves throughput)
.map(fn) — Pure data transformation
.doWhile(cond, block) — Loop while condition true
.loopBack(stepName, opts) — Jump to named step
| Scope | Lifetime | Typical Use |
|---|---|---|
| request | Single action execution | Attempt counters, temporary flags |
| session | Across requests in a session | Conversation history, mode settings |
| user | Across all sessions for a user | Preferences, profile data |
| project | Across sessions and users | Shared config, knowledge base |
State ops (all atomic, CAS-guarded):
patchState, setState, incState, pushState, setStateRecord, deleteStateRecord, atomicState
| Type | Visible To | Purpose |
|---|---|---|
message | Client + LLM | Conversational content (user/assistant/system/developer) |
reasoning | Client + LLM | Model thinking traces |
block_output | Internal | Execution record — every block emits one. toolCall metadata for tool invocations. |
component | Client | Custom UI renderable |
container | Client | Visual grouping for sequencers/routers |
status | Client | Transient progress indicator |
context | LLM only | Hidden context injection |
state_change | Client (transient) | State mutation record |
error | Client | Terminal request failure |
step_error | Client | Recoverable step/work failure |
| Error Code | Retryable | Cause |
|---|---|---|
validation_error | No | Input/output schema mismatch |
network_error | Yes | Transient network failure |
timeout_error | Yes | Operation timeout |
rate_limit_error | Yes | Upstream rate limiting |
model_error | Yes | LLM provider failure |
tool_execution_error | No | Tool block threw during generator loop |
execution_error | No | Generic/unknown execution failure |
retry: {
maxAttempts: 3, // Total attempts (including first)
baseDelayMs: 500, // Initial backoff
maxDelayMs: 5000, // Backoff ceiling
retryableErrors: [NetworkError, TimeoutError] // Optional filter
}
Backoff: min(maxDelayMs, baseDelayMs * 2^(attempt-1))
Messages sent to the model are assembled from slots:
[system] prompt ← config.prompt (string or function)
[system] context slot entries ← config.context (dynamic per tool-step)
[history] prior messages ← config.history (conversation context)
[user] current input ← config.user (current request)
Tools are compiled from block definitions and auto-executed in the model loop (up to maxIterations, default 8).
| Component | Path |
|---|---|
| Handler builder | packages/core/src/blocks/handler.ts |
| Generator builder | packages/core/src/blocks/generator.ts |
| Sequencer DSL | packages/core/src/blocks/sequencer.ts |
| Router builder | packages/core/src/blocks/router.ts |
| Flow definition | packages/core/src/flow/defineFlow.ts |
| Item types | packages/core/src/items/types.ts |
| Block context | packages/core/src/types/block.ts |
| Execution engine | packages/engine/src/execution/executeBlock.ts |
| Action runner | packages/engine/src/execution/runAction.ts |
| Error classes | packages/engine/src/errors/flow-error.ts |
| Retry logic | packages/engine/src/execution/retry.ts |
| CLI run command | packages/cli/src/commands/run.ts |
| CLI block command | packages/cli/src/commands/block.ts |
fsdev block to test suspected failing blocks in isolation before touching code..step() steps.inputSchema/outputSchema compatibility..env.local cause cryptic generator failures. Check OPENAI_API_KEY and similar.