synapse-orch-ai 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -0
- package/backend/core/builder_tools.py +87 -4
- package/backend/core/models_orchestration.py +17 -0
- package/backend/core/native_builder/agents/saver_create.json +1 -1
- package/backend/core/native_builder/agents/saver_update.json +2 -2
- package/backend/core/orchestration/engine.py +40 -0
- package/backend/core/orchestration/steps.py +321 -0
- package/bin/synapse.js +26 -0
- package/frontend-build/.next/BUILD_ID +1 -1
- package/frontend-build/.next/app-path-routes-manifest.json +1 -1
- package/frontend-build/.next/build-manifest.json +3 -3
- package/frontend-build/.next/prerender-manifest.json +7 -7
- package/frontend-build/.next/routes-manifest.json +3 -3
- package/frontend-build/.next/server/app/_global-error.html +1 -1
- package/frontend-build/.next/server/app/_global-error.rsc +1 -1
- package/frontend-build/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/frontend-build/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/frontend-build/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/frontend-build/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/frontend-build/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/frontend-build/.next/server/app/_not-found/page.js +4 -4
- package/frontend-build/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/frontend-build/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/frontend-build/.next/server/app/_not-found.html +1 -1
- package/frontend-build/.next/server/app/_not-found.rsc +3 -3
- package/frontend-build/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/frontend-build/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/frontend-build/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/frontend-build/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/frontend-build/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/frontend-build/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/frontend-build/.next/server/app/icon.png/route/app-paths-manifest.json +3 -0
- package/frontend-build/.next/server/app/icon.png/route.js +7 -0
- package/frontend-build/.next/server/app/{icon.svg → icon.png}/route.js.nft.json +1 -1
- package/frontend-build/.next/server/app/icon.png.body +0 -0
- package/frontend-build/.next/server/app/icon.png.meta +1 -0
- package/frontend-build/.next/server/app/index.html +1 -1
- package/frontend-build/.next/server/app/index.rsc +3 -3
- package/frontend-build/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/frontend-build/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/frontend-build/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/frontend-build/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/frontend-build/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/frontend-build/.next/server/app/page.js +4 -4
- package/frontend-build/.next/server/app/page.js.nft.json +1 -1
- package/frontend-build/.next/server/app/page_client-reference-manifest.js +1 -1
- package/frontend-build/.next/server/app/settings/[tab]/page.js +4 -4
- package/frontend-build/.next/server/app/settings/[tab]/page.js.nft.json +1 -1
- package/frontend-build/.next/server/app/settings/[tab]/page_client-reference-manifest.js +1 -1
- package/frontend-build/.next/server/app-paths-manifest.json +1 -1
- package/frontend-build/.next/server/chunks/[externals]_next_dist_0arv.vj._.js +3 -0
- package/frontend-build/.next/server/chunks/_next-internal_server_app_icon_png_route_actions_12.gv.r.js +3 -0
- package/frontend-build/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_0dh.2jf.js +3 -0
- package/frontend-build/.next/server/chunks/ssr/{[root-of-the-server]__08gf6t7._.js → [root-of-the-server]__0ffv7p5._.js} +2 -2
- package/frontend-build/.next/server/chunks/ssr/[root-of-the-server]__0m2-0f1._.js +3 -0
- package/frontend-build/.next/server/chunks/ssr/[root-of-the-server]__120kr3r._.js +3 -0
- package/frontend-build/.next/server/chunks/ssr/_0b~n.nn._.js +15 -9
- package/frontend-build/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_0k.0n04.js → node_modules_next_dist_esm_build_templates_app-page_00t_ate.js} +3 -3
- package/frontend-build/.next/server/chunks/ssr/{node_modules_next_dist_esm_build_templates_app-page_08ib7il.js → node_modules_next_dist_esm_build_templates_app-page_031v14j.js} +3 -3
- package/frontend-build/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0p6f2go.js +4 -0
- package/frontend-build/.next/server/middleware-build-manifest.js +3 -3
- package/frontend-build/.next/server/pages/404.html +1 -1
- package/frontend-build/.next/server/pages/500.html +1 -1
- package/frontend-build/.next/server/server-reference-manifest.js +1 -1
- package/frontend-build/.next/server/server-reference-manifest.json +1 -1
- package/frontend-build/.next/static/chunks/0n_aip.6onjkv.js +52 -0
- package/frontend-build/.next/static/chunks/0or4d2ll~v9-c.css +1 -0
- package/frontend-build/.next/static/media/icon.17u0z6r5r.04f.png +0 -0
- package/package.json +1 -1
- package/frontend-build/.next/server/app/icon.svg/route/app-paths-manifest.json +0 -3
- package/frontend-build/.next/server/app/icon.svg/route.js +0 -6
- package/frontend-build/.next/server/app/icon.svg.body +0 -4
- package/frontend-build/.next/server/app/icon.svg.meta +0 -1
- package/frontend-build/.next/server/chunks/[root-of-the-server]__02je2si._.js +0 -3
- package/frontend-build/.next/server/chunks/_next-internal_server_app_icon_svg_route_actions_0-0ehc~.js +0 -3
- package/frontend-build/.next/server/chunks/ssr/[root-of-the-server]__0hfoz0-._.js +0 -3
- package/frontend-build/.next/server/chunks/ssr/[root-of-the-server]__0uo38ip._.js +0 -3
- package/frontend-build/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_13pf1l2.js +0 -4
- package/frontend-build/.next/static/chunks/14j.u32zioxms.js +0 -46
- package/frontend-build/.next/static/chunks/17yu_4zcwjlqz.css +0 -1
- package/frontend-build/.next/static/media/icon.08txep8y1sjlr.svg +0 -4
- /package/frontend-build/.next/server/app/{icon.svg → icon.png}/route/build-manifest.json +0 -0
- /package/frontend-build/.next/server/app/{icon.svg → icon.png}/route.js.map +0 -0
- /package/frontend-build/.next/static/{HD0MBamLacNNZ7iSAQZxb → cNaZfKm9YP2TSkTyHzETq}/_buildManifest.js +0 -0
- /package/frontend-build/.next/static/{HD0MBamLacNNZ7iSAQZxb → cNaZfKm9YP2TSkTyHzETq}/_clientMiddlewareManifest.js +0 -0
- /package/frontend-build/.next/static/{HD0MBamLacNNZ7iSAQZxb → cNaZfKm9YP2TSkTyHzETq}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -294,8 +294,60 @@ An orchestration is a directed graph (DAG) of steps — you wire agents together
|
|
|
294
294
|
| **Loop** | Repeat a set of steps N times. Use with transforms to iterate over lists or refine outputs. |
|
|
295
295
|
| **Transform** | Execute arbitrary Python against the shared state dict. Reshape data, compute values, filter lists. |
|
|
296
296
|
| **Human** | Pause and ask a human for input via a generated form. Execution resumes when the user responds. Fully resumable. |
|
|
297
|
+
| **Extract JSON** | Parse JSON out of any text — handles raw JSON, markdown code fences, and multiple objects (stored as an array). No LLM call. Perfect for pulling structured data out of an agent's raw output. |
|
|
298
|
+
| **Print** | Render a text or Markdown template with `{state.key}` interpolation and store it in the shared state. Use for building formatted summaries, reports, or notification bodies without an LLM call. |
|
|
299
|
+
| **IF / Else** | Evaluate a Python expression against the shared state and branch to one of two steps — true path or false path. Supports dot-notation (`state.result.flag`). Missing keys evaluate to `None`. No LLM call. |
|
|
300
|
+
| **Switch** | Match a Python expression's string result against a set of named cases. Each case routes to a different step; unmatched values fall through to the default route. No LLM call. |
|
|
297
301
|
| **End** | Finalize the workflow. |
|
|
298
302
|
|
|
303
|
+
### Deterministic Control-Flow Steps
|
|
304
|
+
|
|
305
|
+
Four step types execute **without any LLM call** — they are fast, free, and completely predictable. Use them to add control flow and data handling between your agent steps.
|
|
306
|
+
|
|
307
|
+
#### Extract JSON
|
|
308
|
+
Finds and parses JSON from raw text. Works with:
|
|
309
|
+
- Plain JSON objects / arrays
|
|
310
|
+
- Markdown code fences (` ```json ... ``` `)
|
|
311
|
+
- Multiple JSON blocks in a single string (stored as an array)
|
|
312
|
+
|
|
313
|
+
```
|
|
314
|
+
Input key: llm_raw_output (e.g. "The answer is: ```json\n{\"score\": 8}\n```")
|
|
315
|
+
Output key: parsed (→ { "score": 8 })
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
#### Print
|
|
319
|
+
Renders a Markdown or plain-text template with `{state.key}` and `{state.key.nested}` placeholders resolved from the shared state, then stores the result.
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
print_content: "# Report\n\nScore: {state.score}\nCategory: {state.category}"
|
|
323
|
+
output_key: report_text
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
#### IF / Else
|
|
327
|
+
Evaluates a Python expression against the shared state and branches to one of two steps. Dot-notation is supported — missing keys are `None`.
|
|
328
|
+
|
|
329
|
+
```
|
|
330
|
+
if_condition: state.score > 7
|
|
331
|
+
if_true_step_id: step_approve
|
|
332
|
+
if_false_step_id: step_reject
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Safe built-ins only (`len`, `str`, `int`, `float`, `bool`, `list`, `dict`, `max`, `min`, `abs`, `round`, `any`, `all`). No imports.
|
|
336
|
+
|
|
337
|
+
#### Switch
|
|
338
|
+
Converts a Python expression to a string and matches it against named cases. Unmatched values fall through to `switch_default_step_id`.
|
|
339
|
+
|
|
340
|
+
```
|
|
341
|
+
switch_expression: state.category
|
|
342
|
+
switch_cases:
|
|
343
|
+
"sports" → step_sports_handler
|
|
344
|
+
"politics" → step_politics_handler
|
|
345
|
+
"science" → step_science_handler
|
|
346
|
+
switch_default_step_id: step_general_handler
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
> **Tip:** Chain these steps to build lightweight classification pipelines — use an LLM step to classify, an **Extract JSON** step to parse its output, and a **Switch** step to route — all without extra LLM calls.
|
|
350
|
+
|
|
299
351
|
### Shared State
|
|
300
352
|
|
|
301
353
|
Every step reads from and writes to a shared state dictionary. Define the schema upfront:
|
|
@@ -40,7 +40,8 @@ STEPS_JSON_DESCRIPTION = (
|
|
|
40
40
|
"in your system prompt for the full field list per type. Keep each step "
|
|
41
41
|
"lean — include only fields that differ from zero-defaults.\n"
|
|
42
42
|
"Sub-structures MUST be JSON strings inside each step: route_map_json, "
|
|
43
|
-
"route_descriptions_json, parallel_branches_json, human_fields_json
|
|
43
|
+
"route_descriptions_json, parallel_branches_json, human_fields_json, "
|
|
44
|
+
"switch_cases_json.\n"
|
|
44
45
|
"Minimal example (2 agent steps):\n"
|
|
45
46
|
"'[{\"id\":\"step_abc1234\",\"name\":\"Analyse\",\"type\":\"agent\","
|
|
46
47
|
"\"agent_id\":\"agent_123\",\"prompt_template\":\"Summarise {state.user_input}\","
|
|
@@ -544,7 +545,7 @@ BUILDER_TOOL_SCHEMAS = [
|
|
|
544
545
|
"name": {"type": "string", "description": "Human-readable step name"},
|
|
545
546
|
"type": {
|
|
546
547
|
"type": "string",
|
|
547
|
-
"enum": ["agent", "llm", "evaluator", "parallel", "merge", "human", "tool", "loop", "transform", "end"],
|
|
548
|
+
"enum": ["agent", "llm", "evaluator", "parallel", "merge", "human", "tool", "loop", "transform", "extract_json", "if_else", "switch", "print", "end"],
|
|
548
549
|
"description": "Step type",
|
|
549
550
|
},
|
|
550
551
|
"agent_id": {"type": "string", "description": "Agent ID (required for agent/tool steps)"},
|
|
@@ -554,7 +555,7 @@ BUILDER_TOOL_SCHEMAS = [
|
|
|
554
555
|
"description": "State keys this step reads",
|
|
555
556
|
},
|
|
556
557
|
"output_key": {"type": "string", "description": "State key this step writes to"},
|
|
557
|
-
"next_step_id": {"type": "string", "description": "Next step ID. NOT used for evaluator steps
|
|
558
|
+
"next_step_id": {"type": "string", "description": "Next step ID. NOT used for evaluator/if_else/switch steps."},
|
|
558
559
|
"evaluator_prompt": {"type": "string", "description": "Routing instructions for evaluator steps"},
|
|
559
560
|
"route_map_json": {
|
|
560
561
|
"type": "string",
|
|
@@ -584,6 +585,41 @@ BUILDER_TOOL_SCHEMAS = [
|
|
|
584
585
|
},
|
|
585
586
|
"loop_count": {"type": "integer", "description": "Number of loop iterations"},
|
|
586
587
|
"transform_code": {"type": "string", "description": "Python code: reads `state`, assigns `result` (transform steps)"},
|
|
588
|
+
# ── New deterministic step fields ──────────────────────────
|
|
589
|
+
"print_content": {
|
|
590
|
+
"type": "string",
|
|
591
|
+
"description": (
|
|
592
|
+
"Markdown or plain text to store in output_key (print steps). "
|
|
593
|
+
"Use {state.key} or {state.key.nested} to embed shared state values."
|
|
594
|
+
),
|
|
595
|
+
},
|
|
596
|
+
"if_condition": {
|
|
597
|
+
"type": "string",
|
|
598
|
+
"description": (
|
|
599
|
+
"Python expression evaluated against shared state (if_else steps). "
|
|
600
|
+
"Use dot-notation: e.g. `state.result.flag == True` or `state.score > 5`. "
|
|
601
|
+
"Missing keys are treated as None."
|
|
602
|
+
),
|
|
603
|
+
},
|
|
604
|
+
"if_true_step_id": {"type": "string", "description": "Step to go to when if_condition is True (if_else steps)"},
|
|
605
|
+
"if_false_step_id": {"type": "string", "description": "Step to go to when if_condition is False (if_else steps)"},
|
|
606
|
+
"switch_expression": {
|
|
607
|
+
"type": "string",
|
|
608
|
+
"description": (
|
|
609
|
+
"Python expression whose str() result is matched against switch_cases (switch steps). "
|
|
610
|
+
"Example: `state.category` or `state.result.status.lower()`."
|
|
611
|
+
),
|
|
612
|
+
},
|
|
613
|
+
"switch_cases_json": {
|
|
614
|
+
"type": "string",
|
|
615
|
+
"description": (
|
|
616
|
+
"JSON string mapping string values to step IDs (switch steps). "
|
|
617
|
+
"Example: '{\"approved\":\"step_abc\",\"rejected\":\"step_def\"}'. "
|
|
618
|
+
"Unmatched values fall through to switch_default_step_id."
|
|
619
|
+
),
|
|
620
|
+
},
|
|
621
|
+
"switch_default_step_id": {"type": "string", "description": "Fallback step when no switch case matches"},
|
|
622
|
+
# ──────────────────────────────────────────────────────────
|
|
587
623
|
"max_turns": {"type": "integer", "description": "Max agent turns (default 15)"},
|
|
588
624
|
"timeout_seconds": {"type": "integer", "description": "Step timeout (default 300)"},
|
|
589
625
|
"model": {"type": "string", "description": "LLM model override for this step"},
|
|
@@ -615,7 +651,7 @@ BUILDER_TOOL_SCHEMAS = [
|
|
|
615
651
|
"name": {"type": "string", "description": "Step name"},
|
|
616
652
|
"type": {
|
|
617
653
|
"type": "string",
|
|
618
|
-
"enum": ["agent", "llm", "evaluator", "parallel", "merge", "human", "tool", "loop", "transform", "end"],
|
|
654
|
+
"enum": ["agent", "llm", "evaluator", "parallel", "merge", "human", "tool", "loop", "transform", "extract_json", "if_else", "switch", "print", "end"],
|
|
619
655
|
},
|
|
620
656
|
"agent_id": {"type": "string"},
|
|
621
657
|
"prompt_template": {"type": "string"},
|
|
@@ -633,6 +669,13 @@ BUILDER_TOOL_SCHEMAS = [
|
|
|
633
669
|
"loop_step_ids": {"type": "array", "items": {"type": "string"}},
|
|
634
670
|
"loop_count": {"type": "integer"},
|
|
635
671
|
"transform_code": {"type": "string"},
|
|
672
|
+
"print_content": {"type": "string", "description": "Markdown/text for print steps. Supports {state.key} interpolation."},
|
|
673
|
+
"if_condition": {"type": "string", "description": "Python condition for if_else steps (e.g. `state.flag == True`)"},
|
|
674
|
+
"if_true_step_id": {"type": "string", "description": "Step ID when condition is True"},
|
|
675
|
+
"if_false_step_id": {"type": "string", "description": "Step ID when condition is False"},
|
|
676
|
+
"switch_expression": {"type": "string", "description": "Expression to evaluate for switch steps"},
|
|
677
|
+
"switch_cases_json": {"type": "string", "description": "JSON string: '{\"value\":\"step_id\"}'"},
|
|
678
|
+
"switch_default_step_id": {"type": "string", "description": "Fallback step when no case matches"},
|
|
636
679
|
"max_turns": {"type": "integer"},
|
|
637
680
|
"timeout_seconds": {"type": "integer"},
|
|
638
681
|
"model": {"type": "string"},
|
|
@@ -1282,6 +1325,28 @@ def _validate_orchestration(orch: dict) -> dict:
|
|
|
1282
1325
|
issues.append(f"Human step '{sid}' has no human_prompt.")
|
|
1283
1326
|
if stype == "transform" and not s.get("transform_code"):
|
|
1284
1327
|
issues.append(f"Transform step '{sid}' has no transform_code.")
|
|
1328
|
+
if stype == "if_else":
|
|
1329
|
+
if not s.get("if_condition"):
|
|
1330
|
+
issues.append(f"If/Else step '{sid}' has no if_condition.")
|
|
1331
|
+
if s.get("if_true_step_id") and not _ref_ok(s.get("if_true_step_id")):
|
|
1332
|
+
issues.append(f"If/Else step '{sid}' if_true_step_id points to unknown step.")
|
|
1333
|
+
if s.get("if_false_step_id") and not _ref_ok(s.get("if_false_step_id")):
|
|
1334
|
+
issues.append(f"If/Else step '{sid}' if_false_step_id points to unknown step.")
|
|
1335
|
+
if stype == "switch":
|
|
1336
|
+
if not s.get("switch_expression"):
|
|
1337
|
+
issues.append(f"Switch step '{sid}' has no switch_expression.")
|
|
1338
|
+
if not (s.get("switch_cases") or {}):
|
|
1339
|
+
issues.append(f"Switch step '{sid}' has no switch_cases defined.")
|
|
1340
|
+
for val, target in (s.get("switch_cases") or {}).items():
|
|
1341
|
+
if target and not _ref_ok(target):
|
|
1342
|
+
issues.append(f"Switch step '{sid}' case '{val}' points to unknown step '{target}'.")
|
|
1343
|
+
default_id = s.get("switch_default_step_id")
|
|
1344
|
+
if default_id and not _ref_ok(default_id):
|
|
1345
|
+
issues.append(f"Switch step '{sid}' switch_default_step_id points to unknown step.")
|
|
1346
|
+
if stype == "extract_json" and not s.get("output_key"):
|
|
1347
|
+
issues.append(f"Extract JSON step '{sid}' has no output_key — extracted JSON will not be stored.")
|
|
1348
|
+
if stype == "print" and not s.get("print_content"):
|
|
1349
|
+
issues.append(f"Print step '{sid}' has no print_content.")
|
|
1285
1350
|
|
|
1286
1351
|
# Writer-membership check: every input_key must be `user_input` /
|
|
1287
1352
|
# `user_query` (engine-seeded) or the `output_key` of some step. Branch
|
|
@@ -1332,6 +1397,14 @@ def _validate_orchestration(orch: dict) -> dict:
|
|
|
1332
1397
|
for bsid in branch:
|
|
1333
1398
|
if bsid in by_id:
|
|
1334
1399
|
successors.append(bsid)
|
|
1400
|
+
# New branching step successors
|
|
1401
|
+
for ref_key in ("if_true_step_id", "if_false_step_id", "switch_default_step_id"):
|
|
1402
|
+
ref = s.get(ref_key)
|
|
1403
|
+
if ref and ref in by_id:
|
|
1404
|
+
successors.append(ref)
|
|
1405
|
+
for target in (s.get("switch_cases") or {}).values():
|
|
1406
|
+
if target and target in by_id:
|
|
1407
|
+
successors.append(target)
|
|
1335
1408
|
if not successors:
|
|
1336
1409
|
issues.append(
|
|
1337
1410
|
f"Step '{cur}' (type={s.get('type')}) has no next_step_id / routes / branches — path dead-ends without reaching an `end` step."
|
|
@@ -1392,6 +1465,8 @@ def _normalize_step_inputs(step: dict) -> dict:
|
|
|
1392
1465
|
s["parallel_branches"] = _parse_json_field(s.pop("parallel_branches_json"), [])
|
|
1393
1466
|
if "human_fields_json" in s:
|
|
1394
1467
|
s["human_fields"] = _parse_json_field(s.pop("human_fields_json"), [])
|
|
1468
|
+
if "switch_cases_json" in s:
|
|
1469
|
+
s["switch_cases"] = _parse_json_field(s.pop("switch_cases_json"), {})
|
|
1395
1470
|
return s
|
|
1396
1471
|
|
|
1397
1472
|
|
|
@@ -1476,6 +1551,14 @@ def _fill_step_defaults(steps: list) -> list:
|
|
|
1476
1551
|
s.setdefault("allowed_tools", None)
|
|
1477
1552
|
s.setdefault("next_step_id", None)
|
|
1478
1553
|
s.setdefault("max_iterations", 3)
|
|
1554
|
+
# New deterministic step defaults
|
|
1555
|
+
s.setdefault("print_content", None)
|
|
1556
|
+
s.setdefault("if_condition", None)
|
|
1557
|
+
s.setdefault("if_true_step_id", None)
|
|
1558
|
+
s.setdefault("if_false_step_id", None)
|
|
1559
|
+
s.setdefault("switch_expression", None)
|
|
1560
|
+
s.setdefault("switch_cases", {})
|
|
1561
|
+
s.setdefault("switch_default_step_id", None)
|
|
1479
1562
|
result.append(s)
|
|
1480
1563
|
return result
|
|
1481
1564
|
|
|
@@ -17,6 +17,10 @@ class StepType(str, Enum):
|
|
|
17
17
|
LOOP = "loop"
|
|
18
18
|
HUMAN = "human"
|
|
19
19
|
TRANSFORM = "transform"
|
|
20
|
+
EXTRACT_JSON = "extract_json"
|
|
21
|
+
IF_ELSE = "if_else"
|
|
22
|
+
SWITCH = "switch"
|
|
23
|
+
PRINT = "print"
|
|
20
24
|
END = "end"
|
|
21
25
|
|
|
22
26
|
|
|
@@ -53,6 +57,19 @@ class StepConfig(BaseModel):
|
|
|
53
57
|
# TRANSFORM -- Python code to run on shared state
|
|
54
58
|
transform_code: str | None = None
|
|
55
59
|
|
|
60
|
+
# PRINT -- user-defined text/markdown stored to output_key
|
|
61
|
+
print_content: str | None = None # Supports {state.key} interpolation
|
|
62
|
+
|
|
63
|
+
# IF_ELSE -- Python condition evaluated against shared state
|
|
64
|
+
if_condition: str | None = None # e.g. "state.result.flag == True"
|
|
65
|
+
if_true_step_id: str | None = None # step to go to when condition is True
|
|
66
|
+
if_false_step_id: str | None = None # step to go to when condition is False
|
|
67
|
+
|
|
68
|
+
# SWITCH -- match a state expression against multiple case values
|
|
69
|
+
switch_expression: str | None = None # e.g. "state.result.status"
|
|
70
|
+
switch_cases: dict[str, str | None] = {} # {value: target_step_id} (None = end)
|
|
71
|
+
switch_default_step_id: str | None = None # fallback if no case matches
|
|
72
|
+
|
|
56
73
|
# HUMAN -- pause for human input
|
|
57
74
|
human_prompt: str | None = None
|
|
58
75
|
human_fields: list[dict[str, str]] = [] # [{name, type, label}]
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"repos": [],
|
|
22
22
|
"db_configs": [],
|
|
23
|
-
"system_prompt": "# Role\nYou are the Create Saver. The plan is already approved - translate it into tool calls. Do NOT re-plan. Act as a strict execution engine. Start immediately and finish with the exact word: DONE.\n\n# Inputs\n- `plan_draft` - Approved plan with flow diagram. Your absolute source of truth.\n- `created_agents` - Markdown list of agents. Each agent is a `##` section with **Role:**, **ID:**, **Type:**, **Description:**, and **Tools:** fields. Extract agent IDs from **ID:** lines; match roles via **Role:** to the plan.\n- `selected_agent_ids` - Pre-picked agent IDs already confirmed to exist.\n\n# Execution Protocol - strict phases\n\n**PHASE 0: Verify references (do this BEFORE any skeleton call)**\n1. If the plan needs agents and if both the `created_agents` and `selected_agent_ids` are missing or empty, reply with `Agents from the plan are missing, cannot proceed` and a one-line error `PARSE_ERROR: <reason>` - do NOT invent agent IDs.\n2. For every distinct `agent_id` you will use in an agent step, call `get_agent(agent_id)` to confirm it exists. If any ID is missing, stop and reply `Agents are missing` with `MISSING_AGENT: <role> <agent_id>` - do NOT fabricate.\n3. Collect every distinct `forced_tool` name the plan_draft asks for in tool steps. Call `get_tools_detail(tool_names=[...all of them])` in ONE batch. If the response contains `_not_found`, then try to get `list_tool_servers` and find the tool similar or same tool mentioned in the plan. if tool not found then say 'Tool not found' and end the chat\n\n**PHASE 1: Initialization**\n1. Call `create_orchestration_skeleton` to get the `orch_id`.\n\n**PHASE 2: Step Construction**\nAdd steps using the most efficient tool:\n- USE `add_multiple_steps` to batch up to 5 plain steps at once (agent, llm, merge, end).\n- USE `add_single_step` ONLY for steps with stringified JSON fields (evaluator, parallel, human) or the final leftover step.\n- For every `agent` step: the `agent_id` comes `created_agents` or `selected_agent_ids`. Never guess.\n- For every `tool` step: the `forced_tool` comes verbatim from the plan_draft's verified tool list.\n- NEVER reuse a `step_id` you have already added.\n\n**PHASE 3: Validation & Finalization**\n1. Once ALL steps are added, call `validate_orchestration`.\n2. If it passes: output `DONE`.\n3. If it fails: call `update_step` to fix the specific issue, then call `validate_orchestration` again.\n4. STRICT LOOP BREAKER: maximum THREE (3) `update_step` attempts. If validation fails a fourth time, output `Step Error` immediately. Do not loop.\n\n# Stringified JSON Warning (CRITICAL)\nParameters ending in `_json` (e.g., `state_schema_json`, `route_map_json`, `parallel_branches_json`, `human_fields_json`, `patch_json`) MUST be passed as properly escaped JSON strings.\n- CORRECT: `route_map_json=\"{\"yes\":\"step_1\", \"no\":\"step_2\"}\"`\n- All other parameters (lists, basic strings) are passed as plain values.\n\n# Step Construction Rules\n- **IDs:** `step_` + 7 lowercase alphanumeric chars (e.g., `step_abc1234`).\n- **State Schema:** `user_input` and `user_query` are built-in; omit them from the schema. Any custom state key needs a step that writes it (`output_key`) AND a schema entry.\n- **Dependencies:** A step's `input_keys` can only reference `user_input`, `user_query`, or an `output_key` generated by an upstream step.\n- **Evaluators:** Do NOT set `next_step_id` for evaluators. The `output_key` stores the chosen route label.\n\n# MCP Tool Discovery (for tool steps)\nWhen the plan has for a `tool` step that uses an MCP server tool:\n1. try to directly call `list_server_tools(server_name)` if you have server_name if not then call `list_tool_servers` to see available servers (names + types).\n2. Then try to find the tool we need to use, once found then stop and go to next step.\n3. Compose the full tool name:\n - **External MCP** (type = `stdio`, `sse`, or `external_mcp`): `\"{server_name}__{raw_tool_name}\"` (double underscore). Example: server `github`, tool `get_repo`
|
|
23
|
+
"system_prompt": "# Role\nYou are the Create Saver. The plan is already approved - translate it into tool calls. Do NOT re-plan. Act as a strict execution engine. Start immediately and finish with the exact word: DONE.\n\n# Inputs\n- `plan_draft` - Approved plan with flow diagram. Your absolute source of truth.\n- `created_agents` - Markdown list of agents. Each agent is a `##` section with **Role:**, **ID:**, **Type:**, **Description:**, and **Tools:** fields. Extract agent IDs from **ID:** lines; match roles via **Role:** to the plan.\n- `selected_agent_ids` - Pre-picked agent IDs already confirmed to exist.\n\n# Execution Protocol - strict phases\n\n**PHASE 0: Verify references (do this BEFORE any skeleton call)**\n1. If the plan needs agents and if both the `created_agents` and `selected_agent_ids` are missing or empty, reply with `Agents from the plan are missing, cannot proceed` and a one-line error `PARSE_ERROR: <reason>` - do NOT invent agent IDs.\n2. For every distinct `agent_id` you will use in an agent step, call `get_agent(agent_id)` to confirm it exists. If any ID is missing, stop and reply `Agents are missing` with `MISSING_AGENT: <role> <agent_id>` - do NOT fabricate.\n3. Collect every distinct `forced_tool` name the plan_draft asks for in tool steps. Call `get_tools_detail(tool_names=[...all of them])` in ONE batch. If the response contains `_not_found`, then try to get `list_tool_servers` and find the tool similar or same tool mentioned in the plan. if tool not found then say 'Tool not found' and end the chat\n\n**PHASE 1: Initialization**\n1. Call `create_orchestration_skeleton` to get the `orch_id`.\n\n**PHASE 2: Step Construction**\nAdd steps using the most efficient tool:\n- USE `add_multiple_steps` to batch up to 5 plain steps at once (agent, llm, merge, end, extract_json, print).\n- USE `add_single_step` ONLY for steps with stringified JSON fields (evaluator, parallel, human, if_else, switch) or the final leftover step.\n- For every `agent` step: the `agent_id` comes `created_agents` or `selected_agent_ids`. Never guess.\n- For every `tool` step: the `forced_tool` comes verbatim from the plan_draft's verified tool list.\n- NEVER reuse a `step_id` you have already added.\n\n**PHASE 3: Validation & Finalization**\n1. Once ALL steps are added, call `validate_orchestration`.\n2. If it passes: output `DONE`.\n3. If it fails: call `update_step` to fix the specific issue, then call `validate_orchestration` again.\n4. STRICT LOOP BREAKER: maximum THREE (3) `update_step` attempts. If validation fails a fourth time, output `Step Error` immediately. Do not loop.\n\n# Stringified JSON Warning (CRITICAL)\nParameters ending in `_json` (e.g., `state_schema_json`, `route_map_json`, `parallel_branches_json`, `human_fields_json`, `switch_cases_json`, `patch_json`) MUST be passed as properly escaped JSON strings.\n- CORRECT: `route_map_json=\"{\"yes\":\"step_1\", \"no\":\"step_2\"}\"`\n- All other parameters (lists, basic strings) are passed as plain values.\n\n# Step Type Palette\n\nUse the right step type for each task. The most common types are agent, llm, evaluator, end. Use the four types below when the plan specifically calls for deterministic control flow or data extraction.\n\n## extract_json — extract JSON from text into shared state (no LLM)\nFinds and parses JSON blocks from input text (handles markdown fences, raw JSON, multiple objects → stored as array).\n- Required: `input_keys`, `output_key`\n- Can be batched with `add_multiple_steps`.\nExample: `{step_id: \"step_ext1111\", name: \"Extract JSON\", type: \"extract_json\", input_keys: [\"llm_raw\"], output_key: \"parsed_json\", next_step_id: \"step_abc2222\"}`\n\n## print — store rendered text/markdown into shared state (no LLM)\nResolves `{state.key}` and `{state.key.nested}` placeholders in user-provided text/markdown, then stores the result.\n- Required: `print_content`, `output_key`\n- Can be batched with `add_multiple_steps`.\nExample: `{step_id: \"step_prt1111\", name: \"Summary Print\", type: \"print\", print_content: \"# Result\\n\\nCategory: {state.category}\\nConfidence: {state.confidence}\", output_key: \"summary\", next_step_id: \"step_end2222\"}`\n\n## if_else — binary branch based on a Python condition (no LLM)\nEvaluates a Python expression against shared state. Missing keys → None.\n- Required: `if_condition`, `if_true_step_id`, `if_false_step_id`\n- MUST use `add_single_step` (branching — no next_step_id).\nExample: `add_single_step(orch_id=\"orch_123\", step_id=\"step_chk1111\", name=\"Flag Check\", type=\"if_else\", if_condition=\"state.score > 7\", if_true_step_id=\"step_ok2222\", if_false_step_id=\"step_ng3333\", input_keys=[\"score\"])`\n\n## switch — multi-way branch by matching a value (no LLM)\nEvaluates an expression, converts result to string, matches against named cases. Falls through to `switch_default_step_id` when no case matches.\n- Required: `switch_expression`, `switch_cases_json`, `switch_default_step_id`\n- MUST use `add_single_step` — `switch_cases_json` is a stringified JSON map.\nExample: `add_single_step(orch_id=\"orch_123\", step_id=\"step_swt1111\", name=\"Category Router\", type=\"switch\", switch_expression=\"state.category\", switch_cases_json=\"{\\\"sports\\\":\\\"step_spt2222\\\",\\\"politics\\\":\\\"step_pol3333\\\",\\\"science\\\":\\\"step_sci4444\\\"}\", switch_default_step_id=\"step_def5555\", input_keys=[\"category\"])`\n\n# Step Construction Rules\n- **IDs:** `step_` + 7 lowercase alphanumeric chars (e.g., `step_abc1234`).\n- **State Schema:** `user_input` and `user_query` are built-in; omit them from the schema. Any custom state key needs a step that writes it (`output_key`) AND a schema entry.\n- **Dependencies:** A step's `input_keys` can only reference `user_input`, `user_query`, or an `output_key` generated by an upstream step.\n- **Evaluators:** Do NOT set `next_step_id` for evaluators. The `output_key` stores the chosen route label.\n- **If/Else & Switch:** Do NOT set `next_step_id` — routing is done via `if_true_step_id`/`if_false_step_id` or `switch_cases_json`/`switch_default_step_id`.\n\n# MCP Tool Discovery (for tool steps)\nWhen the plan has for a `tool` step that uses an MCP server tool:\n1. try to directly call `list_server_tools(server_name)` if you have server_name if not then call `list_tool_servers` to see available servers (names + types).\n2. Then try to find the tool we need to use, once found then stop and go to next step.\n3. Compose the full tool name:\n - **External MCP** (type = `stdio`, `sse`, or `external_mcp`): `\"{server_name}__{raw_tool_name}\"` (double underscore). Example: server `github`, tool `get_repo` → `\"github__get_repo\"`.\n - **Native MCP** (type = `native_mcp`): use `\"{raw_tool_name}\"` directly - no prefix.\n\nSame rule applies when the plan specifies MCP tools for an agent's `tools` list - use the prefixed `server_name__tool_name` form for external servers.\n\n# Examples\n\n## Example 1: Efficient Batching\n`add_multiple_steps(orch_id=\"orch_123\", steps=[\n {step_id: \"step_rsc1111\", name: \"Research\", type: \"agent\", agent_id: \"agent_mq0gwwxgjp\", prompt_template: \"Research: {state.user_input}\", input_keys: [\"user_input\"], output_key: \"research\", next_step_id: \"step_wrt2222\"},\n {step_id: \"step_wrt2222\", name: \"Write\", type: \"agent\", agent_id: \"agent_a1b2c3d4e5\", prompt_template: \"Write based on: {state.research}\", input_keys: [\"research\"], output_key: \"article\", next_step_id: \"step_end3333\"},\n {step_id: \"step_end3333\", name: \"End\", type: \"end\"}\n])`\n\n## Example 2: Evaluator (Stringified JSON)\n`add_single_step(orch_id=\"orch_123\", step_id=\"step_gat1111\", name=\"Gate\", type=\"evaluator\", evaluator_prompt=\"Sufficient?\", route_map_json=\"{\"yes\":\"step_par2222\",\"no\":\"step_rsc1111\"}\", route_descriptions_json=\"{\"yes\":\"OK\",\"no\":\"Redo\"}\", input_keys=[\"research\"], output_key=\"gate\")`\n\n# Error Recovery\n- **Duplicate step id** → Skip it, already added.\n- **Orchestration not found** → Verify you are using the exact `orch_id` from Phase 1.\n- **20+ turns used** → Validate whatever exists, reply `DONE`.",
|
|
24
24
|
"orchestration_id": null,
|
|
25
25
|
"model": null,
|
|
26
26
|
"provider": null,
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
],
|
|
22
22
|
"repos": [],
|
|
23
23
|
"db_configs": [],
|
|
24
|
-
"system_prompt": "# Role\nYou are the Update Saver. The plan is already approved — apply it as TARGETED EDITS to the existing orchestration. Do NOT re-plan and do NOT recreate the whole orchestration. Start immediately and finish with the exact word: DONE.\n\n# Inputs\n- `current_orchestration_id` — the orch_id you are editing. Use this, not a new skeleton.\n- `existing_orch` — the full JSON of the current orchestration. Preserve every step_id that is not explicitly being changed.\n- `plan_draft` — approved plan with flow diagram describing the DIFF.\n- `created_agents` — Markdown list of agents. Each agent is a `##` section with **Role:**, **ID:**, **Type:**, **Description:**, **Tools:**, and optionally **Repos:** and **Existing:** fields. Scan for **Role:** / **ID:** pairs to build a role → agent_id map before editing.\n- `selected_agent_ids` — Pre-picked agent IDs already confirmed to exist.\n\n# Execution Protocol — strict phases\n\n**PHASE 0: Verify references (do this BEFORE any edit call)**\n1. Scan `created_agents` for **Role:** / **ID:** pairs to build a role → agent_id map. If the field is present but contains no **ID:** lines and no agents are needed, continue. If agents are needed but no IDs can be found, reply `DONE` with `PARSE_ERROR: no agent IDs found in created_agents` — do NOT invent agent IDs.\n2. For every `agent_id` you will use in a new-or-changed agent step, call `get_agent(agent_id)` to confirm it exists. If any ID is missing, stop and reply `DONE` with `MISSING_AGENT: <role> <agent_id>`.\n3. Collect every distinct `forced_tool` the plan_draft asks for in new-or-changed tool steps. Call `get_tools_detail(tool_names=[...])` in ONE batch. If `_not_found` is non-empty, stop and reply `DONE` with `MISSING_TOOLS: <names>`.\n\n**PHASE 1: Identify the diff**\nCompare `existing_orch.steps` to the plan_draft. Categorise every step as one of: KEEP (no change), PATCH (use `update_step`), REMOVE (use `remove_step`), ADD (use `add_single_step` / `add_multiple_steps`).\n\n**PHASE 2: Apply edits**\n- `set_orchestration_meta` — only if name/description/entry_step_id/state_schema/limits changed.\n- `update_step(orch_id, step_id, patch_json=\"{...}\")` — only fields that changed; omit everything else.\n- `remove_step(orch_id, step_id)` — for every step in existing_orch that is not in the new plan.\n- `add_multiple_steps` — batch up to 5 new plain steps (agent/llm/merge/end) at once.\n- `add_single_step` — for steps with stringified JSON fields (evaluator/parallel/human) or a single leftover.\n- Preserve existing step_ids exactly. Only generate new IDs for brand-new steps.\n- For every agent step you add or change: `agent_id` comes verbatim from the verified role → agent_id map.\n- For every tool step you add or change: `forced_tool` comes from the verified tool list.\n\n**PHASE 3: Validation & Finalization**\n1. Call `validate_orchestration`.\n2. If it passes: output `DONE`.\n3. If it fails: call `update_step` to fix, then re-validate.\n4. STRICT LOOP BREAKER: maximum TWO (2) fix attempts. After that, output `DONE` anyway.\n\n# Stringified JSON Warning (CRITICAL)\nParameters ending in `_json` (e.g. `state_schema_json`, `route_map_json`, `parallel_branches_json`, `human_fields_json`, `patch_json`) MUST be JSON strings.\n- CORRECT: `patch_json=\"{\"prompt_template\":\"New prompt\"}\"`\n- Lists and basic strings are passed as plain values.\n\n# Step Construction Rules\n- **IDs:** new step IDs are `step_` + 7 lowercase alphanumeric chars.\n- **State Schema:** `user_input` and `user_query` are built-in; never include them in schema edits. Any NEW `output_key` a step writes MUST be added to `state_schema` via `set_orchestration_meta` in the SAME batch of edits.\n- **Dependencies:** input_keys can reference only `user_input`, `user_query`, or an upstream step's `output_key`.\n- **Evaluators:** no `next_step_id` — the `output_key` stores the chosen route label.\n- **Rewiring:** when you PATCH a step to point at a new next_step_id, also PATCH the upstream step (if any) whose `next_step_id` pointed at the step you removed.\n\n# MCP Tool Discovery (for tool steps)\nWhen the plan adds or changes a `tool` step that uses an MCP server tool:\n1. Call `list_tool_servers` to see available servers (names + types).\n2. For any server the plan references, call `list_server_tools(server_name)` to get its raw tool names.\n3. Compose the full tool name:\n - **External MCP** (type = `stdio`, `sse`, or `external_mcp`): `\"{server_name}__{raw_tool_name}\"` (double underscore). Example: server `github`, tool `get_repo` → `\"github__get_repo\"`.\n - **Native MCP** (type = `native_mcp`): use `\"{raw_tool_name}\"` directly — no prefix.\n4. Use this full name as `forced_tool` in the step AND pass it to `get_tools_detail` in PHASE 0 for verification.\n\nSame rule applies when the plan specifies MCP tools for an agent's `tools` list — use `server_name__tool_name` for external servers.\n\n# Worked Example — inserting a new step between two existing ones\n\nExisting flow: `step_rsc1111 (research) → step_wrt2222 (write) → step_end3333`.\nPlan adds a `step_fct4444` fact-check step between research and write, writing new `output_key=facts`.\n\n1. `update_step(orch_id=\"orch_123\", step_id=\"step_rsc1111\", patch_json=\"{\"next_step_id\":\"step_fct4444\"}\")` — rewire upstream.\n2. `add_single_step(orch_id=\"orch_123\", step_id=\"step_fct4444\", name=\"Fact Check\", type=\"agent\", agent_id=\"agent_mq0gwwxgjp\", prompt_template=\"Verify: {state.research}\", input_keys=[\"research\"], output_key=\"facts\", next_step_id=\"step_wrt2222\")` — insert new step.\n3. `set_orchestration_meta(orch_id=\"orch_123\", state_schema_json=\"{\"research\":{\"type\":\"str\",\"default\":\"\"},\"facts\":{\"type\":\"str\",\"default\":\"\"},\"article\":{\"type\":\"str\",\"default\":\"\"}}\")` — extend schema with the new `facts` key (keep existing keys).\n4. `validate_orchestration` → `DONE`.\n\n# Error Recovery\n- **Duplicate step id** → Skip; already added.\n- **Step not found on update/remove** → It was already removed; move on.\n- **20+ turns used** → Validate whatever exists, reply `DONE`.",
|
|
24
|
+
"system_prompt": "# Role\nYou are the Update Saver. The plan is already approved — apply it as TARGETED EDITS to the existing orchestration. Do NOT re-plan and do NOT recreate the whole orchestration. Start immediately and finish with the exact word: DONE.\n\n# Inputs\n- `current_orchestration_id` — the orch_id you are editing. Use this, not a new skeleton.\n- `existing_orch` — the full JSON of the current orchestration. Preserve every step_id that is not explicitly being changed.\n- `plan_draft` — approved plan with flow diagram describing the DIFF.\n- `created_agents` — Markdown list of agents. Each agent is a `##` section with **Role:**, **ID:**, **Type:**, **Description:**, **Tools:**, and optionally **Repos:** and **Existing:** fields. Scan for **Role:** / **ID:** pairs to build a role → agent_id map before editing.\n- `selected_agent_ids` — Pre-picked agent IDs already confirmed to exist.\n\n# Execution Protocol — strict phases\n\n**PHASE 0: Verify references (do this BEFORE any edit call)**\n1. Scan `created_agents` for **Role:** / **ID:** pairs to build a role → agent_id map. If the field is present but contains no **ID:** lines and no agents are needed, continue. If agents are needed but no IDs can be found, reply `DONE` with `PARSE_ERROR: no agent IDs found in created_agents` — do NOT invent agent IDs.\n2. For every `agent_id` you will use in a new-or-changed agent step, call `get_agent(agent_id)` to confirm it exists. If any ID is missing, stop and reply `DONE` with `MISSING_AGENT: <role> <agent_id>`.\n3. Collect every distinct `forced_tool` the plan_draft asks for in new-or-changed tool steps. Call `get_tools_detail(tool_names=[...])` in ONE batch. If `_not_found` is non-empty, stop and reply `DONE` with `MISSING_TOOLS: <names>`.\n\n**PHASE 1: Identify the diff**\nCompare `existing_orch.steps` to the plan_draft. Categorise every step as one of: KEEP (no change), PATCH (use `update_step`), REMOVE (use `remove_step`), ADD (use `add_single_step` / `add_multiple_steps`).\n\n**PHASE 2: Apply edits**\n- `set_orchestration_meta` — only if name/description/entry_step_id/state_schema/limits changed.\n- `update_step(orch_id, step_id, patch_json=\"{...}\")` — only fields that changed; omit everything else.\n- `remove_step(orch_id, step_id)` — for every step in existing_orch that is not in the new plan.\n- `add_multiple_steps` — batch up to 5 new plain steps (agent/llm/merge/end/extract_json/print) at once.\n- `add_single_step` — for steps with stringified JSON fields (evaluator/parallel/human/if_else/switch) or a single leftover.\n- Preserve existing step_ids exactly. Only generate new IDs for brand-new steps.\n- For every agent step you add or change: `agent_id` comes verbatim from the verified role → agent_id map.\n- For every tool step you add or change: `forced_tool` comes from the verified tool list.\n\n**PHASE 3: Validation & Finalization**\n1. Call `validate_orchestration`.\n2. If it passes: output `DONE`.\n3. If it fails: call `update_step` to fix, then re-validate.\n4. STRICT LOOP BREAKER: maximum TWO (2) fix attempts. After that, output `DONE` anyway.\n\n# Stringified JSON Warning (CRITICAL)\nParameters ending in `_json` (e.g. `state_schema_json`, `route_map_json`, `parallel_branches_json`, `human_fields_json`, `switch_cases_json`, `patch_json`) MUST be JSON strings.\n- CORRECT: `patch_json=\"{\"prompt_template\":\"New prompt\"}\"`\n- Lists and basic strings are passed as plain values.\n\n# Step Type Palette\n\nUse the right step type for each task. The four deterministic types below need no LLM and should be preferred when the plan calls for control flow or data wrangling.\n\n## extract_json — extract JSON from text (no LLM)\nParses JSON from text input (markdown fences, raw JSON, multi-object → array).\n- Required: `input_keys`, `output_key`; can be batched with `add_multiple_steps`.\n\n## print — render text/markdown template into shared state (no LLM)\nResolves `{state.key}` placeholders. Required: `print_content`, `output_key`; can be batched.\n\n## if_else — binary branch on a Python condition (no LLM)\nRequired: `if_condition`, `if_true_step_id`, `if_false_step_id`. Use `add_single_step` only.\n- Do NOT set `next_step_id`.\n- `switch_cases_json` not used here.\n\n## switch — multi-way branch by value match (no LLM)\nRequired: `switch_expression`, `switch_cases_json` (stringified JSON map), `switch_default_step_id`. Use `add_single_step` only.\n- Do NOT set `next_step_id`.\n- Example: `switch_cases_json=\"{\\\"sports\\\":\\\"step_a\\\",\\\"politics\\\":\\\"step_b\\\"}\"`\n\n# Step Construction Rules\n- **IDs:** new step IDs are `step_` + 7 lowercase alphanumeric chars.\n- **State Schema:** `user_input` and `user_query` are built-in; never include them in schema edits. Any NEW `output_key` a step writes MUST be added to `state_schema` via `set_orchestration_meta` in the SAME batch of edits.\n- **Dependencies:** input_keys can reference only `user_input`, `user_query`, or an upstream step's `output_key`.\n- **Evaluators:** no `next_step_id` — the `output_key` stores the chosen route label.\n- **If/Else & Switch:** no `next_step_id` — routing via `if_true_step_id`/`if_false_step_id` or `switch_cases_json`/`switch_default_step_id`.\n- **Rewiring:** when you PATCH a step to point at a new next_step_id, also PATCH the upstream step (if any) whose `next_step_id` pointed at the step you removed.\n\n# MCP Tool Discovery (for tool steps)\nWhen the plan adds or changes a `tool` step that uses an MCP server tool:\n1. Call `list_tool_servers` to see available servers (names + types).\n2. For any server the plan references, call `list_server_tools(server_name)` to get its raw tool names.\n3. Compose the full tool name:\n - **External MCP** (type = `stdio`, `sse`, or `external_mcp`): `\"{server_name}__{raw_tool_name}\"` (double underscore). Example: server `github`, tool `get_repo` → `\"github__get_repo\"`.\n - **Native MCP** (type = `native_mcp`): use `\"{raw_tool_name}\"` directly — no prefix.\n4. Use this full name as `forced_tool` in the step AND pass it to `get_tools_detail` in PHASE 0 for verification.\n\nSame rule applies when the plan specifies MCP tools for an agent's `tools` list — use `server_name__tool_name` for external servers.\n\n# Worked Example — inserting a new step between two existing ones\n\nExisting flow: `step_rsc1111 (research) → step_wrt2222 (write) → step_end3333`.\nPlan adds a `step_fct4444` fact-check step between research and write, writing new `output_key=facts`.\n\n1. `update_step(orch_id=\"orch_123\", step_id=\"step_rsc1111\", patch_json=\"{\"next_step_id\":\"step_fct4444\"}\")` — rewire upstream.\n2. `add_single_step(orch_id=\"orch_123\", step_id=\"step_fct4444\", name=\"Fact Check\", type=\"agent\", agent_id=\"agent_mq0gwwxgjp\", prompt_template=\"Verify: {state.research}\", input_keys=[\"research\"], output_key=\"facts\", next_step_id=\"step_wrt2222\")` — insert new step.\n3. `set_orchestration_meta(orch_id=\"orch_123\", state_schema_json=\"{\"research\":{\"type\":\"str\",\"default\":\"\"},\"facts\":{\"type\":\"str\",\"default\":\"\"},\"article\":{\"type\":\"str\",\"default\":\"\"}}\")` — extend schema with the new `facts` key (keep existing keys).\n4. `validate_orchestration` → `DONE`.\n\n# Error Recovery\n- **Duplicate step id** → Skip; already added.\n- **Step not found on update/remove** → It was already removed; move on.\n- **20+ turns used** → Validate whatever exists, reply `DONE`.",
|
|
25
25
|
"orchestration_id": null,
|
|
26
26
|
"model": null,
|
|
27
27
|
"provider": null,
|
|
28
28
|
"max_turns": null
|
|
29
|
-
}
|
|
29
|
+
}
|
|
@@ -434,6 +434,46 @@ class OrchestrationEngine:
|
|
|
434
434
|
return guarded_id, event or loop_event
|
|
435
435
|
return next_id, event
|
|
436
436
|
|
|
437
|
+
# IF_ELSE — deterministic true/false branch
|
|
438
|
+
if step.type == StepType.IF_ELSE:
|
|
439
|
+
decision = run.shared_state.get(f"_if_decision_{step.id}")
|
|
440
|
+
if decision == "true" and step.if_true_step_id:
|
|
441
|
+
target = step.if_true_step_id
|
|
442
|
+
elif step.if_false_step_id:
|
|
443
|
+
target = step.if_false_step_id
|
|
444
|
+
else:
|
|
445
|
+
target = step.next_step_id
|
|
446
|
+
event = {
|
|
447
|
+
"type": "if_decision",
|
|
448
|
+
"orch_step_id": step.id,
|
|
449
|
+
"decision": decision,
|
|
450
|
+
"target_step_id": target,
|
|
451
|
+
}
|
|
452
|
+
if target:
|
|
453
|
+
guarded_id, loop_event = self._apply_loop_guard(target, run)
|
|
454
|
+
return guarded_id, event or loop_event
|
|
455
|
+
return target, event
|
|
456
|
+
|
|
457
|
+
# SWITCH — deterministic case matching
|
|
458
|
+
if step.type == StepType.SWITCH and step.switch_cases:
|
|
459
|
+
matched = run.shared_state.get(f"_switch_decision_{step.id}")
|
|
460
|
+
if matched is not None and matched in step.switch_cases:
|
|
461
|
+
target = step.switch_cases[matched]
|
|
462
|
+
else:
|
|
463
|
+
target = step.switch_default_step_id
|
|
464
|
+
event = {
|
|
465
|
+
"type": "switch_decision",
|
|
466
|
+
"orch_step_id": step.id,
|
|
467
|
+
"matched_case": matched,
|
|
468
|
+
"target_step_id": target,
|
|
469
|
+
}
|
|
470
|
+
if target is None:
|
|
471
|
+
return None, event
|
|
472
|
+
if target:
|
|
473
|
+
guarded_id, loop_event = self._apply_loop_guard(target, run)
|
|
474
|
+
return guarded_id, event or loop_event
|
|
475
|
+
return target, event
|
|
476
|
+
|
|
437
477
|
# Default linear routing
|
|
438
478
|
next_id = step.next_step_id
|
|
439
479
|
|