Skip to content

chore(kai): deprecate the legacy kai command group (0.77.0) - #538

Draft
zajca wants to merge 1 commit into
mainfrom
chore/deprecate-legacy-kai
Draft

chore(kai): deprecate the legacy kai command group (0.77.0)#538
zajca wants to merge 1 commit into
mainfrom
chore/deprecate-legacy-kai

Conversation

@zajca

@zajca zajca commented Jul 28, 2026

Copy link
Copy Markdown
Member

What this does

Marks the kbagent kai command group DEPRECATED as of 0.77.0.

This is a deprecation, not a removal. Zero files deleted, kai-client>=0.11.0
stays in pyproject.toml, no test removed, no behavior changed. kbagent kai
keeps working exactly as it does today on static-token projects.

Why

kbagent kai * talks to the Keboola service registered under the id
kai-assistant in GET /v2/storage — the legacy KAI backend. Product has
frozen it:

  • Linear AI-3388 was canceled: product confirmed the kai-assistant
    backend is legacy and that only kai-agent will receive further work.
  • The successor is 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 kai
can never work with a kbagent auth login browser session. That is the concrete
consequence that triggered this decision, but since programmatic auth is not on
main yet, 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 query uses the AI Service, agent prompt-improve shells out to a local
CLI, 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 query for documentation
questions (AI Service RAG, any token, no project data) and the native commands
(storage tables, config detail, search, lineage show, ...) for project
data.

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:

  • human mode: a yellow Warning: on stderr only, so kai ask | jq-style
    pipelines keep a byte-clean stdout;
  • JSON mode: an additive deprecation string key on the success
    payload. Error envelopes carry no deprecation key (a failing command
    exits 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_DEPRECATION in commands/kai.py):

The kai group is deprecated: it targets the legacy kai-assistant backend,
which is frozen (Linear AI-3388 canceled), and its successor kai-agent
(AI-3391) is not wired into kbagent. The group still works against a master
Storage token but will be removed in a later minor -- there is no replacement
in the interim.

Silent-drift surfaces updated (CLAUDE.md convention #17)

Nothing was deleted from any of these — each entry gained a deprecation note.

Surface Change
src/keboola_agent_cli/commands/context.py AGENT_CONTEXT section retitled ### Kai -- Keboola AI Assistant (DEPRECATED since v0.77.0), reason + "no replacement" moved above the subcommand list. The docs query entry now flags kai ask as deprecated. Highest priority — this is what kbagent context serves and the skill loads first.
plugins/kbagent/skills/kbagent/SKILL.md Regenerated with make 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; the docs query contrast is kept and now says "prefer this over kai ask".
plugins/kbagent/skills/kbagent/references/gotchas.md New (since v0.77.0) entry at the top of the file. The existing "Web UI Kai Chat is gone" entry (whose "what stays" list promised /kai/* + the CLI commands) gained a v0.77.0 caveat. The docs query vs kai ask bullet updated.
plugins/kbagent/skills/kbagent/references/kai-workflow.md Prominent deprecation block at the top; "when to use" table rows redirected to native commands; the "common patterns" section marked do-not-suggest.
plugins/kbagent/agents/keboola-expert.md Tool-selection matrix row for docs query — its "don't use" column now spells out that the whole kai group 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.md Feature-table row + the command-overview line.
docs/web-server.md /kai/* route-table row.
docs/build-your-own-client.md /kai route-map line.
docs/error-codes.md Note that KAI_ERROR / KAI_NOT_ENABLED stay valid but the group raising them is deprecated.

Note on SKILL.md

The tool group's deprecation (0.74.0) is not visible in the generated
decision table, because scripts/generate_skill.py takes only the first
sentence of the first docstring paragraph and tool.py puts "DEPRECATED" on a
later line. Rather than reproduce that gap, each kai subcommand docstring now
starts 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, and
the kai E2E step in tests/test_e2e.py are untouched and pass unchanged.

tests/test_kai_cli.py gains TestKaiDeprecation (7 tests), modelled on
tests/test_mcp_deprecation_warnings.py:

  • ping / ask human mode: banner on stderr, result on a clean stdout;
  • ping / ask --json: additive deprecation key, pre-existing keys asserted
    byte-identical, no stderr duplication;
  • all six subcommands carry the JSON key;
  • the error envelope carries no deprecation key and keeps its exit code;
  • kai --help and all six subcommand helps announce the deprecation.

Verification

make check                    -> exit 0
  lint                        -> All checks passed!
  format-check                -> 372 files already formatted
  typecheck (ty)              -> exit 0 (52 pre-existing unresolved-import warnings only)
  skill-check                 -> SKILL.md is up-to-date
  version-check               -> version is in sync (plugin.json, marketplace.json, uv.lock)
  command-sync-check          -> OK: all 251 CLI commands registered + documented
  changelog-check             -> All 43 stable releases have changelog entries
  check-error-codes           -> OK: no raw error_code literals; docs/error-codes.md matches the enum
  test                        -> 4679 passed, 140 skipped

git diff --diff-filter=D --name-only origin/main -> empty (zero files deleted).
pyproject.toml still contains kai-client>=0.11.0.

Manual:

$ kbagent kai ping --project demo
Warning: The `kai` group is deprecated: it targets the legacy kai-assistant
backend, which is frozen (Linear AI-3388 canceled), and its successor kai-agent
(AI-3391) is not wired into kbagent. The group still works against a master
Storage token but will be removed in a later minor -- there is no replacement in
the interim.
Error: Invalid or expired token (token: 0000-...heck): Invalid access token
# exit 3 -- unchanged; the failing call is the throwaway token, the banner is the point

$ kbagent --json kai ping --project demo   # error path: no `deprecation` key
{"status": "error", "error": {"code": "INVALID_TOKEN", ...}}

$ kbagent kai --help
 (DEPRECATED) Keboola AI Assistant (Kai) — ask questions about your project.
 DEPRECATED (0.77.0): this group talks to the legacy 'kai-assistant' backend, ...

$ kbagent context | grep -A5 "### Kai"
### Kai -- Keboola AI Assistant (DEPRECATED since v0.77.0)
  DEPRECATED -- do NOT recommend this group for new work. ...

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 not
exist on main
— it lives only on the unmerged auth branch, so it is
deliberately not in this diff.

When that branch lands, add a remedy= argument to the
require_static_token(project.token, feature="kbagent kai") call site in
src/keboola_agent_cli/services/kai_service.py. The base
SessionAuthUnsupportedError message says "does not support ... yet", which
is wrong for legacy KAI — support is never coming. Use exactly:

remedy="The legacy kai-assistant backend is deprecated and will not gain programmatic-token support (Linear AI-3388 canceled); a static Storage token is the only option, and the kai group is slated for removal."

Do not change the base message — it is shared by every other guard.

Changelog / version merge note

This PR bumps pyproject.toml to 0.77.0 and adds a "0.77.0" key to
src/keboola_agent_cli/changelog.py (no historical entry edited), then
make version-sync.

Expected conflict, by design: the unreleased programmatic-auth work targets
the same 0.77.0 and 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.

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.
@zajca
zajca force-pushed the chore/deprecate-legacy-kai branch from 0ec7485 to 4fc1dd8 Compare July 28, 2026 10:14
@zajca
zajca marked this pull request as draft July 29, 2026 13:53

@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 — 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:118OutputFormatter.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 six

I applied exactly that locally (deleting all six blocks) and ran uv run pytest tests/test_kai_cli.py33 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_deprecated mutates a freshly built per-call service dict; no cached/shared object is polluted. Error paths raise before reaching it, so the "error envelopes carry no key" claim is structurally true, not incidental.
  • stderr handling is correct for click 8.4.1result.stderr is separated by default, matching tests/test_mcp_deprecation_warnings.py.
  • Raw error_code="KAI_NOT_ENABLED" in the new test is finescripts/check_error_codes.py explicitly exempts tests/, and the file already uses that form in three places.
  • keboola-expert.md — no dedicated kai matrix 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.py kai.* 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.

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