Skip to content

Fix the sub-workflow tool, and write the ladder into the changelog - #86

Merged
ChesterHsu merged 2 commits into
mainfrom
ladder/subworkflow
Sep 2, 2026
Merged

Fix the sub-workflow tool, and write the ladder into the changelog#86
ChesterHsu merged 2 commits into
mainfrom
ladder/subworkflow

Conversation

@ChesterHsu

Copy link
Copy Markdown
Member

Two things, both prerequisites for releasing 2.31.2.

The agent's sub-workflow tool had never executed once

TemplateAgentTool wraps a stored template as a tool an LLM agent can call — flyto's equivalent of n8n's Workflow Tool. Three defects sat on top of each other, and the top one hid the rest.

The import was one level short. _execute_template imported WorkflowEngine from ...engine, which from modules/atomic/llm/ resolves to core.modules.engine and does not exist; every neighbour in the same directory writes four dots. Every call raised ModuleNotFoundError — and invoke's blanket except Exception turned it into {"ok": False, "error": ...}, an ordinary-looking tool failure. The agent reported a template that ran and failed. A crash that never surfaced as a crash.

Behind it, the wrong shape. The workflow was built as {"nodes": ..., "edges": ...}. A template definition holds stepstemplate.invoke builds workflow={'steps': steps} from the same shape — and the engine answers "No steps defined in workflow" to anything else.

Behind that, no recursion bound. template.invoke caps nesting at 16; this path counted nothing, so a template whose agent had that same template as a tool would spawn engines until the execution timeout. It now shares the same context key, so a mixed chain is counted once per level rather than resetting at each hand-off.

Driven against the real engine with a real module:

invoke({"text": "hi"})  ->  ok: True, steps.s1.data.result == "HI"
depth 15 -> runs      depth 16 -> refused
depth -1 -> refused   depth True -> refused

Ten tests; each of the three fixes severed independently — cutting the import fails all ten, the shape fails three, the cap fails one.

The changelog

The 2.31.2 section described the old five-valued verification_status and stopped there. The ladder — what eleven unreleased commits are about — had never been written down.

Changed covers the contract, and why failed and indeterminate are different answers rather than two words for one: retrying a failure is safe, retrying an indeterminate write may do it twice.

Fixed covers nine defects with the measurement rather than an adjective — the revoked webhook that completed green in a shipped recipe, the 158 of 483 modules with no ok key, the 3920-pixel scroll reported as a drag, the misspelled selector that reached a green tick 1495ms faster than the real element.

Verification

Full suite: 5,058 passed, 4 pre-existing failures (packaging, two live-URL visual-diff tests, version metadata). Three further failures in the first run were network flakes and pass on re-run.

check_release_drift.py and check_documentation.py both pass.

🤖 Generated with Claude Code

`TemplateAgentTool` wraps a stored template as a tool an LLM agent can call.
Three defects sat on top of each other, and the top one hid the rest.

THE IMPORT WAS ONE LEVEL SHORT. `_execute_template` imported WorkflowEngine
from `...engine`, which from `modules/atomic/llm/` resolves to
`core.modules.engine` and does not exist; every neighbour in the same directory
writes four dots. So every call raised ModuleNotFoundError -- and `invoke`'s
blanket `except Exception` turned it into `{"ok": False, "error": ...}`, an
ordinary-looking tool failure. The agent reported a template that ran and
failed. A crash that never surfaced as a crash, on every call, for as long as
this was wrong.

BEHIND IT, THE WRONG SHAPE. The workflow was built as
`{"nodes": ..., "edges": ...}`. A template definition holds `steps` --
`template.invoke` builds `workflow={'steps': steps}` from the same shape -- and
the engine answers "No steps defined in workflow" to anything else. Unreachable
behind the import error, so it had never run either. An empty template now says
so here rather than coming back with the engine's wording for a different
problem.

BEHIND THAT, NO RECURSION BOUND. `template.invoke` caps nesting at 16 and
refuses past it; this path counted nothing, so a template whose agent has that
same template as a tool would spawn engines until the execution timeout. It now
reads and increments the same context key, so a mixed chain -- template.invoke
calling an agent whose tool is a template -- is counted once per level rather
than resetting at each hand-off. `True` is rejected along with negatives and
non-integers: it is an `int` in Python and would otherwise pass every
comparison as 1.

Driven against the real engine with a real module:

    invoke({"text": "hi"})  ->  ok: True, steps.s1.data.result == "HI"
    depth 15  -> runs        depth 16 -> refused
    depth -1  -> refused     depth True -> refused

Ten tests, and each of the three fixes severed independently: cutting the
import fails all ten, the shape fails three, the cap fails one.
The 2.31.2 section described the old five-valued `verification_status` and
stopped there. The ladder itself -- the thing eleven unreleased commits are
about -- had never been written down, so a reader of the changelog would have
had no idea it existed.

Changed covers the contract: one shared vocabulary across 483 modules, only
`verified` drawable as success, and why `failed` and `indeterminate` are
different answers rather than two words for the same one -- retrying a failure
is safe, retrying an indeterminate write may do it twice. Plus the two things
that make it reach anyone: a rung can now travel out through a raise, and an
LLM agent is told the rung of every tool it calls.

Fixed covers nine defects, each with the measurement rather than an adjective:
the revoked webhook that completed green in a shipped recipe, the 158 of 483
modules with no `ok` key, the 3920-pixel scroll reported as a drag, the
misspelled selector that reached a green tick 1495ms faster than the real
element, `verify.run` failing at the registry, and the sub-workflow tool that
had never executed once.

Reference pages regenerated; the inventory sentences follow to 230,913 lines.
`check_release_drift.py` and `check_documentation.py` both pass.
@ChesterHsu
ChesterHsu merged commit bc370f7 into main Sep 2, 2026
12 checks passed
@ChesterHsu
ChesterHsu deleted the ladder/subworkflow branch September 2, 2026 01:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant