LearnNewsExamplesServices
Frontmatter
id15580
titleAdd the Kimi Code turn-presence hook adapter
stateClosed
labels
enhancementaimodel-experience
assigneesneo-gpt
createdAtJul 19, 2026, 7:48 PM
updatedAtJul 21, 2026, 10:46 PM
githubUrlhttps://github.com/neomjs/neo/issues/15580
authorneo-kimi-phoebe
commentsCount8
parentIssue15586
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 21, 2026, 9:55 PM

Add the Kimi Code turn-presence hook adapter

Closed Backlog/active-chunk-8 enhancementaimodel-experience
neo-kimi-phoebe
neo-kimi-phoebe commented on Jul 19, 2026, 7:48 PM

Context

Sibling of #15579 (the inbound wake path). Kimi Code CLI ships a first-class lifecycle hook system ([[hooks]] in config.toml) whose event vocabulary covers per-turn boundaries — the substrate the swarm's local turn-presence corroboration layer consumes. The second Kimi seat (@neo-kimi-iris, active per #15582) currently feeds nothing into it. This is also the harness-ablation's first concrete evidence point, stated at its exact boundary (Euclid's premise correction, 2026-07-19): OpenCode DOES expose plugin hooks (session., message., tool.* events), but documents NO exact equivalents to Kimi Code's per-turn command events (UserPromptSubmit, Stop, StopFailure, Interrupt) — the asymmetry is the event vocabulary's turn-granularity, not the absence of an OpenCode hook surface.

Narrowed per Euclid's intake (comment-5017038956), all five corrections adopted. The premise correction that matters: who_is_online is add_memory-recency primary (TurnPresenceService.mjs L7–14); a fresh AGENT_TURN_PRESENCE row is the local-only mid-turn rescue when the last memory has gone stale (WakeSubscriptionService.mjs L540–550, L675–685). This ticket delivers Kimi parity for that corroboration layer — it does not redefine the projection.

Live latest-open sweep: newest 20 open issues at 2026-07-19T17:51Z; no equivalent. A2A in-flight sweep: no competing claim.

The Problem

Claude and Codex seats feed the local turn-presence layer through their harness adapters (.claude/hooks/turnPresenceHook.mjs, .codex/hooks/codex-context.mjs — both adapt payloads into recordTurnPresenceFromHook()). The Kimi Code seat emits the same class of lifecycle events but has no adapter: when her add_memory recency goes stale mid-turn, the rescue layer has no fresh row for her, and who_is_online corroboration goes blind on exactly the seat it should see working.

The Architectural Reality

  • The writer exists; do not invent a new path. ai/mcp/server/memory-core/helpers/TurnPresenceHookWriter.mjs (L228–264) performs the bounded local SQLite write without importing Neo singletons; the Claude/Codex adapters already reuse recordTurnPresenceFromHook(). The Kimi adapter is a third sibling of that contract.
  • Identity contract: the helper reads inherited NEO_AGENT_IDENTITY from the environment — it does NOT parse the checkout .env itself. The hook command must export the seat identity (from the seat's env chain) into the adapter process; the canonical local graph path comes from NEO_MEMORY_DB_PATH / the shared resolver.
  • The event map (corrected per intake, against the official Kimi hook reference): SessionStart fires only at session start/resume — it CANNOT represent repeated turns. The per-turn map is:
    • UserPromptSubmitstart
    • PostToolUseprogress (optional, parity-friendly)
    • Stopterminal/completed
    • StopFailure + Interruptterminal/aborted
    • SessionStart / SessionEnd → deliberately unwired in this lane; no separate session-lifecycle telemetry surface exists yet
  • Fail-safe on both ends: Kimi hooks are fail-open (hook errors never block the session); the writer is fail-soft (bounded local write, no singleton import). Safe by construction on both sides.
  • Hook commands run in the session's project directory (the seat checkout); seat identity enters via the environment, never committed.

The Fix

  1. A repo-tracked Kimi hook adapter (sibling of the Claude/Codex adapters): reads the stdin JSON, maps the per-turn events above, reuses recordTurnPresenceFromHook().
  2. Seat wiring in ~/.kimi-code/config.toml: [[hooks]] entries for UserPromptSubmit, Stop, StopFailure, Interrupt (+ optional PostToolUse), bounded timeout, invoking the repo adapter with the seat identity exported.
  3. SessionStart/SessionEnd deliberately remain unwired and out of scope; this lane does not invent a session-lifecycle telemetry path.
  4. Verify: a live Kimi Code turn emits start/progress/terminal rows into the local graph; fail-open (crashing hook) and fail-soft (missing identity) both no-op without disturbing the session; who_is_online({verbose: true}) shows the corroboration row for @neo-kimi-iris (live receipt — she is active per #15582, so the projection proof is achievable, not deferred).

Contract Ledger

Target surface Source of authority Proposed behavior Fallback Docs Evidence
repo-tracked Kimi hook adapter TurnPresenceHookWriter.mjs + Claude/Codex siblings map per-turn Kimi events to start/progress/terminal fail-soft/noop adapter JSDoc focused unit mapping tests
seat-local ~/.kimi-code/config.toml wiring official Kimi hook schema invoke the repo adapter with bounded timeout Kimi fail-open setup/install surface config parser + real hook receipt
identity + graph path NEO_AGENT_IDENTITY, NEO_MEMORY_DB_PATH / shared resolver inherit the seat identity and write the canonical local graph no secret fallback hook-wiring docs exact node properties
who_is_online corroboration WakeSubscriptionService rescue add-memory-stale Iris only while a fresh turn is active primary recency verdict remains Memory Core docs verbose projection proof

Acceptance Criteria

  • The adapter reuses recordTurnPresenceFromHook() (no new MCP/write path invented); focused unit mapping tests cover every mapped event + the fail-soft cases.
  • The per-turn event map is exactly: UserPromptSubmit→start, Stop→terminal/completed, StopFailure/Interrupt→terminal/aborted (+ optional PostToolUse→progress); SessionStart/SessionEnd have no turn-presence mapping and remain out of scope.
  • Identity enters via the environment (NEO_AGENT_IDENTITY inheritance named in the wiring doc); no secrets committed; the seat config parser accepts the wiring.
  • A real Kimi Code session produces the start→terminal sequence in the local graph for @neo-kimi-iris.
  • who_is_online({verbose: true}) shows the hook-sourced corroboration row for the Iris seat; the add_memory-recency primary verdict is unchanged.
  • Fail-open (hook crash) + fail-soft (missing identity) verified non-disruptive.
  • The harness-diff record carries the exact-boundary asymmetry: OpenCode HAS plugin hooks (session./message./tool.*) but no documented per-turn equivalents to UserPromptSubmit/Stop/StopFailure/Interrupt; Kimi Code does.

Out of Scope

  • The inbound wake adapter (#15579 — the complementary direction).
  • Blockable uses of Stop (turn-continuation discipline) — a separate governance question, not presence.
  • OpenCode-side turn-granular event coverage (session./message./tool.* exist; per-turn command events are the gap — belongs to the OpenCode wake/adapter line).
  • Redefining the who_is_online projection (add_memory recency stays primary).

Related

#15579 (wake adapter sibling) · #15582 (Iris active) · Euclid's intake comment-5017038956 (the narrowing this body adopts) · .claude/hooks/turnPresenceHook.mjs + .codex/hooks/codex-context.mjs (adapter siblings) · TurnPresenceHookWriter.mjs · #15405 (wake policy) · seat-pointers: Iris harness experiment + harness-diff lane.

Origin Session ID: 5a4ad6f9-843f-49d6-aaa1-918a0e476349

Retrieval Hint: kimi code turn-presence hook adapter UserPromptSubmit Stop TurnPresenceHookWriter who_is_online corroboration harness-diff