-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Description
Bug Description
sanitizeForCopilot in lib/workflows/sanitization/json-sanitizer.ts uses internal UUID block IDs as keys when serializing workflow state for the LLM. This means the copilot model sees output like:
{
"blocks": {
"087b3e77-xxxx-xxxx-xxxx-xxxxxxxxxxxx": {
"type": "agent",
"name": "My Agent",
"inputs": { "prompt": "..." },
"connections": {
"source": "a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
}
}
}When the model then generates edit_workflow operations referencing these blocks, it produces UUID-based references like <087b3e77-xxxx.output>, which are fragile and hard to interpret.
Why This Is a Problem
- Smaller / open-weight models (e.g. those served via vLLM or Ollama) tend to copy UUID keys verbatim into generated references, resulting in broken workflows.
- Even high-capability models (Claude Sonnet/Opus) would benefit from human-readable keys — it reduces token waste and makes copilot interactions more predictable.
Expected Behavior
sanitizeForCopilot should replace UUID block keys with deterministic, human-readable identifiers derived from the block type and name, for example:
{
"blocks": {
"start": { "type": "starter", "name": "Start", ... },
"agent_1": { "type": "agent", "name": "My Agent", ... }
}
}Connection targets should also reference the human-readable keys instead of UUIDs.
Analysis
Changing sanitizeForCopilot alone is not sufficient. The fix requires changes across multiple layers:
1. Sanitization layer (json-sanitizer.ts)
- Build a UUID to readable key mapping (e.g.
{type}for unique types,{type}_{index}for duplicates) - Use readable keys for block output, connection targets, and nested nodes
2. Operation normalization layer (new)
A reverse mapping step is needed before the engine processes copilot-generated operations:
- Resolve name-based
block_idto UUID for edit/delete operations - Resolve name-based connection targets to UUID
- Replace name-based
<block.field>input references to UUID-based references - Handle edge case: model adding a
starterblock whenstart_triggeralready exists — convert to edit
3. Engine layer (edit-workflow/engine.ts, builders.ts)
- Integrate the normalizer before operation execution
Why all layers are needed
Without the reverse mapping, the copilot generates operations with readable keys (e.g. block_id: "agent_1") that the engine cannot resolve — it expects UUIDs. This causes all edit/delete operations on existing blocks to fail silently.
Proposed Approach
Given the scope of changes required, I'd like to discuss the preferred approach before submitting a PR:
- Centralizing normalization — A single normalizer file that handles all name-UUID resolution, keeping
engine.tsandbuilders.tschanges minimal - Backwards compatibility — The normalizer should fall through to UUID if the block_id is already a valid UUID, so existing behavior is preserved
- Testing strategy — Unit tests for the key mapping, reverse mapping, and end-to-end operation normalization
Would appreciate feedback on the approach before proceeding with implementation.