Skip to content

feat: Kimi Code CLI host support — skills + lifecycle hooks via extra_skill_dirs and hook wrappers (working setup attached) #30

Open

Description

@lx-0

The problem

The operator runs the same ytstack-tracked projects (e.g. meinkontext, active .ytstack/ with M034 pointer) from four agents: Claude Code, Codex, Kimi Code and OpenCode (#28). ytstack has official adapters for the first two; Kimi Code CLI had no documented path, so a Kimi session started with zero ytstack context — no using-ytstack directive, no STATE.md snapshot, no greenfield routing — the exact "remind me what we were working on" ritual the SessionStart hook exists to prevent.

Motivating session: 2026-11-09, Kimi Code CLI in a .ytstack/ project, ytstack v0.4.2 (pinned checkout), macOS.

What works today (verified 2026-11-09)

Kimi Code CLI offers three integration surfaces; all three needed only thin wiring:

  1. Skills — native, zero adaptation. Kimi scans user-declared extra_skill_dirs (config.toml) for agentskills-style SKILL.md trees and hot-reloads the catalog mid-session (verified: all 31 v0.4.2 skills appeared in the running session's catalog within a minute). The extra ytstack frontmatter fields (tier, version, allowed-tools, user-invocable, kind) are ignored gracefully; only name + description matter, and ytstack's description-driven selection maps 1:1 onto Kimi's model-invoked skills.

  2. Lifecycle hooks — three user-level wrappers. Kimi's hook system has SessionStart, PreCompact, SessionEnd events whose stdin payload ({hook_event_name, session_id, cwd, ...}, snake_case) is field-compatible with the hooks' existing stdin contract. Config in ~/.kimi-code/config.toml:

extra_skill_dirs = ["~/.kimi-code/ytstack/skills"]

[[hooks]]
event = "SessionStart"
command = "bash /Users/alex/.kimi-code/hooks/ytstack-session-start.sh"
timeout = 15

[[hooks]]
event = "PreCompact"
command = "bash /Users/alex/.kimi-code/hooks/ytstack-pre-compact.sh"
timeout = 10

[[hooks]]
event = "SessionEnd"
command = "bash /Users/alex/.kimi-code/hooks/ytstack-session-end.sh"
timeout = 10

The wrappers delegate to the plugin's own helper; only SessionStart needs an unwrap, because Kimi appends raw stdout to context (see friction 1):

# ~/.kimi-code/hooks/ytstack-session-start.sh
out=$(bash "$HOME/.kimi-code/ytstack/bin/ytstack-codex" hook session-start 2>/dev/null) || exit 0
[ -n "$out" ] || exit 0
printf '%s' "$out" | jq -r '.hookSpecificOutput.additionalContext // empty'

pre-compact and session-end run through the same helper with stdout discarded (they only write HANDOFF.md / journal JSONL). Kimi hooks are fail-open like ytstack's, so a missing plugin is a silent no-op.

  1. Same install source as feat: OpenCode host support — official adapter for skills + session-start/pre-compact hooks (working bridge attached) #28. Symlink ~/.kimi-code/ytstack -> ~/.local/share/ytstack (pinned v0.4.2 checkout). An earlier attempt pointed at the Codex plugin cache (.../0.1.6), which goes stale on every plugin update — same finding as feat: OpenCode host support — official adapter for skills + session-start/pre-compact hooks (working bridge attached) #28.

Measured: SessionStart wrapper output is ~14.4k chars on the meinkontext repo (plugin root + adapter banner + using-ytstack directive + STATE.md snapshot). Claude Code caps SessionStart additionalContext at 10k (per the v0.4.2 hook comment); Kimi documents no cap.

Friction points (suggestions)

  1. additionalContext JSON vs plain-stdout hosts. hooks/session-start always emits Claude's {"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":...}} envelope. Every non-Claude host needs an unwrap step (the OpenCode bridge in feat: OpenCode host support — official adapter for skills + session-start/pre-compact hooks (working bridge attached) #28 does the same). Suggest: honor an env switch such as YTSTACK_HOOK_FORMAT=plain (or YTSTACK_HOST=kimi|opencode) and emit plain text — one branch in emit_json, removes a wrapper from every future adapter.
  2. Injected host context is Codex-specific. bin/ytstack-codex hook session-start prepends YTSTACK_HOST_CONTEXT built from codex/adapter.md — instructions for Codex's skill picker ($ytstack:<name>) that do not map to Kimi (/skill:<name> invocation, Skill tool). The override mechanism exists but the helper overwrites it. Suggest: ship kimi/adapter.md, or make the banner host-neutral ("resolve ytstack:<name> to this plugin's skills/<name>/SKILL.md via your host's skill mechanism"). Cosmetic: the banner prints the unresolved symlink path (~/.kimi-code/ytstack) rather than the real checkout — pwd -P in the helper would fix it.
  3. No stable, documented plugin path per host. Both the Claude plugin cache and the Codex cache are version-pinned; any third host ends up inventing a pinned checkout plus symlink (feat: OpenCode host support — official adapter for skills + session-start/pre-compact hooks (working bridge attached) #28 and this issue did so independently). Suggest: document the pinned-checkout model as the canonical multi-host install.
  4. Flat skill names outside Claude. Without the plugin namespace, Kimi registers plan-task, summarize-task, ... globally; a future user skill with the same name collides silently (first directory wins). Low risk today (the 31 curated names don't collide with the operator's existing skill set), worth a naming note in host docs.
  5. Kimi hooks are user-level only. Kimi has no project-level hook file, so ytstack activation is per-user config, not per-repo: cloning a ytstack project onto a fresh machine does not activate hooks the way a Claude plugin install does. Host docs should state this activation/trust model.
  6. Unverified: Kimi's docs say SessionStart stdout "may be appended to context"; whether the directive lands in every session's context is confirmed at the next session start. Fallback is unchanged: resume-session / reading STATE.md manually.

Out of scope (noted for completeness)

Kimi Code CLI is also an MCP client (mcp.json, HTTP transport, OAuth login). The meinkontext context MCP server (use_skill / remember / recall) could attach the same way Codex attaches it, but that is meinkontext integration, not ytstack.

Alternatives considered

  • Hook-free mode (read using-ytstack + STATE.md by hand at session start): works, documented for Codex; rejected as the primary path because it relies on agent discipline instead of the system.
  • Staying on the Codex cache 0.1.6: rejected, three months stale vs v0.4.2.

Scope check

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions