chore(kai): deprecate the legacy kai command group (0.77.0) - #538
Conversation
The `kbagent kai` group talks to the legacy `kai-assistant` backend, which
product has frozen. Linear AI-3388 ("Support PAT tokens in KAI Assistant BFF")
was canceled, with the Kai PO stating "kai-assistant is the old one" and
"Not relevant. Only kai-agent will be handled." The successor `kai-agent`
(Linear AI-3391) is a different API surface and is not wired into kbagent;
retargeting it is a separate, future task.
This is a deprecation, not a removal. Nothing is deleted: all six subcommands,
the `/kai/*` serve routes, the `kai-client` dependency, and every existing test
stay exactly as they are. Behavior, exit codes, and API calls are unchanged --
`kbagent kai` keeps working against a master Storage token with the `agent-chat`
feature. Removal is planned for a later minor, and there is no replacement in
the interim: use `kbagent docs query` for documentation questions and the
native commands for project data.
The deprecation is surfaced exactly the way the `tool` group's is
(commands/tool.py): a yellow `Warning:` on stderr in human mode (stdout stays
byte-clean for piping) and an additive `deprecation` string key on the `--json`
success payload. Error envelopes carry no `deprecation` key, and no existing
key was renamed or removed, so scripts parsing `kbagent --json kai ...` today
keep working.
Silent-drift surfaces updated per CLAUDE.md convention #17: context.py
AGENT_CONTEXT (loaded by the skill as its first step), CLAUDE.md command list,
keboola-expert.md tool-selection matrix, commands-reference.md, gotchas.md (new
`(since v0.77.0)` entry plus a caveat on the existing "Kai Chat is gone" entry),
kai-workflow.md, README.md, docs/web-server.md, docs/build-your-own-client.md,
docs/error-codes.md. SKILL.md's CI-checked decision table was regenerated with
`make skill-gen` and now carries `(DEPRECATED)` on all six rows.
0ec7485 to
4fc1dd8
Compare
padak
left a comment
There was a problem hiding this comment.
Review — chore(kai): deprecate the legacy kai command group (0.77.0)
Verdict: good change, well-scoped, ready to merge after a rebase. The deprecation is genuinely additive — zero deletions, zero behavior changes, and it reuses the tool-group mechanism verbatim instead of inventing one. The silent-drift surfaces from CLAUDE.md convention #17 are all covered. Findings below: 1 blocking (mechanical), 2 non-blocking, 5 nits.
Verification I ran myself
Checked out 4fc1dd8 into a clean detached worktree (uv sync --extra server):
| Check | Result |
|---|---|
uv run pytest tests/ |
4679 passed, 140 skipped — matches the PR body exactly |
make lint / format-check |
pass (372 files) |
make skill-check |
SKILL.md is up-to-date |
make version-check |
in sync (plugin.json, marketplace.json, uv.lock) |
make command-sync-check |
OK, 251 commands |
make check-error-codes |
OK |
make changelog-check |
FAILS — see finding 1 |
git diff --diff-filter=D --name-only origin/main |
empty (zero deletions confirmed) |
kbagent kai --help |
renders the deprecation banner correctly, all six subcommands show (DEPRECATED) |
tests/test_e2e.py kai steps |
safe — all kai E2E calls go through _run(), which always passes --json, so the stderr banner never reaches json.loads(result.output) |
I also confirmed the mechanism against output.py:118 — OutputFormatter.warning is stderr-only and a documented no-op in JSON mode, so the stdout-stays-byte-clean claim holds.
🔴 1. Branch is stale — mergeStateStatus: DIRTY, and make changelog-check fails as-is
src/keboola_agent_cli/changelog.py, pyproject.toml, uv.lock, plugins/kbagent/.claude-plugin/plugin.json, .claude-plugin/marketplace.json
The branch forked at fe662ed3 and bumps 0.76.2 → 0.77.0, but main has since shipped 0.76.3 (#541). A trial merge conflicts in exactly those five files.
That staleness also makes make changelog-check fail on the branch right now:
Missing changelog entries for: 0.76.3 (6 pre-release(s) skipped)
The script audits gh release list against CHANGELOG, and v0.76.3 is tagged upstream while this branch's changelog.py predates it. This is not a defect in the diff — a rebase onto main resolves both the conflicts and the check. Flagging it because changelog-check is a local-only gate (CI runs lint/format/skill/version/tests only), so green CI will not catch it for you.
The PR body's merge note anticipates the conflict with the unmerged programmatic-auth branch, but not this one with released main. After rebase the bump reads 0.76.3 → 0.77.0, which is still correct for a deprecation (the tool group deprecation was likewise a minor, 0.74.0).
🟡 2. /kai/* REST routes are documented as deprecated, but nothing in the code marks them
src/keboola_agent_cli/server/routers/kai.py:12
Four surfaces in this diff now tell readers the serve routes are deprecated — docs/web-server.md, docs/build-your-own-client.md, the new gotchas.md entry ("The /kai/* REST routes on kbagent serve are deprecated too"), and the CLAUDE.md comment ("The group and the serve /kai/* routes will be removed in a later minor"). But the router returns registry.kai.* verbatim and never touches commands/kai.py, so a REST consumer — the SPA, kbagent http, or anyone following build-your-own-client.md — receives no signal on any path: no deprecation key, no OpenAPI deprecated flag, no Deprecation/Sunset header.
Given CONTRIBUTING's 1:1 CLI↔REST convention, and that build-your-own-client.md is aimed precisely at the people who would keep building on these routes, a one-liner would close the gap:
router = APIRouter(prefix="/kai", tags=["kai"], deprecated=True)FastAPI propagates that to every route in the OpenAPI schema, so /docs and every generated client mark it. Non-blocking — the docs do carry the message — but code-level marking is what a generated client actually sees.
🟡 3. In JSON mode the deprecation is delivered only on success — the least likely path for this group
src/keboola_agent_cli/commands/kai.py:62-79
_output_deprecated injects the key into the success payload, and formatter.warning is a no-op in JSON mode. So a --json consumer that hits an error envelope gets nothing at all: no key, no stderr line.
For the tool group that was fine. For kai it is the common case, not the edge case: the group requires a master Storage token plus the agent-chat flag, and your own gotchas entry says that gate is now permanent — so the modal outcome for a scripted kbagent --json kai ... is KAI_NOT_ENABLED, which under this design never learns it is calling a doomed command.
The tool-group precedent is a real argument for keeping it as-is, and I would not block on it. But writing the banner to formatter.err_console unconditionally (stdout stays valid JSON either way) would guarantee delivery on every exit path. If you keep the current behavior, it is worth one sentence in gotchas.md saying error envelopes are silent — right now that entry frames "error envelopes carry no deprecation key" as a compatibility guarantee rather than as a delivery gap.
🟢 4. Wrong cross-reference in gotchas.md
plugins/kbagent/skills/kbagent/references/gotchas.md:674
/kai/*REST routes are now DEPRECATED (see the next entry).
The new deprecation entry is at the top of the file (line 14). The actual next entry (line 687) is "Dashboard ▶ run button on scheduled agents…". Should read "see the first entry" / "see the kai group deprecation entry above". The reciprocal pointer in the new entry ("See also the 'Web UI Kai Chat is gone' entry below") is correct. Small, but this file is read verbatim by agents.
🟢 5. Six identical warn call-sites could be one — verified
src/keboola_agent_cli/commands/kai.py:80-82, plus lines 107, 161, 215, 264, 327, 385
The same formatter.warning(KAI_DEPRECATION) call, each preceded by the same three-line comment, is copy-pasted into all six subcommands. The group already has a callback that runs before every subcommand:
@kai_app.callback(invoke_without_command=True)
def _kai_permission_check(ctx: typer.Context) -> None:
check_cli_permission(ctx, "kai")
get_formatter(ctx).warning(KAI_DEPRECATION) # one site instead of sixI applied exactly that locally (deleting all six blocks) and ran uv run pytest tests/test_kai_cli.py → 33 passed, including the new TestKaiDeprecation stdout-cleanliness and help-text assertions. Net −18 lines of duplicated comment and one place to edit at removal time. _output_deprecated still has to stay per-command for the JSON key, so the split is: callback = human surface, _output_deprecated = JSON surface.
Your call — the current form is explicit and mirrors tool.py, so it is defensible as-is.
🟢 6. "not wired into kbagent yet" contradicts "do not assume it will migrate"
src/keboola_agent_cli/commands/kai.py:52-53
Group help says the successor "is not wired into kbagent yet", implying migration is queued. gotchas.md:25 says "Do not assume kbagent kai will migrate to it." Both are AI-facing; agents will quote whichever they hit. Dropping "yet" from the help text (the module docstring at line 11 and the KAI_DEPRECATION banner at line 43 already avoid it) makes the three consistent.
🟢 7. preflight loses its token requirement from the generated SKILL.md row
src/keboola_agent_cli/commands/kai.py:243
The generated row went from "Check whether the configured token can use Kai (master token + AI Agent Chat)" to "…can use Kai" because generate_skill.py takes the first docstring sentence. You noted the generator quirk in the PR body, but here it is recoverable — the parenthetical fits under the 100-char limit:
"""(DEPRECATED) Check whether the configured token can use Kai (master token + AI Agent Chat).98 chars including indent and quotes. That keeps the decision table as informative as it was pre-0.77.0.
🟢 8. services/kai_service.py module docstring untouched
src/keboola_agent_cli/services/kai_service.py:1
The service is the layer both the CLI and the REST routes go through, so it is the natural place for the next reader to land. A one-line "the CLI group and /kai/* routes that use this are deprecated since 0.77.0 (legacy kai-assistant backend frozen)" would save them a hop. Optional.
What I checked and found clean
- Additive-only contract holds.
_output_deprecatedmutates a freshly built per-call service dict; no cached/shared object is polluted. Error pathsraisebefore reaching it, so the "error envelopes carry no key" claim is structurally true, not incidental. stderrhandling is correct for click 8.4.1 —result.stderris separated by default, matchingtests/test_mcp_deprecation_warnings.py.- Raw
error_code="KAI_NOT_ENABLED"in the new test is fine —scripts/check_error_codes.pyexplicitly exemptstests/, and the file already uses that form in three places. keboola-expert.md— no dedicatedkaimatrix row existed to update (kai only ever appeared in the docs-query NEVER column), so the single-row edit is the right scope. File is 59,148 B, under the 62,000 B cap.permissions.pykai.*operation classifications correctly left alone — deprecated is not removed.
Nice work on the "deprecate without touching behavior" discipline: keeping kai-client in pyproject.toml, leaving test_kai_service.py / test_server_smoke.py / the E2E step untouched, and pinning the pre-0.77.0 contract with the existing tests is exactly the right shape for this kind of change.
What this does
Marks the
kbagent kaicommand group DEPRECATED as of0.77.0.This is a deprecation, not a removal. Zero files deleted,
kai-client>=0.11.0stays in
pyproject.toml, no test removed, no behavior changed.kbagent kaikeeps working exactly as it does today on static-token projects.
Why
kbagent kai *talks to the Keboola service registered under the idkai-assistantinGET /v2/storage— the legacy KAI backend. Product hasfrozen it:
kai-assistantbackend is legacy and that only
kai-agentwill receive further work.kai-agent(kai-agent.{stack_suffix}, a Hono server),tracked in Linear AI-3391. It is a different API surface; retargeting
kbagent at it is a separate, future task and explicitly out of scope here.
Reviewer context (not user-facing in this PR — see "Follow-up" below): because
the legacy backend will never gain programmatic-token (PAT) support,
kbagent kaican never work with a
kbagent auth loginbrowser session. That is the concreteconsequence that triggered this decision, but since programmatic auth is not on
mainyet, none of the user-facing text in this diff references it.The deprecation is self-contained: nothing else in the CLI depends on KAI.
docs queryuses the AI Service,agent prompt-improveshells out to a localCLI, MCP is independent, and the React SPA replaced its Kai tile with the
"Local AI" page back in v0.41.9.
Removal is planned for a later minor, and there is no replacement in the
interim. Users should reach for
kbagent docs queryfor documentationquestions (AI Service RAG, any token, no project data) and the native commands
(
storage tables,config detail,search,lineage show, ...) for projectdata.
How the deprecation is surfaced
Copied verbatim from the established in-repo pattern in
src/keboola_agent_cli/commands/tool.py(epic #390) — no new mechanism invented:Warning:on stderr only, sokai ask | jq-stylepipelines keep a byte-clean stdout;
deprecationstring key on the successpayload. Error envelopes carry no
deprecationkey (a failing commandexits before the key is injected).
No existing key was renamed or removed, no exit code changed, no API call
changed. A script parsing
kbagent --json kai ...today keeps working.Banner text (
KAI_DEPRECATIONincommands/kai.py):Silent-drift surfaces updated (CLAUDE.md convention #17)
Nothing was deleted from any of these — each entry gained a deprecation note.
src/keboola_agent_cli/commands/context.pyAGENT_CONTEXTsection retitled### Kai -- Keboola AI Assistant (DEPRECATED since v0.77.0), reason + "no replacement" moved above the subcommand list. Thedocs queryentry now flagskai askas deprecated. Highest priority — this is whatkbagent contextserves and the skill loads first.plugins/kbagent/skills/kbagent/SKILL.mdmake skill-gen. All six decision-table rows now read(DEPRECATED) ...— the deprecation does flow into the CI-checked table (see note below).plugins/kbagent/skills/kbagent/references/commands-reference.md## Kai ...section header + a deprecation paragraph; thedocs querycontrast is kept and now says "prefer this overkai ask".plugins/kbagent/skills/kbagent/references/gotchas.md(since v0.77.0)entry at the top of the file. The existing "Web UIKai Chatis gone" entry (whose "what stays" list promised/kai/*+ the CLI commands) gained a v0.77.0 caveat. Thedocs queryvskai askbullet updated.plugins/kbagent/skills/kbagent/references/kai-workflow.mdplugins/kbagent/agents/keboola-expert.mddocs query— its "don't use" column now spells out that the wholekaigroup is deprecated.CLAUDE.md## All CLI Commands# kai group DEPRECATED (0.77.0+)comment block, styled like the existing# tool group DEPRECATED (0.74.0+, epic #390)note.README.mddocs/web-server.md/kai/*route-table row.docs/build-your-own-client.md/kairoute-map line.docs/error-codes.mdKAI_ERROR/KAI_NOT_ENABLEDstay valid but the group raising them is deprecated.Note on SKILL.md
The
toolgroup's deprecation (0.74.0) is not visible in the generateddecision table, because
scripts/generate_skill.pytakes only the firstsentence of the first docstring paragraph and
tool.pyputs "DEPRECATED" on alater line. Rather than reproduce that gap, each
kaisubcommand docstring nowstarts with
(DEPRECATED), so the CI-checked table carries it. Same generator,same mechanism — the rows just say what they should.
Tests
Additive only —
tests/test_kai_service.py,tests/test_server_smoke.py, andthe
kaiE2E step intests/test_e2e.pyare untouched and pass unchanged.tests/test_kai_cli.pygainsTestKaiDeprecation(7 tests), modelled ontests/test_mcp_deprecation_warnings.py:ping/askhuman mode: banner on stderr, result on a clean stdout;ping/ask--json: additivedeprecationkey, pre-existing keys assertedbyte-identical, no stderr duplication;
deprecationkey and keeps its exit code;kai --helpand all six subcommand helps announce the deprecation.Verification
git diff --diff-filter=D --name-only origin/main-> empty (zero files deleted).pyproject.tomlstill containskai-client>=0.11.0.Manual:
Follow-up for whoever lands the programmatic-auth branch
The PAT/session guard (
require_static_token,SessionAuthUnsupportedError,AUTH_NOT_SUPPORTED_ON_STACK,tests/test_auth_sentinel_guards.py) does notexist on
main— it lives only on the unmerged auth branch, so it isdeliberately not in this diff.
When that branch lands, add a
remedy=argument to therequire_static_token(project.token, feature="kbagent kai")call site insrc/keboola_agent_cli/services/kai_service.py. The baseSessionAuthUnsupportedErrormessage says "does not support ... yet", whichis wrong for legacy KAI — support is never coming. Use exactly:
Do not change the base message — it is shared by every other guard.
Changelog / version merge note
This PR bumps
pyproject.tomlto0.77.0and adds a"0.77.0"key tosrc/keboola_agent_cli/changelog.py(no historical entry edited), thenmake version-sync.Expected conflict, by design: the unreleased programmatic-auth work targets
the same
0.77.0and already has a"0.77.0"changelog key on its branch.Whoever merges second should fold their bullets into the same
"0.77.0"list rather than creating a second key.