Push-awareness hooks so Claude Code and Kimi CLI know what each other is doing on the same machine — without polling, without MCP calls, without either agent thinking about it.
When you flip between claude and kimi in two terminals all day, you keep
re-explaining context: "I just edited this file", "Kimi already tried that
approach, see the bridge", "don't redo what I just did". coagent kills that
loop with three small Python hooks and one shared SQLite table.
Claude Code session Kimi CLI session
────────────────── ────────────────
SessionStart ─┐ ┌─ SessionStart
│ │
PostToolUse ─┤ ┌──────────────────────────┐ ├─ PostToolUse
│ │ ~/.kimi-bridge/bridge.db │ │
├──▶│ activity_log │◀──┤
Stop ─┤ │ (project-scoped writes) │ ├─ Stop
│ └──────────────────────────┘ │
SessionEnd ─┘ └─ SessionEnd
Each agent writes what it just did (PostToolUse → one row, Stop → one
checkpoint summary) and reads what the other did when it boots up
(SessionStart → ≤2KB markdown digest of last 24h activity in this project,
filtered to exclude itself).
Two agents on one Mac is genuinely useful — Kimi has a different subscription
quota, different strengths on long-context, different style. But they don't
know about each other. Existing options are pull-only: you can install the
kimi-bridge MCP server and call
bridge_recent() manually, but the agent has to remember to do it, and
SessionStart inject is the only place that information would actually be free.
coagent is the push layer: a 1KB stdout digest at session start that
both agents see automatically, scoped to the project you're in (by cwd),
limited to the last 24 hours, and excluding noise (Read/Grep/LS calls aren't
worth surfacing — only Edit/Write/Bash/Shell get logged).
When you cd into a project and start claude, the SessionStart hook injects
this into the model context before the first prompt:
=== kimi-bridge: what kimi was doing here (last 24h) ===
[3m ago] checkpoint: Session: 12 tool uses, touched services/web_discovery.py, tests/test_web.py
[42m ago] edit: services/web_discovery.py
[42m ago] edit: tests/test_web.py
[1h ago] shell: pytest tests/test_web.py -v
[1h ago] checkpoint: Worked on services/web_discovery.py
=== End ===
Claude now knows Kimi was just here, what it touched, and what it ran. The reverse works the same way for Kimi (with one Kimi 1.41 Beta caveat documented below).
- Not a replacement for kimi-bridge — it complements it. coagent only
writes to and reads from the
activity_logtable that kimi-bridge owns. You still need kimi-bridge installed for the database file and (if you want formal handoffs / shared notes / Claude→Kimi delegation) its MCP tools. - Not a chat relay — agents don't talk to each other in real time. Awareness is at session boundaries. By design.
- Not project-wide noise — every row is scoped by
cwd. Working in~/Projects/foo/never leaks into~/Projects/bar/.
- Python 3.11+
kimi-bridgeinstalled (provides the SQLite file at~/.kimi-bridge/bridge.dband the schema the hooks read/write)- Claude Code (any recent version) and/or Kimi CLI 1.41+
git clone https://github.com/Argona7/coagent ~/projects/coagentThe hooks are stdlib-only Python — there's nothing to pip install.
Add these three hook entries (merge with whatever you already have under
hooks):
⚠️ If your config has an inlinehooks = [], delete that line first — TOML rejects mixing inline arrays with[[hooks]]array-of-tables.
[[hooks]]
event = "SessionStart"
command = "BRIDGE_HOOK_ACTOR=kimi /usr/bin/env python3 /Users/<you>/projects/coagent/hooks/inject_activity.py"
timeout = 5
[[hooks]]
event = "PostToolUse"
matcher = "WriteFile|StrReplaceFile|Shell"
command = "BRIDGE_HOOK_ACTOR=kimi /usr/bin/env python3 /Users/<you>/projects/coagent/hooks/log_tool_use.py"
timeout = 5
[[hooks]]
event = "Stop"
command = "BRIDGE_HOOK_ACTOR=kimi /usr/bin/env python3 /Users/<you>/projects/coagent/hooks/checkpoint.py"
timeout = 5
[[hooks]]
event = "SessionEnd"
command = "BRIDGE_HOOK_ACTOR=kimi /usr/bin/env python3 /Users/<you>/projects/coagent/hooks/checkpoint.py"
timeout = 5Edit a file in either CLI, then check the DB:
sqlite3 ~/.kimi-bridge/bridge.db \
"SELECT actor, action, summary, datetime(created_at, 'unixepoch') \
FROM activity_log ORDER BY id DESC LIMIT 5;"You should see your tool uses tagged with claude or kimi.
Then start the other CLI in the same project — the SessionStart inject
should print a === kimi-bridge: what … was doing here === block.
| Script | Hook event | Reads | Writes |
|---|---|---|---|
hooks/inject_activity.py |
SessionStart | last 24h activity for cwd, excluding self + pending handoffs targeting self |
nothing |
hooks/log_tool_use.py |
PostToolUse | nothing | one row per significant tool use (deduped within 30s) |
hooks/checkpoint.py |
Stop / SessionEnd | last hour's tool use for self | one summary row |
Both agents run the same scripts. The only difference is the
BRIDGE_HOOK_ACTOR env var in the wrapping command, which tags rows on write
and filters them on read.
Claude Code and Kimi CLI use different names for the same actions. The logger unifies them:
| Claude name | Kimi name | Stored summary |
|---|---|---|
Edit, Write, MultiEdit |
WriteFile, StrReplaceFile |
edit: <file_path> |
Bash |
Shell |
shell: <command-truncated-to-80-chars> |
Read-only / introspection tools are skipped entirely so the digest stays
signal-heavy: Read, Grep, Glob, LS, ListDir, BashOutput, anything
starting with bridge_, handoff_, or mcp__claude-mem__.
- 30s dedupe —
log_tool_use.pyUPDATEscreated_atinstead of inserting if the same actor logged the same summary within 30 seconds. Prevents burst-noise (e.g. 5 quick edits to the same file) from filling the DB. stop_hook_activeguard —checkpoint.pyexits immediately when Claude signals it's already inside a Stop hook chain. Without this, summarizing triggers another Stop, which triggers another summary, ad infinitum.- Self-exclusion — the inject hook filters
actor != $BRIDGE_HOOK_ACTOR, so each agent never sees its own activity echoed back at session start.
Every hook exits 0 on any error — malformed stdin, missing DB, locked DB,
import failure, you name it. Hooks must never break the host CLI session, so
the worst case is "this session doesn't get a digest" — the agent keeps
working as if coagent weren't installed.
The injected digest is hard-capped at 2048 bytes. Beyond that we trim
oldest entries first and append (... older entries trimmed). Claude Code's
SessionStart inject limit is 10KB so we have plenty of headroom; the cap is
about signal density, not the limit.
Three ways to disable it:
- Per-session —
BRIDGE_HOOK_ACTOR=disabledin the hook command. Hook still runs and exits 0, but does no DB I/O. - Permanently — remove the hook entries from
~/.claude/settings.json/~/.kimi/config.toml. The hook scripts themselves can stay on disk. - Schema-level —
mv ~/.kimi-bridge/bridge.db ~/.kimi-bridge/bridge.db.off. Hooks see the missing DB and exit silently. Useful for debugging without piling on writes.
- Claude Code: stable channel (Opus 4.7)
- Kimi CLI: 1.41.0 (Beta)
The SessionStart hook fires correctly in Kimi (you'll see Triggering 1 hooks for SessionStart in ~/.kimi/logs/kimi.log) and the DB writes happen,
but the stdout content from the inject hook is not surfaced into the model
context in 1.41 Beta — Kimi can't "see" the digest unless it explicitly
calls bridge_recent via the kimi-bridge MCP tool.
The fallback works: tell Kimi to call bridge_recent(20) and handoff_list()
at session start (e.g. via your AGENTS.md). When Kimi ships a fix for
SessionStart inject, no script changes will be needed here.
Claude Code's SessionStart inject works as advertised — the asymmetry is purely on the Kimi side.
cd ~/projects/coagent
python3 -m pytest tests/test_hooks.py -v34 subprocess-driven tests covering: empty DB, cwd filtering, actor
exclusion, handoff inclusion, 2KB truncation, all tool name mappings,
noisy-tool skip, 30s dedupe, stop_hook_active guard, no-recent-activity
short-circuit, disabled kill switch, and malformed JSON tolerance.
coagent/
├── hooks/
│ ├── _common.py # shared helpers: stdin, env, DB, time formatting
│ ├── inject_activity.py # SessionStart digest
│ ├── log_tool_use.py # PostToolUse logger w/ 30s dedupe
│ └── checkpoint.py # Stop / SessionEnd summarizer
├── tests/
│ ├── _dbutil.py # mini schema mirror so tests don't need kimi-bridge
│ ├── conftest.py # makes tests/ importable
│ └── test_hooks.py # 34 subprocess tests
├── pyproject.toml
├── LICENSE # MIT
└── README.md # you're here
MIT — see LICENSE.
{ "hooks": { "SessionStart": [ { "hooks": [{ "type": "command", "command": "BRIDGE_HOOK_ACTOR=claude /usr/bin/env python3 /Users/<you>/projects/coagent/hooks/inject_activity.py", "timeout": 5 }] } ], "PostToolUse": [ { "matcher": "Edit|Write|Bash", "hooks": [{ "type": "command", "command": "BRIDGE_HOOK_ACTOR=claude /usr/bin/env python3 /Users/<you>/projects/coagent/hooks/log_tool_use.py", "timeout": 5 }] } ], "Stop": [ { "hooks": [{ "type": "command", "command": "BRIDGE_HOOK_ACTOR=claude /usr/bin/env python3 /Users/<you>/projects/coagent/hooks/checkpoint.py", "timeout": 5 }] } ] } }