Skip to content

feat: add Kilo Code eval backend - #16

Closed
tsaqib wants to merge 3 commits into
mainfrom
feat/kilo-eval-backend
Closed

tsaqib wants to merge 3 commits into
mainfrom
feat/kilo-eval-backend

Conversation

@tsaqib

@tsaqib tsaqib commented May 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Second parallel agent backend in skills-evals, alongside the existing Claude Code path. Follows docs/internal/adding-agent-eval-backend.md end-to-end.

  • Commit 1 — feat(init): append kilo to agentTargets (.kilo/skills/, no per-agent config), bash + PowerShell e2e gain a --agents=kilo install case, reset_user_home in bash now wipes $HOME/.kilo/ between cases.
  • Commit 2 — feat(skills-evals): new kilo_driver.py (one kilo run --auto --format json per turn, multi-turn via --session <id> captured from JSON events, same reply yes confirmation gate as the Claude driver), conftest gains KILO_ENV_DEFAULTS + a per-workspace kilo.json writing an openai-compatible DeepSeek provider, 4 ported scenario tests (test_kilo_{stream_smoke,todo,plan_extends,reminders_supersedes_todo}.py), and a manual-kilo-judge.yml workflow. Existing Claude workflows get their pytest invocation scoped so they don't pick up the new kilo tests.

Why draft

Two pieces of Kilo's CLI surface are documented only at the surface level — --format json is described as "machine-readable event stream" with no public schema, and the session-resume model (--continue / --session <id>) hasn't been validated end-to-end through the auto-yes loop yet. The smoke test (test_kilo_stream_smoke.py) is the canary that pins the actual shape on a real DeepSeek run. Until that's green in CI, the driver's session-ID capture and assistant-text extraction are best-effort with extensive defensive logging.

Test plan

  • go vet ./... clean
  • go test ./... clean (parameterized tests already iterate agentTargets so no new Go unit tests required)
  • bash scripts/e2e_test.sh — 588 passed, 0 failed (incl. new --agents=kilo case)
  • pwsh scripts/e2e_test.ps1 — local PS1 run pending (no pwsh in this env); CI's windows-cli.yml will catch any drift on the PR
  • uv run pytest --collect-only collects 8 tests (4 claude + 4 kilo)
  • manual-kilo-judge.yml first run on the feat/kilo-eval-backend branch via the Actions UI after merge to main (per the doc's note that GitHub UI only registers workflows from the default branch — this PR adds the workflow but it can't be triggered from a feature branch)
  • manual-claude-judge.yml smoke re-run to confirm the pytest-scope change didn't regress the Claude path

References

tsaqib added 2 commits May 26, 2026 00:58
Append a third row to agentTargets in constants.go:

  {"kilo", "Kilo Code", ".kilo/skills", "", ""}

Kilo discovers skills natively at `.kilo/skills/` (project) and
`~/.kilo/skills/` (user) per https://kilo.ai/docs/customize/skills. It
also auto-picks-up `.agents/skills/` (Open Agent Standard) and
`.claude/skills/` (compat mode), but `.kilo/skills/` is the path it
treats as the agent's own home, so that's where `x-x init` installs.

configSrc / configRel are intentionally empty. Kilo's `kilo.jsonc` is
provider-routing config, not a hook integration surface like Claude
`settings.json` or Codex `hooks.json`. Users opt into provider routing
through Kilo's `/connect` flow or by writing `kilo.jsonc` themselves —
shipping a default would impose a model+endpoint choice on every project
init, which is the wrong policy for a config file Kilo expects users to
own.

E2e parity:
- bash + PowerShell both gain a KILO_SKILLS_REL constant and an
  `init --agents=kilo` single-agent install case asserting `.kilo/skills/`
  appears and `.claude/`, `.agents/` do not.
- `reset_user_home` in the bash harness also wipes `$HOME/.kilo/` between
  cases; without it, `.kilo/skills/` leaked from one user-scope init test
  into the next and broke the "skills remove --user is a silent no-op"
  case (kilo's leftover dir made skills-remove report a non-empty pass).
  PS1 harness already nukes the whole sandbox HOME between cases, so no
  parallel change there.

No new code-branch edits in init.go / skill.go — both already iterate
agentTargets and handle empty configSrc via the existing gate
(`if t.configSrc == ""`).

Local: go vet, go test ./... (1 pkg ok), scripts/e2e_test.sh (588 passed
0 failed).

Signed-off-by: Tanzim Saqib <[email protected]>
Second parallel agent backend after Claude Code. Follows the recipe in
docs/internal/adding-agent-eval-backend.md.

skills_evals/kilo_driver.py: drives `kilo run --auto --format json`
non-interactively. Kilo's CLI is one-shot per invocation in autonomous
mode (unlike Claude's stream-json which holds stdin open); the driver
keeps multi-turn working by re-entering with `--session <id>` (captured
from the first turn's JSON events) and the same `reply yes` confirmation
gate the Claude driver uses. One Popen per turn — simpler than Claude's
shared-pipe pump but with the same logging-everything policy. Wire
format is documented only as "machine-readable event stream"
(kilo.ai/docs/code-with-ai/platforms/cli); the parser is defensive,
session-ID capture is best-effort across several plausible event shapes,
and test_kilo_stream_smoke.py pins the actual schema once it runs.

tests/conftest.py: routing made agent-aware.
- Renames `_load_dotenv_and_route_claude` → `_load_dotenv_and_route_agents`
  and sets both CLAUDE_* and KILO_* env defaults; each CLI ignores the
  other's namespace, so always-set is safe.
- Mirrors DEEPSEEK_API_KEY into KILO_API_KEY (belt-and-suspenders for
  paths that bypass the workspace config).
- New `X_X_AGENT_UNDER_TEST` env var (default "claude") drives the
  `--agents` flag for `x-x init` and gates the per-workspace kilo.json
  write. Workspace fixture also skips if the chosen agent's CLI isn't
  on PATH (previously hard-coded to skip on missing claude).
- When the agent is kilo, the fixture writes `kilo.json` into the
  workspace declaring an openai-compatible provider pointing at
  api.deepseek.com/v1 with `{env:DEEPSEEK_API_KEY}` substitution
  (documented Kilo pattern at kilo.ai/docs/code-with-ai/agents/custom-models).
  Without this file kilo has no env-var-only route to a custom
  OpenAI-compat endpoint.

tests/test_kilo_*.py: four scenarios ported from the Claude versions
(smoke, todo, plan_extends, reminders_supersedes_todo) with the driver
import swapped. The TASK strings, judge invocations, and plan-mechanic
assertions are agent-agnostic by design — they test the AGENT'S behavior
against the skill spec, not the wire format. Each Kilo test file's
docstring matches its Claude counterpart so they read as a pair.

.github/workflows/manual-kilo-judge.yml: copies the Claude workflow's
shape (60min timeout, setup-go + setup-node + setup-uv pinned versions,
artifact upload on failure). Replaces the Anthropic env block with
KILO_PROVIDER + KILOCODE_MODEL + X_X_AGENT_UNDER_TEST=kilo, installs
`@kilocode/cli@latest` (will pin after first successful smoke run), and
scopes pytest to `tests/test_kilo_*.py` explicitly.

manual-claude-judge.yml + manual-claude-judge-user-scope.yml: pytest
invocation scoped to claude + smoke tests. Without this both Claude
workflows would now collect the new test_kilo_*.py files and try to
spawn `kilo` (not on the Claude runner's PATH).

Local: `uv run pytest --collect-only` collects all 8 tests cleanly;
import-checked kilo_driver + the conftest module independently.

Signed-off-by: Tanzim Saqib <[email protected]>
@tsaqib tsaqib mentioned this pull request May 26, 2026
3 tasks done
tsaqib added a commit that referenced this pull request May 26, 2026
Mirrors the project-scope manual-kilo-judge.yml with
X_X_INSTALL_SCOPE=user added so `x-x init` lands skills under
~/.kilo/skills/ instead of <workspace>/.kilo/skills/. Matches the
existing claude/codex/copilot patterns where every backend ships
both scope variants.

Same merge-safety story as the project-scope file: workflow_dispatch
only, depends on skills-evals files from feat/kilo-eval-backend (#16)
that aren't on main yet, no new secrets.

Signed-off-by: Tanzim Saqib <[email protected]>
Mirrors the project-scope manual-kilo-judge.yml with
X_X_INSTALL_SCOPE=user added so `x-x init` lands skills under
~/.kilo/skills/ instead of <workspace>/.kilo/skills/. Matches the
existing claude/codex/copilot patterns where every backend ships
both scope variants. Same file is being added to main via
feat/kilo-workflows-only (#17) so the two PRs land identical content
and don't conflict on merge.

The user-scope conftest path was already wired in the previous commit
on this branch — the workspace fixture reads X_X_INSTALL_SCOPE and
logs the resolved skills dir under either .kilo/skills/ (project) or
~/.kilo/skills/ (user). No code change here, workflow file only.

Signed-off-by: Tanzim Saqib <[email protected]>
tsaqib added a commit that referenced this pull request May 26, 2026
* ci: register kilo eval workflows on main

GitHub Actions registers `workflow_dispatch` triggers from the default
branch only. Until `manual-kilo-judge.yml` exists on `main`, it won't
show up in the Actions tab and `gh workflow run` will refuse it — even
when targeting the feature branch that holds the implementation. This
is the small focused PR described in
`docs/internal/adding-agent-eval-backend.md` §6, same shape as PR #12
(copilot) and PR #13 (codex).

The file is copied verbatim from `feat/kilo-eval-backend` (PR #16). No
code changes, no test changes, no doc changes.

Safe to merge now:

- `workflow_dispatch:` only — no push / pull_request / schedule
  triggers, so merging this PR cannot run anything on its own.
- The job depends on skills-evals files (`kilo_driver.py`,
  `test_kilo_*.py`, the `X_X_AGENT_UNDER_TEST`-aware conftest) that
  don't exist on `main` yet. Dispatching against `main` would fail at
  the pytest step; the workflow is only meaningful when dispatched
  with `ref=feat/kilo-eval-backend`.
- No new secrets. `DEEPSEEK_API_KEY` is already configured for the
  other manual-*-judge workflows.

After merge: open Actions, pick "manual / kilo code <- llm judge", set
**Use workflow from: feat/kilo-eval-backend**, and dispatch.

Signed-off-by: Tanzim Saqib <[email protected]>

* ci: add user-scope variant to the kilo registration

Mirrors the project-scope manual-kilo-judge.yml with
X_X_INSTALL_SCOPE=user added so `x-x init` lands skills under
~/.kilo/skills/ instead of <workspace>/.kilo/skills/. Matches the
existing claude/codex/copilot patterns where every backend ships
both scope variants.

Same merge-safety story as the project-scope file: workflow_dispatch
only, depends on skills-evals files from feat/kilo-eval-backend (#16)
that aren't on main yet, no new secrets.

Signed-off-by: Tanzim Saqib <[email protected]>

---------

Signed-off-by: Tanzim Saqib <[email protected]>
@tsaqib

tsaqib commented May 26, 2026

Copy link
Copy Markdown
Contributor Author

Abandoning the kilo eval backend. First dispatch of manual / kilo code <- llm judge showed the smoke green (kilo CLI + --format json + DeepSeek-via-openai-compat routing + driver event parser all work), but all three scenarios failed with len(plans)==0 — kilo accepts /x-plan as literal text rather than invoking the x-plan skill, and the auto-yes loop exits cleanly because no reply yes confirmation phrase ever appears in the transcript. Diagnosis between (a) slash-syntax not recognized by kilo's matcher and (b) kilo run --auto not loading .kilo/skills/ at all was inconclusive without the workflow artifact. Parking the branch (driver + tests + agent-registry row preserved) the same way #11 / feat/codex-eval-backend is parked. The two workflow files merged via #17 will be deleted from main in a follow-up.

@tsaqib tsaqib closed this May 26, 2026
tsaqib added a commit that referenced this pull request May 26, 2026
First dispatch of `manual / kilo code <- llm judge` against
feat/kilo-eval-backend showed the smoke green — kilo CLI installs,
`kilo run --auto --format json` accepts the openai-compatible DeepSeek
route declared in the per-workspace `kilo.json`, the driver's JSON event
parser handles the wire format, and the auto-yes loop terminates
cleanly. So the plumbing works.

All three scenarios (`test_kilo_todo`, `test_kilo_plan_extends`,
`test_kilo_reminders_supersedes_todo`) failed with
`len(plans)==0` — kilo "completed" each turn without ever invoking the
`x-plan` skill or writing a single plan file to `.x-plans/`. Two
plausible root causes, neither validated:

  1. `/x-plan <task>` slash syntax is Claude-Code-specific. Kilo's
     skill matcher (per kilo.ai/docs/customize/skills) keys off natural
     language — "use the X skill" — so `/x-plan` likely lands as
     literal text.
  2. `kilo run --auto` may not load `.kilo/skills/` at all. The docs
     say skills load "at session start"; whether one-shot `kilo run`
     invocations walk the same loader as interactive sessions is
     undocumented.

The auto-yes loop never triggers a plan write either way: with no skill
invocation, kilo just chats about the task and ends its turn without
the `reply yes` confirmation phrase the driver looks for. The driver
sees `completed=True`, exits clean, and the test asserts on an empty
`.x-plans/`.

Diagnosing between (1) and (2) requires the run's pytest-tmp artifact
or a local repro against `npm install -g @kilocode/cli`. Both are
multi-step rabbit holes for an integration whose value proposition
isn't worth that follow-up right now. Removing the workflow files so
they stop appearing in the Actions UI as broken.

Same shape as #20 (codex workflows removal). The driver, conftest
changes, agent-registry row in `constants.go`, and the four
`test_kilo_*.py` files on `feat/kilo-eval-backend` (closed in #16) are
preserved-but-parked; pick them up if the kilo invocation question
gets answered.

Signed-off-by: Tanzim Saqib <[email protected]>
@tsaqib
tsaqib deleted the feat/kilo-eval-backend branch May 28, 2026 02:25
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