UNPKG

@agentled/cli

Version:

CLI for Agentled — manage workflows, apps, and knowledge from the command line. Zero context-window cost for AI agents.

113 lines (80 loc) 8.93 kB
# Editing Existing Workflows — Merge Model > Loaded on demand from the Agentled skill. Read this before editing any > dictionary-shaped step field (`stepInputData.fieldUpdates`, > `pipelineStepPrompt.responseStructure`, `knowledgeSync.fieldMapping`) or any > `context.*` / `metadata.*` workflow-level field. For live workflows, prefer per-step tools over bulk updates: - `replace_step_dictionary` / `replace_step_path` / `unset_step_path` / `append_step_array_item` / `remove_step_array_item` — preferred for common single-path edits (dictionary keys, nested config, array items) - `update_step(workflowId, stepId, updates)` — low-level merge for complex multi-path edits - `add_step(workflowId, step, insertAfter?)` — insert a new step - `remove_step(workflowId, stepId)` — delete a step and re-wire neighbors - After edits: `validate_workflow``publish_workflow` (or `promote_draft` for live workflows) ## Merge model Prefer the surgical path tools for common single-path edits — they call `get_step` when needed and map to the same merge contract. Keep raw `update_step` for complex multi-path batches. `update_step` accepts three explicit operations on the same call. At least one must be non-empty. - **`updates`** — partial step patch, **deep-merged ONE LEVEL deep**. Top-level scalars are replaced; nested objects (e.g. `pipelineStepPrompt`, `stepInputData`) get their direct keys merged with the stored value's keys. Keys nested two levels deep are overwritten as a unit, not merged. - **`replace: string[]`** — dot-paths whose values from `updates` are assigned **wholesale**, skipping the deep-merge. Use this for **dictionary-shaped fields where keys are user data** (not config) — patching one inner key with `updates` alone silently wipes the others. - **`unset: string[]`** — dot-paths to delete. Each path must currently exist on the step (validated against the original — you cannot unset something you just created in the same call). **Dictionary fields.** Prefer `replace_step_dictionary` for `stepInputData.fieldUpdates`, `pipelineStepPrompt.responseStructure`, and `knowledgeSync.fieldMapping`. With raw `update_step`, call `get_step` first, modify locally, send the full object via `replace[]`. **Diff in the response.** Every `update_step` / surgical-tool call returns `diff: { addedPaths, changedPaths, removedPaths }` and `warnings[]`. If the merge silently removed ≥6 fields without an explicit `unset`, a warning fires. Read it. ## What to use where | Path / field | API | How to edit | Notes | |---|---|---|---| | `name`, `goal`, `description`, `pipelineStepPrompt.template`, `creditCost` | `update_step` | `updates` | Plain scalar; safe to send alone. | | `next`, `loopConfig`, `entryConditions` (full block) | `update_step` | `updates` | Direct nested config; sending the new value wholesale is fine — these are config, not user-data dictionaries. | | `tools`, `integrations` | surgical / `update_step` | `append_step_array_item` / `remove_step_array_item`, or `updates` with full array | Arrays are replaced wholesale by design. | | `stepInputData.fieldUpdates` (kg.update-rows / kg.upsert-rows) | surgical | `replace_step_dictionary` | Keys are user data; raw one-level merge can drop sibling mappings. | | `pipelineStepPrompt.responseStructure` | surgical | `replace_step_dictionary` | Output-shape dictionary; treat as user data. | | `knowledgeSync.fieldMapping` | surgical | `replace_step_dictionary` | Source→target dict; same trap as `fieldUpdates`. | | `renderer.config` (when preserving sibling keys matters) | surgical / `update_step` | `replace_step_path` on the nested key, or full `renderer.config` + `replace` | Surgical wrappers replace the owning top-level field safely. | | `entryConditions.criteria` (when preserving the rest of `entryConditions`) | `update_step` | `updates: { entryConditions: {...full block...} }` | Send the full `entryConditions` block; one-level merge already does the right thing for direct children. | | Removing a step input or stale field | surgical / `update_step` | `unset_step_path` or `unset: ["stepInputData.oldKey"]` | Path must exist on the original. | | `context.inputPages`, `context.outputPages`, `context.executionInputConfig` | `update_workflow_context` | Three explicit verbs (`updates` / `replace` / `unset`) on workflow-relative paths, OR legacy `{ contextKey, value }` for wholesale per-key replacement | **Workflow-level, not step-level.** `update_step` cannot reach `context.*`. See workflow-level merge model below. | | `metadata` | `update_workflow_context` | Same three verbs on `metadata.*` paths; one-level shallow-merge under `updates.metadata` | Workflow-level. Metadata edits bypass the draft snapshot (write direct to Pipeline row even on live workflows). | **Type changes.** `step.type` is technically mutable but stale type-specific fields (`pipelineStepPrompt`, `app`, `tools`, `orchestratorConfig`) persist unless you `unset` them. For clean conversions, prefer `remove_step` + `add_step`. If editing in place, list the incompatible fields under `unset`. **Live workflows.** Edits are routed to a draft snapshot. Response includes `editingDraft: true`. Inspect via `get_draft`, ship via `promote_draft`, throw away via `discard_draft`. For high-stakes edits, `create_snapshot` first as a manual checkpoint. **Draft staleness.** When a draft already exists, every `update_step` / `get_step` response carries a `draft` summary: ```jsonc "draft": { "exists": true, "draftCreatedAt": "...", "liveUpdatedAt": "...", "stale": true, // live advanced past draft.createdAt "modifiedStepIds": ["step-x"], // step IDs whose JSON differs between draft and live "modifiedFields": ["steps"] // top-level config keys that differ } ``` If `draft.stale === true`, the live workflow has been touched (template upgrade, UI edit, deploy) since the draft was forked. Promoting the draft will land its values for fields you didn't touch in this session — those values may be older than current live. Recovery: `discard_draft` and re-apply your edit, or call `get_draft` to inspect what's pending. The `update_step` response also pushes a staleness string into `warnings[]`. **Never** send a full `steps[]` array via `update_workflow`. Use surgical path tools, `update_step`, `add_step`, `remove_step` instead. ## Workflow-level merge model (`update_workflow_context`) `update_workflow_context` is the workflow-level analog of `update_step` — same three verbs, but on **workflow-relative paths** (`context.executionInputConfig.fields`, `metadata.tags`, …). Step-relative paths like `pipelineStepPrompt.template` are rejected here, and conversely `update_step` rejects `context.*` / `metadata.*` paths. The boundary is hard. Allowed path prefixes for `replace[]` / `unset[]`: - `context.inputPages` - `context.outputPages` - `context.executionInputConfig` - `metadata` Out-of-scope paths (e.g. `steps`, `name`) → `PATH_OUT_OF_SCOPE` (400). Other typed errors mirror `update_step`: `EMPTY_PAYLOAD`, `INVALID_PATH`, `REPLACE_VALUE_MISSING`, `UNSET_PATH_NOT_FOUND`. The response includes `diff: { addedPaths, changedPaths, removedPaths }` and `warnings[]` (≥6 silent removals fires the warning). Recipes: ```jsonc // Flip executionInputConfig.internal — same merge-order trap as update_step. // Fetch (get_workflow), merge locally, replace at the parent level: { "updates": { "context": { "executionInputConfig": {...full merged executionInputConfig with internal: true...} } }, "replace": ["context.executionInputConfig"] } // Replace inputPages wholesale: { "updates": { "context": { "inputPages": [...new pages...] } }, "replace": ["context.inputPages"] } // Add a metadata tag (one-level shallow-merge — siblings preserved): { "updates": { "metadata": { "tags": ["beta"] } } } // Delete an obsolete metadata key: { "unset": ["metadata.legacyFlag"] } ``` Live workflows: context edits route to the draft snapshot. Metadata is NOT part of the snapshot config — it always writes direct to the Pipeline row, immediately and on the live workflow. ⚠ **Mixed metadata + context in one call** is the sharp edge: metadata is applied immediately while context becomes pending in the draft. `discard_draft` reverts the pending context changes but **does NOT revert metadata** — metadata never went to the draft. If you need a single atomic checkpoint covering both, `create_snapshot` first or split the call into two. A legacy `{ contextKey, value }` body shape is still accepted as a **compatibility shape** for one-shot wholesale replacement of a single root context key. It can't edit metadata, doesn't return `diff` / `warnings`, and is not the recommended path for new code — prefer the three-verb shape above. ## Post-authoring 1. Test: `start_workflow` with sample input 2. Check results: `get_execution` to see step outputs