Skip to content

Author loops from a spec, and stop diff reporting Kizen's defaults - #97

Closed
jbedient-kizen wants to merge 2 commits into
bug/loop-automations-fail-to-writefrom
feat/author-loops-from-a-spec
Closed

jbedient-kizen wants to merge 2 commits into
bug/loop-automations-fail-to-writefrom
feat/author-loops-from-a-spec

Conversation

@jbedient-kizen

@jbedient-kizen jbedient-kizen commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #96; merge #96 first, then retarget this to main.

Problem

After #96, the CLI can read and edit automations with loops, but a spec author still can't write one cleanly. A spec loop isn't sent as a branch-group initiator, the fan-out warning fires on every loop, and diff reports the keys Kizen adds to a loop's item variable. More widely, diff reported Kizen's defaults as changes whenever a spec left them out (error_notification_email, deduplicate_array, array_separator, value_html, the per-type default descriptions), so a lean spec never diffed clean against what it had just created.

Solution

A spec describes a loop the way Kizen's builder makes one: the array and item variables, the loop step with step_loop: {array_variable, item_variable}, its body, a next_or_finish closer and any exit_loop steps. create, update and diff handle it like any other step. A loop is always sent as an initiator, since the server stores that regardless, and closers no longer count toward the fan-out warning.

The plan refuses a loop nested more than three deep, or one over the same array as a loop around it. The API accepts both, but Kizen's builder doesn't, and such an automation can't be edited in the UI.

diff now compares a key the spec leaves out as the value Kizen stores for it, ignores the server-derived keys on a loop's item variable, and reads a null key in a top-level or initialize_variable definition as absent. kizen docs show automation gains a Loops section with a worked example over search_records results.

Testing

Local checks after merging main: lint, format, typecheck, pytest (2971 passed, 4 skipped, 1 xfailed) and the CLI-tree diff, passed: lint format typecheck test extra_checks.

Live on a test tenant: a String-array loop, a Record-array loop over search_records with a merged condition and an exit_loop, and nested loops with an exit_loop in the inner body were each created from a spec, then diff → "no changes"; a one-edit update diffed as exactly that edit and clean after; roundtrip --execute → drift: []. The same three re-created from lean specs (no defaults, no descriptions) diffed clean. Test runs iterated once per item, and the nested run's exit_loop ended only the inner loop. create --dry-run of a four-deep spec and of a same-array nested pair exit 1 with the refusal text, writing nothing. Drift tier: 109 passed, 4 failed, all also failing on main (schema snapshot predates the 2026-10-26 release).

Design notes / tradeoffs

Explicit over sugar: the author names the item-variable step and the closer. Having the builder insert them from step_loop alone was rejected, because diff would then show steps the spec never named, and follow-on steps would need a parent_key for a closer the author never keyed. (steps add is a different case; see #98 (the steps add loop PR).)

Default-filling compares an omitted key as its default on both sides rather than dropping it, so a deliberate non-default value (a semicolon separator, say) still shows.

Overlaps: as #96, plus src/kizen_builder/translate.py. #95 (the webhook-auth fix) and #99 (the Call LLM MCP-server fix) also change tools/planners/automations.py, docs/specs/automation.md and CHANGELOG.md.

A spec can now describe a loop the way Kizen's builder makes one: the
array and item variables, the loop, its body, the next_or_finish
closer and any exit_loop steps. A loop is always sent as a branch group
initiator, since the server stores that regardless, and closers no
longer count toward the fan-out warning.

The plan refuses a loop nested more than three deep, or one over the
same array as a loop around it. The API accepts both, but Kizen's
builder doesn't, and such an automation can't be edited in the UI.

diff now compares a key the spec leaves out as the value Kizen stores
for it (error_notification_email, deduplicate_array, array_separator,
value_html and the per-type default descriptions), ignores the keys the
server derives on a loop's item variable, and reads a null key in a
variable definition as absent. Without this, a spec that omits defaults
never diffs clean against what it created.
@jbedient-kizen

Copy link
Copy Markdown
Contributor Author

Included in the 0.4.0 release PR #102; merge that instead.

@sangyeT sangyeT left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the loop authoring path (always-initiator loops, closers excluded from the fan-out warning, the builder-only depth/same-array rules via enclosing_loops) and the diff defaults (_with_default_description, _diffable_variable_block, _WIRE_TOP_DEFAULTS). The default-description fill is the right call since a PUT without one overwrites a UI-set description, so the diff stays honest. Approving.

@jbedient-kizen

Copy link
Copy Markdown
Contributor Author

Merged to main through the 0.4.0 release PR #102 (39beea1), released as v0.4.0. This PR's head is on main; GitHub couldn't mark it merged because its base was the stacked branch.

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.

2 participants