Skip to content

[AJDA-2813] Conditional flows support, drop orchestrator (0.57.0)#387

Merged
padak merged 8 commits into
mainfrom
ondra/AJDA-2813
Jun 4, 2026
Merged

[AJDA-2813] Conditional flows support, drop orchestrator (0.57.0)#387
padak merged 8 commits into
mainfrom
ondra/AJDA-2813

Conversation

@ondrajodas

@ondrajodas ondrajodas commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Rewrites the kbagent flow command group around the Conditional Flow component (keboola.flow) and drops keboola.orchestrator support entirely. Closes the gap where the CLI treated keboola.flow as if it shared the legacy orchestrator dependsOn shape — the old template was wrong and _validate_dag validated nothing real.

Linear: https://linear.app/keboola/issue/AJDA-2813/cf-add-support-in-new-cli

Breaking changes

  • --component-id removed from all flow subcommands and component_id removed from the /flows REST surface — keboola.flow is the only flow component.
  • flow list no longer returns keboola.orchestrator configs; a count of ignored legacy flows is surfaced as a warning.
  • flow new / flow update validate payloads against the conditional-flow schema; invalid definitions are rejected with INVALID_FLOW_DEFINITION (replaces INVALID_FLOW_DAG).
  • flow schema prints the conditional-flow YAML template; the legacy dependsOn template is gone.

Key design points

  • Live schema, no bundle: the CF JSON schema is fetched at runtime from the AI Service (configurationSchema of keboola.flow), so validation always matches what the stack actually serves — no vendored copy to drift.
  • Graceful degradation: when the schema fetch fails (network, stale stack), structural validation is skipped with a warning and semantic checks still run — writes are never blocked by an AI Service outage. Semantic checks (unique ids, task.phase/goto references, mandatory default transition, ≥1 enabled task per phase, operator/function operand arity, unreachable-phase warnings) are pure Python and always on.
  • New commands: flow validate [--project] (offline semantic-only, or full validation against the live schema) and flow schema --full --project (dump the live JSON schema).
  • flow detail human rendering rewritten for phases/transitions/conditions, task type badges and retry info; JSON output stays full-body passthrough.
  • update_flow stays merge-aware: validation runs on the merged result when only phases or only tasks are provided.
  • Incidental fix: ComponentDetail now tolerates explicit null documentation fields returned by the AI Service (e.g. for keboola.flow).

Docs & plugin sync

All silent-drift surfaces updated per convention #17: CLAUDE.md command list, AGENT_CONTEXT, keboola-expert.md, SKILL.md (regenerated), commands-reference.md, flow-workflow.md (full rewrite), gotchas.md (since v0.57.0 entries), README, changelog.

Testing

  • New tests/test_flow_validation.py (validator unit tests incl. arity and reachability).
  • test_flow_service.py / test_flow_cli.py rewritten with CF payloads; schema-fetch success/failure/empty fallback covered with mocked AI client.
  • E2E flow round-trip rewritten for CF (create → validate → detail → update → schedule → delete), skips clearly when the project lacks conditional_flows.
  • Full suite: 3827 passed, 126 skipped; ruff + format + ty clean.

Release

Version bump to 0.57.0 with a breaking-change changelog entry.

@linear

linear Bot commented Jun 4, 2026

Copy link
Copy Markdown

AJDA-2813

@ondrajodas

Copy link
Copy Markdown
Contributor Author
# Release Notes
kbagent 0.56.0 rewrites the `flow` command group around Conditional Flows (keboola.flow) and drops legacy keboola.orchestrator support. Flow payloads are now validated against the live CF schema fetched from the AI Service (semantic checks always run, even offline). New commands: `flow validate [--project]` for pre-push validation and `flow schema --full --project` to dump the exact JSON schema. Breaking: `--component-id` removed from all flow subcommands and from the /flows REST surface; `flow list` ignores orchestrator configs (legacy count warning); INVALID_FLOW_DAG error code replaced by INVALID_FLOW_DEFINITION.

# Plans for Customer Communication
CLI users and AI agents consuming the kbagent plugin. Communicated via the 0.56.0 changelog entry (shown by the auto-update "What's new" hook on startup) and updated plugin docs (gotchas.md tagged "since v0.56.0"). No direct customer outreach needed — orchestrator-based flow management was already broken/misleading in previous versions.

# Impact Analysis
Affects only the kbagent CLI and its embedded REST server (/flows endpoints) — no platform services change. Users with scripts passing `--component-id` to flow commands will get a usage error (exit 2) and must drop the flag. Projects without the conditional_flows feature get a clear actionable API error on flow writes. Schema fetch failure degrades gracefully to semantic-only validation — AI Service outage never blocks writes.

# Change Type
Feature (breaking) — CLI surface rewrite + new validation subsystem.

# Justification
The CLI previously treated keboola.flow as the legacy orchestrator shape: wrong template, no-op DAG validation, inconsistent component defaults across subcommands. Conditional Flows are the only supported flow type going forward; the old orchestrator is deprecated. Linear: AJDA-2813.

# Deployment Plan
Standard release flow: merge to main, tag v0.56.0, create GitHub release — auto-update picks it up on next CLI startup. No migrations, no config changes, no infrastructure impact.

# Rollback Plan
Revert the release: delete/yank the GitHub release so /releases/latest points back to 0.55.0; users can pin via `uv tool install keboola-agent-cli==0.55.0`. No persisted state changes to roll back (config.json format untouched).

# Post-Release Support Plan
Watch for reports of: false-positive validation rejections (escape: schema is live-fetched, so a stack-side schema fix propagates without a CLI release), AI Service schema propagation lag on some stacks (degrades to semantic-only with a visible warning), and users hitting removed `--component-id` in automation. Gotchas documented in the plugin for AI-agent consumers.

@ondrajodas ondrajodas left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Review of #387 — [AJDA-2813] Conditional flows support, drop orchestrator (0.56.0)

Generated by kbagent-pr-reviewer subagent. Verdict and findings below
are advisory; the human author retains every veto. CI-coverable issues
(lint, format, tests) are confirmed via make check, not duplicated here.

Summary

This PR rewrites the kbagent flow command group around the Conditional Flow component (keboola.flow), drops keboola.orchestrator support, adds two new commands (flow validate, flow schema --full), and performs a thorough pass over all silent-drift surfaces (AGENT_CONTEXT, keboola-expert.md, commands-reference.md, gotchas.md, CLAUDE.md, SKILL.md, flow-workflow.md). The implementation is well-structured: pure-Python validation in a separate flow_validation.py module, graceful schema-fetch degradation, merge-aware update, and full service+CLI+E2E test coverage.

One blocking finding: the two new CLI commands (flow validate and flow schema --full) have no corresponding REST routes in server/routers/flows.py, and the PR description does not document a skip rationale as required by CONTRIBUTING.md. This breaks the 1:1 CLI-to-REST convention that external consumers (Web UI, scheduled agents) rely on.

Verdict: REQUEST CHANGES.

Verdict

  • Verdict: REQUEST CHANGES
  • Blocking findings: 1
  • Non-blocking findings: 2
  • Nits: 2

Blocking findings

[B-1] src/keboola_agent_cli/server/routers/flows.pyflow validate and flow schema --full have no REST endpoints

The PR adds two new CLI commands (flow validate, flow schema --full) but flows.py has 8 routes for the 9 total commands. Neither POST /flows/validate nor GET /flows/{project}/schema is present.

Per CONTRIBUTING.md > "Checklist: Adding a New CLI Command" — "HTTP API endpoint in server/routers/<group>.py" — every command needs a matching route, and the only documented exception is for genuinely terminal-only commands (interactive prompts, Rich-rendered-only output, doctor/init/update-style infrastructure). Neither flow validate nor flow schema --full meet that bar: both produce structured JSON output ({"valid", "errors", "warnings", "notes"} and {"format", "schema"}) that external consumers can use, and FlowService.fetch_flow_schema() is already a public service method making the plumbing trivial. The PR description does not mention a skip rationale.

Fix: add a POST /flows/validate route (body: FlowValidate(phases, tasks, project)) that calls flow_validation.validate_conditional_flow with an optional live schema fetch, and add GET /flows/{project}/schema that calls service.fetch_flow_schema(project). If there is a deliberate reason to skip (e.g., validate is treated as a pure-client pre-flight that should never traverse the serve API), document it in the PR description with a one-line reason.

Non-blocking findings

[NB-1] plugins/kbagent/skills/kbagent/references/schedule-workflow.md:50 — stale keboola.orchestrator example in schedule-workflow.md

schedule-workflow.md was not changed in this PR, but it contains an example JSON with "parent_component_id": "keboola.orchestrator". After 0.56.0 drops orchestrator support from the flow group, an AI agent reading this as an archetypal schedule example may infer that orchestrator flows are still actively supported. The schedule list command itself is broader (it lists every keboola.scheduler config regardless of its target component) so the field value is technically not wrong — legacy schedules whose targets are orchestrators will still appear — but the example is confusing when paired with the 0.56.0 gotcha that says orchestrators are dropped.

Fix: add a clarifying sentence: "The example shows keboola.orchestrator as parent_component_id because legacy orchestrator schedules still appear in schedule list output; since 0.56.0 new flows use keboola.flow." Alternatively, update the example JSON to show a keboola.flow target.

[NB-2] src/keboola_agent_cli/services/flow_service.py:154 — broad except Exception in _fetch_flow_schema

_fetch_flow_schema catches except Exception as exc: return None, str(exc) to convert network-level or unexpected errors into a graceful (None, reason) result. This is intentional — a schema-fetch failure must not block a write — but the broad catch swallows errors that might indicate real bugs (e.g., a KeyError from a malformed AiServiceClient response). CONTRIBUTING.md § "Specific exception handling -- never bare except:" applies to production code.

Fix: replace with except (OSError, ConnectionError, TimeoutError) as exc: for transport errors and let unexpected exceptions propagate. The KeboolaApiError path already handles 4xx/5xx API errors; the except Exception catch is only needed for low-level transport issues. Alternatively, keep it as-is but add a comment justifying the broad catch (e.g., # intentional: any failure here must degrade, not block the write).

Nits

  • [NIT-1] src/keboola_agent_cli/services/flow_service.py:77,227,251,547,641,710exc.error_code == "NOT_FOUND" uses a raw string literal instead of ErrorCode.NOT_FOUND. StrEnum subclasses str so comparisons work, but the pattern ErrorCode.NOT_FOUND is the stated convention (CONTRIBUTING.md > "Error codes -- enum only, never raw strings"). This pattern exists in one other place (org_service.py:353) and is grandfathered; consider a sweep but not urgent.

  • [NIT-2] src/keboola_agent_cli/commands/flow.py:415import json as _json and from rich.syntax import Syntax are deferred inside the flow schema --full branch. Deferred imports are fine for rarely-executed paths and keep startup fast, but they are inconsistent with how the same rich.syntax.Syntax import is written in the sibling non---full branch (also deferred, line 432). Both occurrences are in the same function; extract them to the function top (or to the module top) to avoid the duplicated deferred-import pattern. This is cosmetic.

Verification log

  • gh pr view 387 --json title,body,files,additions,deletions,baseRefName,headRefName,labels,state → 31 files, +4724/-1600, state OPEN, headRefName ondra/AJDA-2813
  • git rev-parse --abbrev-ref HEADondra/AJDA-2813 (working tree matches PR branch) ✓
  • grep typer src/keboola_agent_cli/services/flow_service.py → empty ✓ (no layer violation)
  • grep httpx src/keboola_agent_cli/commands/flow.py → empty ✓ (no layer violation)
  • grep '"flow\.' src/keboola_agent_cli/permissions.py → 9 entries including flow.validate: read, flow.schema: read, flow.new: write, flow.update: write, flow.delete: destructive, flow.schedule: write, flow.schedule-remove: destructive, flow.list: read, flow.detail: read ✓ (all commands registered)
  • grep '@flow_app.command' commands/flow.py | wc -l → 9 commands
  • grep '@router\.' server/routers/flows.py | wc -l → 8 routes (MISMATCH: flow validate and flow schema --full have no REST route — [B-1])
  • make check3827 passed, 8 skipped in 87 seconds, exit 0 ✓
  • Silent-drift surfaces checked:
    • src/keboola_agent_cli/commands/context.py AGENT_CONTEXT: flow validate and flow schema --full present ✓
    • CLAUDE.md ## All CLI Commands: flow validate and flow schema --full present ✓
    • plugins/kbagent/agents/keboola-expert.md §2 Tool Selection Matrix: "Author / edit a conditional flow" row updated for 0.56.0 ✓; §3 inline gotchas updated ✓; §1 Rule 6 VERSION GATE: no new version-gated example needed (0.56.0 features are in the matrix row's NEVER column) ✓
    • plugins/kbagent/skills/kbagent/references/commands-reference.md: flow section updated with 0.56.0 note and new commands ✓
    • plugins/kbagent/skills/kbagent/references/gotchas.md: 7 new entries all tagged since v0.56.0
    • plugins/kbagent/skills/kbagent/references/flow-workflow.md: full rewrite with CF structure ✓
    • plugins/kbagent/skills/kbagent/SKILL.md: regenerated ✓
    • plugins/kbagent/.claude-plugin/plugin.json: version synced to 0.56.0 ✓
    • src/keboola_agent_cli/permissions.py OPERATION_REGISTRY: all 9 flow commands registered ✓
    • INVALID_FLOW_DAG removed from ErrorCode enum and replaced with INVALID_FLOW_DEFINITION; backward-compat: no remaining production code references to INVALID_FLOW_DAG ✓; changelog notes the rename ✓
  • schedule-workflow.md not changed in this PR; still contains keboola.orchestrator example (see [NB-1])
  • Behavior reproduction: not run against a live API (no E2E credentials in environment). E2E tests in test_e2e.py cover flow schema → flow validate → flow new → flow list → flow detail → flow update → flow schedule → flow schedule-remove → flow delete (steps 1-9) and flow validate with --project and flow schema --full --project (steps 2.1-2.2) with a pytest.skip guard when the project lacks conditional_flows. Test suite passes locally without credentials (unit + CLI layer only, 3827 passed) ✓.

Open questions for the author

  • Was the omission of REST routes for flow validate and flow schema --full intentional? If so, add a one-line skip reason to the PR description (e.g., "validate is a pre-flight safety check designed for client-side use only; serving it over the REST API would encourage callers to bypass local validation, which is the anti-pattern"). If it was an oversight, see [B-1] for the fix.

@ondrajodas

Copy link
Copy Markdown
Contributor Author

Review findings addressed in 2acf109:

  • [B-1] Added POST /flows/validate and GET /flows/{project}/schema routes — 1:1 parity restored (9 commands / 10 routes incl. the schedules listing). Routes are declared before the /{project}//{project}/{config_id} path-param routes so the literal segments win; router tests cover both endpoints including route-ordering guards, the fetch-failure degrade path (validate stays 200 with a note) and the 502 path (schema endpoint has no degrade to offer).
  • [NB-1] schedule-workflow.md example switched to keboola.flow + added a sentence explaining legacy orchestrator schedules still appear in schedule list output.
  • [NB-2] Kept the broad catch, added the justifying comment. The suggested narrowing to (OSError, ConnectionError, TimeoutError) would not work for this stack: httpx.HTTPError (and its TransportError/ConnectError subclasses) does not subclass OSError, so transport failures would propagate and block the write — the exact behavior the degrade contract forbids.
  • [NIT-1] All 6 exc.error_code == "NOT_FOUND" comparisons in flow_service.py switched to ErrorCode.NOT_FOUND. (org_service.py:353 left for a separate sweep as suggested.)
  • [NIT-2] json and rich.syntax.Syntax imports hoisted to module top — rich.table was already a module-level import, so the deferred pattern saved nothing.

Full suite: 3833 passed, 126 skipped; ruff check + format clean.

@ondrajodas ondrajodas marked this pull request as ready for review June 4, 2026 16:11

@ondrajodas ondrajodas left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Review of #387 — [AJDA-2813] Conditional flows support, drop orchestrator (0.56.0)

Generated by kbagent-pr-reviewer subagent. Scope: commit 2acf109 only (the review-fix commit), per the <focus> directive. The rest of the PR was covered by review 4429120221.

Verdict and findings below are advisory; the human author retains every veto.


Summary

Commit 2acf109 correctly addresses all five findings from the previous review. The two new REST routes (POST /flows/validate, GET /flows/{project}/schema) are well-formed: they are declared before the path-parameter routes so literal segments win in FastAPI's declaration-order match, the error paths are correct (degrade-to-200 for validate, 502 for schema), and the six new router tests confirm both happy paths and the route-ordering guards. The ErrorCode.NOT_FOUND enum switch eliminates the last raw string comparison in flow_service.py, the broad except Exception in _fetch_flow_schema is now accompanied by an accurate justification comment, and the schedule-workflow.md example is updated. The only residual item is a leftover deferred import that the commit message does not claim to have addressed and that carries no runtime risk.

Verdict: APPROVE (zero blocking findings).


Verdict

  • Verdict: APPROVE
  • Blocking findings: 0
  • Non-blocking findings: 0
  • Nits: 1

Blocking findings

(none)


Non-blocking findings

(none)


Nits

  • [NIT-1] src/keboola_agent_cli/commands/flow.py:467 — one deferred import remains after the hoist. from ..services.flow_validation import find_unreachable_phases, validate_conditional_flow sits inside the flow_validate() function body. The commit hoisted json and rich.syntax.Syntax but left this one. flow_validation has no circular-import risk (it imports only stdlib + jsonschema), so the import is safe to move to the module-level block alongside the other service imports. No runtime consequence; purely a style inconsistency with the rest of the file and with the commit's stated intent.

Verification log

  • git rev-parse --abbrev-ref HEADondra/AJDA-2813 ✓ (working tree on PR branch)
  • gh pr view 387 --json stateOPEN
  • git show 2acf109 --stat → 5 files changed: flows.py (+55), flow_service.py (+16), test_server_router_calls.py (+138), flow.py (+10 imports), schedule-workflow.md (+6) ✓
  • Route ordering (grep -n '@router\.' server/routers/flows.py): POST /validate at line 51, GET /{project}/schema at line 80, GET "" at line 92, GET /{project}/{config_id} at line 105, POST /{project} at line 116. Literal-segment routes declared before path-param routes in both cases ✓
  • Route ordering guard tests: test_flows_validate_without_project_is_semantic_only asserts flow_svc.create_flow.assert_not_called(); test_flows_get_schema_returns_live_schema asserts flow_svc.get_flow_detail.assert_not_called()
  • Error paths: 502 on schema-fetch failure confirmed in test_flows_get_schema_fetch_failure_is_502; degrade-to-200 on validate schema-fetch failure confirmed in test_flows_validate_schema_fetch_failure_degrades
  • ErrorCode.NOT_FOUND switch: grep '"NOT_FOUND"' src/keboola_agent_cli/services/flow_service.py → empty ✓ (all raw comparisons replaced)
  • Broad except comment in _fetch_flow_schema (flow_service.py:155): comment present and accurate — explains httpx.HTTPError / OSError inheritance gap ✓
  • Deferred imports in flow.py: json and rich.syntax.Syntax hoisted to module top ✓; flow_validation import still deferred at line 467 (NIT-1 above)
  • schedule-workflow.md: both example occurrences of "componentId": "keboola.orchestrator" switched to "keboola.flow"; note added for legacy orchestrators ✓
  • uv run pytest tests/test_server_router_calls.py -k flows -v → 6 passed ✓
  • make check3833 passed, 8 skipped — all checks passed, lint clean, make check-error-codes clean ✓

Open questions for the author

(none)

ondrajodas

This comment was marked as duplicate.

@ondrajodas ondrajodas requested a review from padak June 4, 2026 20:07

@padak padak left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review of #387 — Conditional flows support, drop orchestrator (0.56.0)

Generated by kbagent-pr-reviewer subagent. Verdict and findings below
are advisory; the human author retains every veto. CI-coverable issues
(lint, format, tests) are confirmed via make check, not duplicated here.

Summary

This PR rewrites the kbagent flow command group around the Conditional Flow component (keboola.flow), drops keboola.orchestrator support entirely, adds two new commands (flow validate, flow schema --full), migrates the error code INVALID_FLOW_DAGINVALID_FLOW_DEFINITION, and removes --component-id from every flow subcommand. The implementation is thorough: all major plugin sync surfaces are updated, make check passes clean (3866 passed, 8 skipped), the agent prompt stays within the 62 KB budget (48 840 bytes), the command-sync CI gate is green (229 commands registered), and SKILL.md is fresh. There are zero blocking findings. Two non-blocking issues are flagged: two semantically distinct 2-tuples added to flow_service.py when the coding convention calls for dataclasses, and flow.py crossing the soft LOC ceiling for the first time. One NIT on the REST backward-compat story. Verdict: APPROVE.

Verdict

  • Verdict: APPROVE
  • Blocking findings: 0
  • Non-blocking findings: 2
  • Nits: 1

Blocking findings

(none)

Non-blocking findings

[NB-1] src/keboola_agent_cli/services/flow_service.py:140,173 — two new semantically-distinct 2-tuples instead of dataclasses

_fetch_flow_schema (line 140) and the public fetch_flow_schema (line 173) both return tuple[dict[str, Any] | None, str | None] where the first element is the schema and the second is a failure reason. These are two semantically distinct values (schema payload vs. human-readable error string), and both are new additions in this PR. Per CONTRIBUTING.md > "Code Quality Patterns" > "Return values": "two-element tuples should use a dataclass when the values are semantically distinct" and "existing tuple[...] returns in services are grandfathered, but do not add new ones." A named dataclass (e.g. @dataclass(frozen=True) class SchemaFetchResult: schema: dict | None; failure_reason: str | None) would make every call site self-documenting, which matters because the pattern is reused in three locations (_fetch_flow_schema, fetch_flow_schema, and the REST router's validate endpoint).

Fix: introduce a SchemaFetchResult dataclass adjacent to FlowService and update the three call sites; the router keeps its local unpacking.

[NB-2] src/keboola_agent_cli/commands/flow.py:1–946 — file crosses soft LOC ceiling for first time

Before this PR flow.py was 778 lines (under the 800-line soft ceiling). This PR takes it to 946 lines (past soft, still under the 1200-line hard ceiling). Per CONTRIBUTING.md > "File-size budgets": "When a file crosses the soft ceiling, the next PR that adds material to it should split first." The file now owns command parsing for 8 subcommands (list, detail, schema, validate, new, update, delete, schedule, schedule-remove) plus substantial private helper functions (_load_flow_yaml, _format_flow_detail, _format_flows_table, _format_schedule_info). A natural split point would be a _flow_formatters.py private helper for the Rich output helpers, leaving the Typer command functions in flow.py.

This does not block the current PR because the ceiling was not yet crossed before; it is flagged so the next PR touching flow.py is on notice.

Nits

  • [NIT-1] src/keboola_agent_cli/server/routers/flows.py:16-38FlowCreate, FlowUpdate, and FlowSchedule silently dropped component_id (was str = "keboola.flow" default). The PR description calls this a breaking change at the CLI level, but the REST router breaking change gets no mention there. Any external caller that currently passes component_id in the POST body would get a 422 Unprocessable Entity (Pydantic extra-field rejection) after this ships. If the REST API consumer set is small and well-known (just the Web UI and scheduled agents), this is acceptable given the version bump to 0.56.0; if not, an extra = "ignore" model config or an explicit deprecation header would make the transition safer. Worth a one-sentence note in the PR description and changelog entry for REST API consumers.

Verification log

  • gh pr view 387 --json title,body,files,state → 35 files, +4938/-1612, state OPEN. PR title lacks conventional-commit prefix but commits use feat(flow)!: which is correct. ✓
  • git rev-parse --abbrev-ref HEADondra/AJDA-2813 after gh pr checkout 387. ✓
  • Layer violation checks (typer in services, httpx in commands, formatter in clients) → all empty. ✓
  • grep -E '^\+.*error_code\s*=\s*"[A-Z_]+"' /tmp/kbagent-pr-387.diff | grep -v tests/ → only test files use raw string literals; production code uses ErrorCode enum throughout. ✓
  • grep -E '^\+\s*except\s*:' → empty (no bare excepts). ✓
  • grep -E '^\+\s*print\(' ... src/ → empty. ✓
  • grep -n 'flow\.' src/keboola_agent_cli/permissions.pyflow.schema: read, flow.validate: read both present alongside existing flow entries. ✓
  • make command-sync-checkOK: all 229 CLI commands are registered. ✓
  • make skill-checkSKILL.md is up-to-date. ✓
  • wc -c plugins/kbagent/agents/keboola-expert.md → 48 840 bytes (budget 62 000). ✓
  • tests/test_agent_prompt.py budget assertion: 48 840 < PROMPT_BYTE_BUDGET. ✓
  • grep 'INVALID_FLOW_DAG' src/ tests/ plugins/ docs/ → no live production reference; historical changelog text only. ✓ (ErrorCode dropped cleanly, changelog notes it.)
  • grep -rn 'ORCHESTRATOR_COMPONENTS' src/ tests/ → single hit in changelog.py history text; removed from sync/config_format.py. ✓
  • grep 'component_id' src/keboola_agent_cli/server/routers/flows.py → empty; FlowCreate/FlowUpdate/FlowSchedule models no longer carry component_id. ✓
  • grep 'since.*0\.56\|0\.56\.0' plugins/.../gotchas.md → 9 tagged entries covering CF-only migration, string IDs, dropped orchestrator, validate semantics, live schema fetch, graceful degradation, --component-id removal. ✓
  • wc -l src/keboola_agent_cli/commands/flow.py → 946 lines (soft ceiling 800, hard ceiling 1200). Crossing soft ceiling → NB-2.
  • grep '-> tuple' src/keboola_agent_cli/services/flow_service.py (new additions) → _fetch_flow_schema and fetch_flow_schema both return tuple[dict|None, str|None] added in this PR → NB-1.
  • uv sync --extra server && make check3866 passed, 8 skipped, 12 warnings in 81.95s; ruff + format + ty + SKILL + version + command-sync + changelog + error-codes all green. ✓
  • flow validate exit code: 0 on valid (warnings still printed), exit 2 on errors — confirmed in commands/flow.py:516-517. ✓
  • flow list dual-mode legacy warning: JSON passes legacy_orchestrator_count in result dict (programmatically readable); human mode prints formatter.warning(...) via _format_flows_table. ✓
  • flow validate JSON shape {valid, errors, warnings, notes} confirmed at commands/flow.py:505. Matches PR description claim. ✓
  • Behavior reproduction against real API: not possible without live credentials; the E2E suite at tests/test_e2e.py:5185 (CF round-trip) and 5342 (validation rejects invalid) is the substitute and is present.

Open questions for the author

(none)

ondrajodas and others added 6 commits June 4, 2026 22:57
--component-id removed from all flow subcommands and the /flows REST
surface; orchestrator configs dropped from flow list (legacy count
warning). Payload validation runs against the live conditional-flow JSON
Schema fetched at runtime from the AI Service component registry (never
bundled), with semantic-only graceful degradation when the fetch fails.
New flow validate [--project] and flow schema --full. Detail rendering
rewritten for conditional flows. INVALID_FLOW_DAG renamed to
INVALID_FLOW_DEFINITION. Docs and plugin surfaces synced.
- add POST /flows/validate and GET /flows/{project}/schema REST routes
  to keep the 1:1 CLI-to-REST parity for the new flow commands; routes
  are declared before the path-param routes so literal segments win
- add router call tests for both endpoints (incl. route-ordering guards
  and the fetch-failure degrade/502 paths)
- document why the broad except in _fetch_flow_schema is intentional
  (httpx errors do not subclass OSError; any failure must degrade)
- use ErrorCode.NOT_FOUND enum instead of raw string in flow_service
- hoist deferred json/rich.syntax imports to module top in commands/flow.py
- schedule-workflow.md: switch example target to keboola.flow and note
  legacy orchestrator schedules still appear in schedule list
Follow-up to the review-fix commit: the deferred import inside
flow_validate() was the last remaining one; flow_validation is pure
(stdlib + jsonschema), so module-level import is circular-import safe.
TestPullJobsFallback asserted MagicMock.call_count on a path where
_fetch_jobs_per_config calls the mock from a ThreadPoolExecutor.
MagicMock.call_count is incremented without a lock (read-modify-write),
so concurrent calls lose updates -- reproducible locally with a lowered
sys.setswitchinterval (30/30 runs lose increments) and surfaced in CI
where coverage tracing widens the race window (observed 174 != 200).

call_args_list.append IS atomic under the GIL, so assert on
len(call_args_list) instead; the 200-config test additionally checks
the distinct (component_id, config_id) pairs to guarantee exactly-once
fetching. Production code is unaffected (results were already collected
under an explicit lock).
'3 legacy keboola.orchestrator flow(s) ... migrate to keboola.flow' read
like internal component IDs; reword to product terminology: 'Legacy
Flows were dropped in 0.56.0; migrate them to Conditional Flows'.
main shipped v0.56.0 as a maintenance re-release (#399) — claiming the 0.56.0 version, git tag, and GitHub Latest release — to push the 0.55.0 reference-data commands through the auto-update version gate. The conditional-flows work therefore moves to 0.57.0.

Bumps pyproject + plugin.json + marketplace.json + uv.lock, and rewrites every (since v0.56.0) doc tag to v0.57.0 across CLAUDE.md, README, keboola-expert.md, gotchas.md, commands-reference.md, flow/schedule workflows, AGENT_CONTEXT, and code comments. The changelog 0.57.0 key carries the conditional-flows entry; the 0.56.0 maintenance entry is preserved.
@padak padak force-pushed the ondra/AJDA-2813 branch from 2f62e38 to 0a950ea Compare June 4, 2026 21:15
Addresses self-review findings on the conditional-flows work:

- Replace the (schema, reason) tuple from FlowService._fetch_flow_schema / fetch_flow_schema with a frozen FlowSchemaFetch dataclass (CONTRIBUTING.md forbids new semantically-distinct tuple returns). Updates all six src call sites (flow_service, flows router, flow command) and the tuple mocks in test_flow_service / test_flow_cli / test_server_router_calls.

- Add a structural integer-id rejection test (test_flow_validation): an integer id is rejected only when the live schema is supplied; without a schema the semantic checks coerce it and stay silent.

- Add router-level tests that POST/PATCH /flows/{project} call create_flow / update_flow WITHOUT component_id, proving the orchestrator-drop is enforced at the REST layer (FlowCreate/FlowUpdate silently drop a legacy component_id).
@padak

padak commented Jun 4, 2026

Copy link
Copy Markdown
Member

Rebased onto main + retargeted to 0.57.0

Heads-up on the force-push to this branch (done while preparing it for merge):

Version 0.56.0 → 0.57.0. While this PR was open, main shipped v0.56.0 as a maintenance re-release (#399) — it claimed the 0.56.0 version, the v0.56.0 git tag, and the GitHub "Latest" release (to push the 0.55.0 reference-data commands through the auto-update version gate). Conditional flows therefore can't ship as 0.56.0: the changelog.py key collides, and auto-update's Version(local) >= Version(latest) check would never deliver it to users already on 0.56.0. Retargeted to 0.57.0 across pyproject.toml + plugin.json + marketplace.json + uv.lock, the changelog key, and every (since v0.56.0) doc tag (CLAUDE.md, README, keboola-expert.md, gotchas.md, commands-reference.md, flow/schedule workflows, AGENT_CONTEXT, code comments). The 0.56.0 maintenance changelog entry is left untouched.

Rebase. Branch was 5 behind main; the only conflict was the duplicate 0.56.0 changelog key, resolved by splitting into 0.57.0 (this work, top) + 0.56.0 (maintenance, preserved). tests/test_e2e.py auto-merged cleanly — both the non-US/keypair/branched-storage robustness from main and the conditional-flow round-trip + flow validate from this branch are present.

Three self-review findings addressed (commit d0f251c):

  • Replaced the (schema, reason) tuple returned by _fetch_flow_schema / fetch_flow_schema with a frozen FlowSchemaFetch dataclass (CONTRIBUTING.md forbids new semantically-distinct tuple returns); updated all 6 call sites + the tuple mocks in the three flow test files.
  • Added a structural integer-id rejection test in test_flow_validation.py (integer id rejected only when the live schema is supplied; semantic-only path coerces and stays silent).
  • Added router-level tests that POST/PATCH /flows/{project} call create_flow/update_flow without component_id (the orchestrator drop is enforced at the REST layer too — a legacy component_id in the body is silently dropped by the Pydantic model).

make check is green: 3870 passed, 8 skipped. No behavioral change to the conditional-flows feature itself — only the version retarget and the dataclass/test cleanups.

padak added a commit that referenced this pull request Jun 4, 2026
CONTRIBUTING.md requires semantically-distinct multi-value returns to use a @DataClass, not a bare tuple. That rule is subjective ('semantically distinct'), so unlike the deterministic error_code check it cannot be a CI gate -- it slipped past CI to an LLM/human review on #387 (the FlowSchemaFetch finding).

Adds to the reviewer's Step 3.8 a grep that surfaces '-> tuple[...]' return annotations added in the diff (skipping variadic 'tuple[X, ...]'), plus a judging rubric: ignore the ~63 grandfathered returns and the BaseService parallel-result shape; flag a newly-added heterogeneous tuple of semantically-distinct values as NON-BLOCKING with a @DataClass recommendation.

@padak padak left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review of #387 — [AJDA-2813] Conditional flows support, drop orchestrator (0.57.0)

Generated by kbagent-pr-reviewer subagent. Verdict and findings below
are advisory; the human author retains every veto. CI-coverable issues
(lint, format, tests) are confirmed via make check, not duplicated here.

Summary

This is a well-executed breaking-change PR that rewrites the flow command group
around the Conditional Flow component (keboola.flow), drops keboola.orchestrator
support, adds flow validate and flow schema --full, and ships a live-schema
validation path for flow new/flow update. All silent-drift surfaces from the
Plugin synchronization map are updated (AGENT_CONTEXT, CLAUDE.md, keboola-expert.md,
SKILL.md, commands-reference.md, gotchas.md, flow-workflow.md), the command-sync gate
passes (229 commands registered), and make check is green (3870 passed). Two
NON-BLOCKING findings and two NITs; no blocking issues. Verdict: APPROVE.

Verdict

  • Verdict: APPROVE
  • Blocking findings: 0
  • Non-blocking findings: 2
  • Nits: 2

Blocking findings

(none)

Non-blocking findings

[NB-1] docs/build-your-own-client.md:269 — stale INVALID_FLOW_DAG error code in public-facing docs

The ErrorCode.INVALID_FLOW_DAG enum member was removed in this PR and replaced by
INVALID_FLOW_DEFINITION. docs/build-your-own-client.md is the project's
public "third-party client" guide and still lists INVALID_FLOW_DAG as a
domain-specific code (line 269: ``INVALID_FLOW_DAG, …). A downstream developer
reading this doc will implement against a code that no longer exists and will have
a silent no-match on error handling. Fix by replacing `INVALID_FLOW_DAG` with
`INVALID_FLOW_DEFINITION` in that sentence. This file is not CI-checked and was not
in the modified files list.

[NB-2] src/keboola_agent_cli/services/flow_service.py:73-75_count_phases_tasks is dead code with a banned tuple return

The helper _count_phases_tasks (returns tuple[int, int]) was defined but the two
actual callsites (list_flows lines 358-359 and get_flow_detail lines 428-429)
compute len(phases) and len(tasks) directly instead of calling it. The function
is only exercised in test_flow_service.py:130,133, which tests the helper in
isolation. Per CONTRIBUTING.md "Code Quality Patterns", two-element tuples should use
a @dataclass when the values are semantically distinct (phase count vs. task count
clearly are). Since the function is effectively unused in production paths, the
simplest fix is either (a) delete it and the two test lines, or (b) convert to a
dataclass PhaseTaskCounts and actually route the callsites through it so the name
pays off. Option (a) is faster; option (b) is the intended convention path.

Nits

  • [NIT-1] src/keboola_agent_cli/commands/flow.py is at 948 LOC, approaching the
    800-LOC soft ceiling for commands/*.py. Not blocking now, but the next PR that
    adds materially to this file should split first (e.g. extract the validate/schema
    commands or the Rich-rendering helpers into a _flow_render.py sibling).

  • [NIT-2] PR title and description say "(0.56.0)" but the actual version bump is
    to 0.57.0 (pyproject.toml, changelog.py, gotchas.md, keboola-expert.md
    all correctly say 0.57.0). The description also says "Version bump to 0.56.0".
    This is cosmetic (the code is right; the PR description is stale text from an
    intermediate draft), but worth correcting before merge so the GitHub search history
    isn't misleading.

Verification log

  • gh pr view 387 --json title,body,files,additions,deletions,baseRefName,headRefName,labels,state → 34 files, +5039/-1612, state=OPEN ✓
  • git rev-parse --abbrev-ref HEADondra/AJDA-2813 (worktree checked out via gh pr checkout 387) ✓
  • 3-layer compliance: grep -E '(from typer|import typer|formatter\.|console\.print)' diff | grep services/ → empty ✓; grep -E '(from httpx|import httpx)' diff | grep commands/ → empty ✓
  • Plugin synchronization map (all "NO" rows walked):
    • src/keboola_agent_cli/commands/context.py AGENT_CONTEXT → flow validate, flow schema --full present under ### Flows heading ✓
    • CLAUDE.md ## All CLI Commandsflow schema [--full --project NAME] and flow validate --file @flow.yaml|- [--project NAME] both present ✓
    • permissions.py OPERATION_REGISTRY → flow.validate: read added (line 291) ✓; all 9 flow entries present ✓
    • keboola-expert.md §2 Tool Selection Matrix → flow row updated with flow validate as First choice, --component-id and keboola.orchestrator in NEVER column ✓; §3 Inline Gotchas → since 0.57.0 gotcha block present ✓
    • plugins/kbagent/skills/kbagent/references/commands-reference.mdflow validate and flow schema [--full] present under ## Flows section ✓
    • plugins/kbagent/skills/kbagent/references/gotchas.md → 9× (since v0.57.0) version tags on all new CF gotchas ✓
    • plugins/kbagent/skills/kbagent/references/flow-workflow.md → full rewrite for CF; flow validate workflow at lines 96 and 161 ✓
    • SKILL.mdmake skill-check → "SKILL.md is up-to-date" ✓
    • uv run python scripts/check_command_sync.py → "OK: all 229 CLI commands are registered" ✓
  • Layer violations: none found ✓
  • Magic numbers / raw error_code strings in production code: "UNEXPECTED_ERROR" at flow_service.py:300 is a pre-existing grandfathered pattern present in base.py:163, branch_service.py:73, etc. -- not introduced by this PR ✓; all new KeboolaApiError raises use ErrorCode.INVALID_FLOW_DEFINITION
  • INVALID_FLOW_DAG removal: removed from ErrorCode; still present in docs/build-your-own-client.md:269 (NB-1) and in historical changelog.py entries (acceptable per design doc) ✓
  • FlowSchemaFetch return type: @dataclass(frozen=True) at flow_service.py:39-42, not a tuple ✓
  • _count_phases_tasks tuple return: tuple[int, int] at line 73, function is defined but unused in production paths; tested in isolation (NB-2)
  • keboola-expert.md byte budget: 48840 bytes vs 62000-byte hard cap ✓ (13160 bytes headroom)
  • flow.py size: 948 LOC vs 800 soft / 1200 hard ceiling -- approaching soft ceiling (NIT-1) ✓
  • flow_service.py size: 774 LOC vs 1000 soft ceiling ✓
  • Server router (server/routers/flows.py): POST /flows/validate and GET /flows/{project}/schema added; component_id field silently dropped by Pydantic for backward compat; test test_flows_create_drops_component_id covers this ✓; all 9 CLI flow subcommands have matching REST routes ✓
  • make check: 3870 passed, 8 skipped, 14 warnings (all pre-existing asyncio coroutine warnings from MCP tests) -- exit 0 ✓
  • Backward compat -- exit codes: INVALID_FLOW_DAG removed from ErrorCode; not found in any production code path in src/ -- only in changelog.py historical text, errors.py comment, and docs/build-your-own-client.md ✓ (compat break is intentional and changelog-announced)
  • Token discipline: no new token exposure in flow_service, flow_validation, commands/flow ✓
  • E2E tests: test_flow_crud_and_schedule, test_flow_validation_rejects_invalid_definition, test_flow_list_no_project_returns_all, test_flow_list_with_schedules all present; flow validate exercised with and without --project
  • Behavior reproduction: not run against a live project (E2E credentials not available in reviewer environment); see E2E test suite for coverage

Open questions for the author

(none)

- docs/build-your-own-client.md: INVALID_FLOW_DAG -> INVALID_FLOW_DEFINITION. The error code was renamed in this PR; the client-docs example still referenced the removed name (docs/ was outside the original rename sweep, which only grepped src/ + tests/).

- Remove the dead _count_phases_tasks helper from flow_service.py: the conditional-flows rewrite orphaned it (zero src callers, exercised only by an isolated test) and it carried a banned tuple[int, int] return. Drops its two tests too.
@padak padak changed the title [AJDA-2813] Conditional flows support, drop orchestrator (0.56.0) [AJDA-2813] Conditional flows support, drop orchestrator (0.57.0) Jun 4, 2026
@padak

padak commented Jun 4, 2026

Copy link
Copy Markdown
Member

Addressed the latest review's findings in 76a34ab, plus a PR-metadata fix:

  • NB-1docs/build-your-own-client.md error-code example INVALID_FLOW_DAGINVALID_FLOW_DEFINITION. The rename's docs/ leg was missed by the original sweep, which only grepped src/ + tests/.
  • NB-2 — removed the dead _count_phases_tasks helper from flow_service.py. The conditional-flows rewrite orphaned it (zero src/ callers, exercised only by an isolated test) and it carried a banned tuple[int, int] return. Dropped its two tests too.
  • NIT-2 — PR title + description retargeted 0.56.0 → 0.57.0. 0.56.0 was claimed by the maintenance re-release (chore(release): 0.56.0 — maintenance re-release (redo after #389 merge collision) #399) while this PR was open, so the conditional-flows work ships as 0.57.0 (the changelog/pyproject/doc tags were already correct; only the GitHub title/body were stale).

Left as a follow-up (not in this PR):

  • NIT-1commands/flow.py is ~948 LOC, nearing the 800-LOC soft ceiling. The next PR that adds materially to it should split first (e.g. extract the validate/schema rendering helpers).

make check green (3868 passed, 8 skipped). Branch tip 76a34ab.

@padak padak merged commit 269c163 into main Jun 4, 2026
4 checks passed
@padak padak deleted the ondra/AJDA-2813 branch June 4, 2026 22:22
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