agents-can-communicate 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +176 -0
  3. package/bin/acc-hook.mjs +53 -0
  4. package/bin/acc-mcp.mjs +46 -0
  5. package/bin/acc.mjs +24 -0
  6. package/docs/CAPABILITIES.md +153 -0
  7. package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +13 -0
  8. package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/.claude-plugin/plugin.json +5 -0
  9. package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/hooks/hooks.json +56 -0
  10. package/node_modules/@agents-can-communicate/adapter-claude-code/plugin/skills/acc/SKILL.md +217 -0
  11. package/node_modules/@agents-can-communicate/adapter-claude-code/src/adapter.mjs +64 -0
  12. package/node_modules/@agents-can-communicate/adapter-claude-code/src/hooks.mjs +106 -0
  13. package/node_modules/@agents-can-communicate/adapter-claude-code/src/install.mjs +278 -0
  14. package/node_modules/@agents-can-communicate/adapter-codex/package.json +13 -0
  15. package/node_modules/@agents-can-communicate/adapter-codex/plugin/.codex-plugin/plugin.json +17 -0
  16. package/node_modules/@agents-can-communicate/adapter-codex/plugin/hooks.json +55 -0
  17. package/node_modules/@agents-can-communicate/adapter-codex/plugin/skills/acc/SKILL.md +217 -0
  18. package/node_modules/@agents-can-communicate/adapter-codex/src/adapter.mjs +81 -0
  19. package/node_modules/@agents-can-communicate/adapter-codex/src/hooks.mjs +133 -0
  20. package/node_modules/@agents-can-communicate/adapter-codex/src/install.mjs +235 -0
  21. package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +6 -0
  22. package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/hooks/hooks.json +69 -0
  23. package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/skills/acc/SKILL.md +217 -0
  24. package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +13 -0
  25. package/node_modules/@agents-can-communicate/adapter-gemini-cli/src/adapter.mjs +74 -0
  26. package/node_modules/@agents-can-communicate/adapter-gemini-cli/src/hooks.mjs +109 -0
  27. package/node_modules/@agents-can-communicate/adapter-gemini-cli/src/install.mjs +149 -0
  28. package/node_modules/@agents-can-communicate/adapter-kimi/package.json +13 -0
  29. package/node_modules/@agents-can-communicate/adapter-kimi/plugin/.kimi-plugin/plugin.json +9 -0
  30. package/node_modules/@agents-can-communicate/adapter-kimi/plugin/skills/acc/SKILL.md +217 -0
  31. package/node_modules/@agents-can-communicate/adapter-kimi/src/adapter.mjs +73 -0
  32. package/node_modules/@agents-can-communicate/adapter-kimi/src/hooks.mjs +125 -0
  33. package/node_modules/@agents-can-communicate/adapter-kimi/src/install.mjs +216 -0
  34. package/node_modules/@agents-can-communicate/adapter-sdk/package.json +12 -0
  35. package/node_modules/@agents-can-communicate/adapter-sdk/src/capabilities.mjs +101 -0
  36. package/node_modules/@agents-can-communicate/adapter-sdk/src/config-merge.mjs +215 -0
  37. package/node_modules/@agents-can-communicate/adapter-sdk/src/context-projector.mjs +217 -0
  38. package/node_modules/@agents-can-communicate/adapter-sdk/src/events.mjs +55 -0
  39. package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +148 -0
  40. package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +16 -0
  41. package/node_modules/@agents-can-communicate/adapter-sdk/src/session-binding.mjs +95 -0
  42. package/node_modules/@agents-can-communicate/adapter-sdk/src/toml-block.mjs +71 -0
  43. package/node_modules/@agents-can-communicate/cli/package.json +12 -0
  44. package/node_modules/@agents-can-communicate/cli/src/args.mjs +163 -0
  45. package/node_modules/@agents-can-communicate/cli/src/claim-spelling.mjs +79 -0
  46. package/node_modules/@agents-can-communicate/cli/src/config-command.mjs +149 -0
  47. package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +85 -0
  48. package/node_modules/@agents-can-communicate/cli/src/git-probe.mjs +41 -0
  49. package/node_modules/@agents-can-communicate/cli/src/help.mjs +78 -0
  50. package/node_modules/@agents-can-communicate/cli/src/index.mjs +10 -0
  51. package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +107 -0
  52. package/node_modules/@agents-can-communicate/cli/src/main.mjs +344 -0
  53. package/node_modules/@agents-can-communicate/cli/src/platform-paths.mjs +109 -0
  54. package/node_modules/@agents-can-communicate/cli/src/runtime-paths.mjs +58 -0
  55. package/node_modules/@agents-can-communicate/cli/src/session-owner.mjs +126 -0
  56. package/node_modules/@agents-can-communicate/cli/src/workspace-discovery.mjs +151 -0
  57. package/node_modules/@agents-can-communicate/core/package.json +12 -0
  58. package/node_modules/@agents-can-communicate/core/src/claims.mjs +180 -0
  59. package/node_modules/@agents-can-communicate/core/src/communication.mjs +334 -0
  60. package/node_modules/@agents-can-communicate/core/src/index.mjs +7 -0
  61. package/node_modules/@agents-can-communicate/core/src/intents.mjs +75 -0
  62. package/node_modules/@agents-can-communicate/core/src/materialisation.mjs +86 -0
  63. package/node_modules/@agents-can-communicate/core/src/notify.mjs +95 -0
  64. package/node_modules/@agents-can-communicate/core/src/participants.mjs +48 -0
  65. package/node_modules/@agents-can-communicate/core/src/ports.mjs +56 -0
  66. package/node_modules/@agents-can-communicate/core/src/service.mjs +44 -0
  67. package/node_modules/@agents-can-communicate/core/src/sessions.mjs +190 -0
  68. package/node_modules/@agents-can-communicate/core/src/status.mjs +132 -0
  69. package/node_modules/@agents-can-communicate/core/src/sync.mjs +273 -0
  70. package/node_modules/@agents-can-communicate/core/src/tasks.mjs +238 -0
  71. package/node_modules/@agents-can-communicate/core/src/workstreams.mjs +109 -0
  72. package/node_modules/@agents-can-communicate/hook-runner/package.json +12 -0
  73. package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +379 -0
  74. package/node_modules/@agents-can-communicate/installer/package.json +10 -0
  75. package/node_modules/@agents-can-communicate/installer/src/apply.mjs +58 -0
  76. package/node_modules/@agents-can-communicate/installer/src/detect.mjs +79 -0
  77. package/node_modules/@agents-can-communicate/installer/src/index.mjs +6 -0
  78. package/node_modules/@agents-can-communicate/installer/src/ownership.mjs +162 -0
  79. package/node_modules/@agents-can-communicate/installer/src/plan.mjs +62 -0
  80. package/node_modules/@agents-can-communicate/mcp-server/package.json +12 -0
  81. package/node_modules/@agents-can-communicate/mcp-server/src/resources.mjs +58 -0
  82. package/node_modules/@agents-can-communicate/mcp-server/src/server.mjs +250 -0
  83. package/node_modules/@agents-can-communicate/mcp-server/src/tools.mjs +201 -0
  84. package/node_modules/@agents-can-communicate/protocol/package.json +12 -0
  85. package/node_modules/@agents-can-communicate/protocol/src/config.mjs +151 -0
  86. package/node_modules/@agents-can-communicate/protocol/src/envelopes.mjs +25 -0
  87. package/node_modules/@agents-can-communicate/protocol/src/errors.mjs +30 -0
  88. package/node_modules/@agents-can-communicate/protocol/src/fields.mjs +103 -0
  89. package/node_modules/@agents-can-communicate/protocol/src/ids.mjs +25 -0
  90. package/node_modules/@agents-can-communicate/protocol/src/index.mjs +9 -0
  91. package/node_modules/@agents-can-communicate/protocol/src/resources.mjs +74 -0
  92. package/node_modules/@agents-can-communicate/protocol/src/schema.mjs +175 -0
  93. package/node_modules/@agents-can-communicate/protocol/src/states.mjs +55 -0
  94. package/node_modules/@agents-can-communicate/storage-filesystem/package.json +12 -0
  95. package/node_modules/@agents-can-communicate/storage-filesystem/src/atomic-json.mjs +135 -0
  96. package/node_modules/@agents-can-communicate/storage-filesystem/src/identity.mjs +67 -0
  97. package/node_modules/@agents-can-communicate/storage-filesystem/src/index.mjs +4 -0
  98. package/node_modules/@agents-can-communicate/storage-filesystem/src/journal.mjs +87 -0
  99. package/node_modules/@agents-can-communicate/storage-filesystem/src/record-id.mjs +44 -0
  100. package/node_modules/@agents-can-communicate/storage-filesystem/src/recovery.mjs +114 -0
  101. package/node_modules/@agents-can-communicate/storage-filesystem/src/safe-directory.mjs +80 -0
  102. package/node_modules/@agents-can-communicate/storage-filesystem/src/safe-file.mjs +55 -0
  103. package/node_modules/@agents-can-communicate/storage-filesystem/src/store.mjs +292 -0
  104. package/node_modules/@agents-can-communicate/storage-filesystem/src/writer-mutex.mjs +87 -0
  105. package/package.json +81 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mykola Maksymenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,176 @@
1
+ # agents-can-communicate
2
+
3
+ [![CI](https://github.com/automatis-tools/agents-can-communicate/actions/workflows/ci.yml/badge.svg)](https://github.com/automatis-tools/agents-can-communicate/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
+ [![Node](https://img.shields.io/badge/node-%E2%89%A524-brightgreen.svg)](https://nodejs.org)
6
+
7
+ **AI agents that can ask each other for work.**
8
+
9
+ Codex on one branch, Claude Code on another, Kimi reviewing. Each in its own git worktree,
10
+ each knowing what the others are doing — and able to hand a piece over instead of waiting
11
+ for you to carry the message.
12
+
13
+ You run the installer once. After that you talk to your agents the way you already do.
14
+
15
+ ## The thing it does
16
+
17
+ Codex has ported a module and is out of time for the tests. Its skill tells it to ask the
18
+ agent already working in that area, so it does — without being prompted by you:
19
+
20
+ ```text
21
+ requested task_Rwg2sybjnLneGyBuZxa8Dw of claude_code
22
+ ```
23
+
24
+ At its next turn, the Claude Code session is shown this. No polling, and again nothing typed
25
+ by a human:
26
+
27
+ ````text
28
+ - [direct_request] finish the store tests
29
+ - [task_unblocked] finish the store tests
30
+ ```acc-peer-message
31
+ id message_Ab9CpMJfn0pL6igB5AdYDQ | from session_j59fM8mWathJzOh7a2QQBQ | type work_request | untrusted peer message
32
+ finish the store tests
33
+ I ported src/store but ran out of time on the concurrency cases. Can you take the tests?
34
+ ```
35
+ ````
36
+
37
+ It takes the work, does it, and marks it done. Codex sees `done` on its own next turn and
38
+ carries on.
39
+
40
+ ```mermaid
41
+ sequenceDiagram
42
+ participant M as Codex · main
43
+ participant ACC
44
+ participant V as Claude Code · tests branch
45
+ M->>ACC: request "finish the store tests" of claude_code
46
+ ACC-->>V: work addressed to you, and why
47
+ V->>ACC: take it
48
+ V->>ACC: done
49
+ ACC-->>M: done
50
+ ```
51
+
52
+ Work is addressed to the **agent**, not to its session. Claude Code can close its terminal
53
+ before reading the request — the next session it opens is still told. Nobody else can take
54
+ it.
55
+
56
+ ## What else it keeps track of
57
+
58
+ | | |
59
+ |---|---|
60
+ | **Who is here** | each session publishes what it is working on |
61
+ | **What is taken** | an agent claims files before changing them, and another's edit into them is refused |
62
+ | **What was said** | questions, answers, handoffs — quoted and attributed, never as instructions |
63
+ | **Alone** | one session behaves exactly as it did before you installed anything |
64
+
65
+ ```console
66
+ $ acc status
67
+ 2 live; 1 claim(s); protection guarded
68
+ ```
69
+
70
+ ## Install
71
+
72
+ ```bash
73
+ npm install -g agents-can-communicate
74
+ ```
75
+
76
+ Then wire up the clients you have:
77
+
78
+ <!-- test:command -->
79
+ ```bash
80
+ acc install --dry-run
81
+ ```
82
+
83
+ This prints every file it would touch. Run `acc install` to apply it, then open your clients
84
+ in the project — in one directory or in several worktrees — and work normally.
85
+
86
+ ## Commands
87
+
88
+ Coordination needs none from you. Requesting work, taking it, claiming files and messaging
89
+ are things the agents do, taught by the skill each adapter installs.
90
+
91
+ What is left for a person is the install and looking in on it:
92
+
93
+ | | |
94
+ |---|---|
95
+ | `acc help` | every command, one line each |
96
+ | `acc status` | who is here, what is claimed, what is in flight |
97
+ | `acc doctor` | what is installed, what is missing, what to do next |
98
+ | `acc install` · `acc uninstall` | wire clients up, or take it back out |
99
+
100
+ Uninstall removes only files ACC wrote, and only where they still match what it wrote.
101
+ Every operation is in the [CLI reference](docs/CLI.md) if you want to drive it yourself.
102
+
103
+ ## Supported clients
104
+
105
+ | | Sees others | Blocks edits | Receives work and updates |
106
+ |---|---|---|---|
107
+ | Codex | yes | yes¹ | yes |
108
+ | Claude Code | yes | yes | yes |
109
+ | Gemini CLI | yes | yes² | yes |
110
+ | Kimi Code | yes | yes | yes |
111
+ | Any MCP client | yes | – | yes, when it polls |
112
+
113
+ ¹ models editing through `apply_patch` · ² approval modes that expose edit tools ·
114
+ [what was measured](docs/CAPABILITIES.md)
115
+
116
+ ## Limits
117
+
118
+ - A claim blocks file edits. It does not block an agent that edits by running a shell
119
+ command, since the command names no file.
120
+ - `protection guarded` applies while every session present is one ACC can stop. One that
121
+ cannot changes it to `advisory`.
122
+ - Codex requires you to trust the plugin before its hooks run. `acc doctor` reports this.
123
+ - Nothing is pruned yet. A workspace that has carried thousands of messages makes each turn
124
+ slower to build; a project's worth of coordination is fine, an archive is not.
125
+ - Windows does not work: the store fsyncs a directory after a rename, which Windows
126
+ refuses, and `O_NOFOLLOW` does not hold there. Last measured at 86 failures out of 587
127
+ tests; the suite has grown a good deal since and nobody has run it there again. macOS and
128
+ Linux are supported and both run in CI.
129
+
130
+ ## How it works
131
+
132
+ ```mermaid
133
+ graph LR
134
+ C[your client] -->|hook| H[acc-hook]
135
+ H --> K["core<br/>sessions · work · claims · messages"]
136
+ K --> S[(state, outside your repo)]
137
+ K -->|answer| H
138
+ H --> C
139
+ ```
140
+
141
+ Clients call out when a session starts and ends, when a turn begins, and before a tool
142
+ runs — plus a heartbeat, on the one client that sends them. ACC answers at those and is
143
+ idle otherwise. A hook that does not answer within five seconds lets the tool run, so ACC
144
+ can be slow or broken without stopping anyone's work.
145
+
146
+ State lives beside your other tool settings, never inside the repository:
147
+
148
+ ```text
149
+ ~/Library/Application Support/acc macOS
150
+ ~/.local/share/acc Linux
151
+ ```
152
+
153
+ Inside it, one directory per workspace — keyed by the repository rather than the folder, so
154
+ every worktree of it is one workspace and two unrelated projects share nothing. Deleting a
155
+ workspace directory loses that project's coordination history and nothing else.
156
+
157
+ ## Documentation
158
+
159
+ | Using it | Understanding it | Building on it |
160
+ |---|---|---|
161
+ | [Getting started](docs/GETTING_STARTED.md) | [Concepts](docs/CONCEPTS.md) | [Writing an adapter](docs/ADAPTER_AUTHORING.md) |
162
+ | [CLI](docs/CLI.md) | [Architecture](docs/ARCHITECTURE.md) | [Protocol](docs/PROTOCOL.md) |
163
+ | [Configuration](docs/CONFIGURATION.md) | [Capabilities](docs/CAPABILITIES.md) | [Security](docs/SECURITY_MODEL.md) |
164
+ | [MCP](docs/MCP.md) | [Decisions](docs/DESIGN_DECISIONS.md) | [Threat model](docs/THREAT_MODEL.md) |
165
+ | [Troubleshooting](docs/TROUBLESHOOTING.md) | [Prior art](docs/PRIOR_ART.md) | [Contributing](AGENTS.md) |
166
+
167
+ Examples: [three workstreams](examples/three-workstreams.md) ·
168
+ [research without Git](examples/non-git-research.md)
169
+
170
+ ## Requirements
171
+
172
+ Node 24+, macOS or Linux. Git optional.
173
+
174
+ ## License
175
+
176
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ // Hook entry point: `acc-hook <adapter-id> <kind>`, payload on stdin.
3
+ //
4
+ // Every harness runs this as a short-lived child process in front of the user's
5
+ // turn. The one rule that overrides all others here: never be the reason
6
+ // someone's session stops working. Unknown adapter, malformed payload, broken
7
+ // store, missing binding - all of them end in "allow, exit 0".
8
+ import { randomBytes } from "node:crypto";
9
+
10
+ import { createId } from "@agents-can-communicate/protocol";
11
+ import { runHook } from "@agents-can-communicate/hook-runner";
12
+
13
+ import { createClaudeCodeAdapter } from "@agents-can-communicate/adapter-claude-code";
14
+ import { createCodexAdapter } from "@agents-can-communicate/adapter-codex";
15
+ import { createGeminiCliAdapter } from "@agents-can-communicate/adapter-gemini-cli";
16
+ import { createKimiAdapter } from "@agents-can-communicate/adapter-kimi";
17
+
18
+ const adapters = {
19
+ claude_code: createClaudeCodeAdapter(),
20
+ codex: createCodexAdapter(),
21
+ gemini_cli: createGeminiCliAdapter(),
22
+ kimi: createKimiAdapter(),
23
+ };
24
+
25
+ const readStdin = () => new Promise(resolve => {
26
+ // A hook is always given its payload on stdin, but a client that closes it
27
+ // without writing must not leave this process hanging in front of a turn.
28
+ if (process.stdin.isTTY) return resolve("");
29
+ let raw = "";
30
+ process.stdin.setEncoding("utf8");
31
+ process.stdin.on("data", chunk => { raw += chunk; });
32
+ process.stdin.on("end", () => resolve(raw));
33
+ process.stdin.on("error", () => resolve(""));
34
+ return undefined;
35
+ });
36
+
37
+ const [adapterId] = process.argv.slice(2);
38
+
39
+ let payload = null;
40
+ try {
41
+ payload = JSON.parse(await readStdin());
42
+ } catch {
43
+ payload = null;
44
+ }
45
+
46
+ const result = await runHook({ adapterId, payload, adapters,
47
+ runtime: { clock: { now: () => new Date().toISOString() },
48
+ ids: { next: kind => createId(kind, randomBytes) } },
49
+ env: process.env });
50
+
51
+ if (result.stdout !== "") process.stdout.write(result.stdout);
52
+ if (result.stderr) process.stderr.write(`${result.stderr}\n`);
53
+ process.exitCode = result.exitCode ?? 0;
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ import { randomBytes } from "node:crypto";
3
+
4
+ import { createId } from "@agents-can-communicate/protocol";
5
+ import { createCoordinationService } from "@agents-can-communicate/core";
6
+ import { openFilesystemStore } from "@agents-can-communicate/storage-filesystem";
7
+ import { createGitProbe, discoverWorkspace, platformDataHome, runtimePaths }
8
+ from "@agents-can-communicate/cli";
9
+ import { serve } from "@agents-can-communicate/mcp-server";
10
+
11
+ // The composition root is the only place allowed to reach for ambient time and
12
+ // randomness. The participant name comes from configuration, never from the
13
+ // client: the protocol says clientInfo is self-reported and must not drive
14
+ // behaviour, and the session is derived from this configuration alone.
15
+ const participantId = process.env.ACC_MCP_PARTICIPANT ?? "mcp";
16
+ const clock = { now: () => new Date().toISOString() };
17
+ const ids = { next: kind => createId(kind, randomBytes) };
18
+
19
+ const descriptor = await discoverWorkspace({
20
+ cwd: process.env.ACC_MCP_WORKSPACE ?? process.cwd(),
21
+ env: process.env,
22
+ gitProbe: createGitProbe(),
23
+ });
24
+ const paths = runtimePaths({
25
+ dataHome: platformDataHome({ platform: process.platform, env: process.env }),
26
+ workspaceId: descriptor.id,
27
+ workspaceRoots: descriptor.roots,
28
+ });
29
+ const store = await openFilesystemStore({ root: paths.root, clock, ids,
30
+ workspaceId: descriptor.id });
31
+
32
+ await serve({
33
+ input: process.stdin,
34
+ output: process.stdout,
35
+ log: message => process.stderr.write(`acc-mcp: ${message}\n`),
36
+ context: {
37
+ service: createCoordinationService({ store, clock, ids }),
38
+ workspaceId: descriptor.id,
39
+ participantId,
40
+ descriptor,
41
+ runtimeDir: paths.root,
42
+ },
43
+ });
44
+
45
+ // Exiting on stdin EOF is the only portable graceful shutdown in this binding.
46
+ process.exitCode = 0;
package/bin/acc.mjs ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import { randomBytes } from "node:crypto";
3
+ import { readFile } from "node:fs/promises";
4
+
5
+ import { createId } from "@agents-can-communicate/protocol";
6
+ import { main } from "@agents-can-communicate/cli";
7
+
8
+ // The composition root is the only place allowed to reach for ambient time and
9
+ // randomness; everything below it receives them as ports.
10
+ const runtime = {
11
+ cwd: process.cwd(),
12
+ env: process.env,
13
+ platform: process.platform,
14
+ stdout: process.stdout,
15
+ stderr: process.stderr,
16
+ clock: { now: () => new Date().toISOString() },
17
+ ids: { next: kind => createId(kind, randomBytes) },
18
+ // Asked for only by `acc version`, so a package missing its own manifest
19
+ // fails that one command rather than every command.
20
+ version: async () => JSON.parse(
21
+ await readFile(new URL("../package.json", import.meta.url), "utf8")).version,
22
+ };
23
+
24
+ process.exitCode = await main(process.argv.slice(2), runtime);
@@ -0,0 +1,153 @@
1
+ # Capabilities
2
+
3
+ What each harness was **observed** doing, on the versions named here. Nothing in this
4
+ table is inferred from documentation: every `yes` has a fixture captured from a real
5
+ session, and every `no` means it was not seen, not that it is impossible.
6
+
7
+ Certified 2026-08-16 on macOS 15 (darwin 25.5.0, arm64). No other operating system has
8
+ been tested, and at least one finding here is filesystem- and path-shaped, so the table
9
+ should be re-run before claiming another platform.
10
+
11
+ ## Clients
12
+
13
+ | Adapter | Client | Version |
14
+ |---|---|---|
15
+ | `codex` | `codex-cli` | 0.147.0 |
16
+ | `claude_code` | Claude Code | 2.1.233 |
17
+ | `gemini_cli` | Gemini CLI | 0.37.0 and 0.55.1 |
18
+ | `kimi` | Kimi Code | 0.36.1 |
19
+
20
+ ## Matrix
21
+
22
+ | Capability | codex | claude_code | gemini_cli | kimi |
23
+ |---|---|---|---|---|
24
+ | `lifecycle.sessionStart` | yes | yes | yes | yes |
25
+ | `lifecycle.sessionResume` | no | no | no | no |
26
+ | `lifecycle.sessionEnd` | yes | yes | yes | no |
27
+ | `lifecycle.heartbeat` | no | no | no | yes |
28
+ | `lifecycle.childSessions` | no | no | no | no |
29
+ | `context.startupInjection` | no | no | no | no |
30
+ | `context.beforeTurnInjection` | yes | yes | yes | yes |
31
+ | `context.safePointInjection` | no | no | no | no |
32
+ | `guards.beforeRead` | no | no | no | no |
33
+ | `guards.beforeWrite` | yes | yes | yes | yes |
34
+ | `guards.beforeShell` | yes | yes | yes | yes |
35
+ | `delivery.polling` | yes | yes | yes | yes |
36
+ | `delivery.activeNotification` | no | no | no | no |
37
+ | `delivery.wakeDormantSession` | no | no | no | no |
38
+ | `execution.launch` | no | no | no | no |
39
+ | `execution.resume` | no | no | no | no |
40
+ | `execution.terminate` | no | no | no | no |
41
+
42
+ ## What the yes values do not promise
43
+
44
+ A capability says the client can do the thing. Several of them are conditional on how the
45
+ client is being run, and the conditions differ per harness. These are the ones that bite.
46
+
47
+ **`guards.beforeWrite` on `codex` depends on the model.** Whether the client offers
48
+ `apply_patch` at all is a property of the model's metadata (`apply_patch_tool_type`), not
49
+ a user setting. With a model that does not have it, edits run through `exec_command`,
50
+ which reaches hooks as `tool_name: "Bash"` carrying a command string. A command names no
51
+ resource, so there is nothing to compare against a claim. Observed on 0.147.0: the default
52
+ toolset contained no `apply_patch`.
53
+
54
+ **`guards.beforeWrite` on `gemini_cli` depends on the approval mode.** In the default and
55
+ `plan` modes the client declares no write tool to the model at all. `write_file` and
56
+ `replace` appear under `auto_edit`; `run_shell_command` under `yolo`.
57
+
58
+ **`guards.beforeShell` is never resource-aware.** A shell command can write anywhere, and
59
+ ACC does not parse commands. The guard fires and is allowed through, because guessing a
60
+ path out of a command would block work at random and still miss real writes. Shell calls
61
+ therefore declare no targets on every adapter.
62
+
63
+ Where the guard cannot help, the turn context does: it names the claims other sessions
64
+ hold and says which way this session stands with them. Two facts decide the wording -
65
+ what the claim's owner asked for, and whether ACC can stop this session at all:
66
+
67
+ | Claim | This session | Note |
68
+ |---|---|---|
69
+ | guarded | can be guarded | `file edits are blocked; edits made through a shell are not` |
70
+ | guarded | cannot be guarded | `not enforced for this session; do not edit it` |
71
+ | advisory | either | `advisory; nothing will stop you, the owner is asking` |
72
+
73
+ Unenforceable is not the same as unknown - and neither is it the same as unclaimed.
74
+
75
+ **`lifecycle.sessionEnd` on `kimi` is false and it matters.** Each `kimi -p` run leaves an
76
+ attached session that only ages out on its declared 60s cadence, so a peer reading the
77
+ roster inside that window sees sessions that have already exited. Interactive sessions
78
+ heartbeat and do not have this problem.
79
+
80
+ **`lifecycle.heartbeat` is Kimi's alone.** It fires on a timer - observed at 60002, 120004
81
+ and 180006 ms of uptime - so an idle Kimi session keeps its presence honest. The other
82
+ three reach a hook only when the user takes a turn, so their idle sessions go stale while
83
+ alive. This is why it is a capability of its own rather than a flavour of
84
+ `delivery.polling`.
85
+
86
+ ## Response contracts, which do not port
87
+
88
+ The single most portable-looking mistake an adapter can make. Measured by running each
89
+ candidate against a real session of each client and checking whether the tool actually
90
+ ran.
91
+
92
+ A dash means the candidate was never run against that client, not that it fails. Only the
93
+ shape each adapter actually uses was measured on every client.
94
+
95
+ | Reply to a guard hook | codex | claude_code | gemini_cli | kimi |
96
+ |---|---|---|---|---|
97
+ | exit code 2 | denies | - | denies | denies |
98
+ | `{"hookSpecificOutput":{…,"permissionDecision":"deny"}}` | - | denies | **ignored** | denies |
99
+ | `{"decision":"block","reason":…}` | - | - | denies | **ignored** |
100
+ | `{"permission":"deny"}` | - | - | ignored | ignored |
101
+ | exit code 1 | - | - | ignored | ignored |
102
+
103
+ Codex has no structured reply at all: it denies by exiting 2 with the reason on stderr.
104
+ Gemini ignores the shape that Claude Code and Kimi Code both honour, and Kimi ignores the
105
+ shape Gemini needs. Each ignored case fails silently - the write goes through and the
106
+ client reports nothing.
107
+
108
+ Context injection does not follow the deny contract even within one client:
109
+
110
+ | Injection | codex | claude_code | gemini_cli | kimi |
111
+ |---|---|---|---|---|
112
+ | `hookSpecificOutput.additionalContext` | - | works | works | works, but **not unwrapped** |
113
+ | plain text on stdout | works | - | dropped | works |
114
+
115
+ Codex delivers a hook's stdout as a `developer` role message, verbatim - the most direct
116
+ of the four channels, and a reason for care rather than comfort: at that role a model
117
+ reads text as instruction, so peer-authored text has to stay framed as data.
118
+
119
+ Kimi Code shows the model whatever a hook printed, wrapped in
120
+ `<hook_result hook_event="…">`, so the JSON envelope itself would end up in the
121
+ conversation. Gemini unwraps the envelope and appends `<hook_context>…</hook_context>` to
122
+ the user turn, and drops a bare string entirely.
123
+
124
+ ## Installation is not uniform either
125
+
126
+ | | codex | claude_code | gemini_cli | kimi |
127
+ |---|---|---|---|---|
128
+ | Where hooks live | marketplace plugin | plugin | `settings.json` | `config.toml` |
129
+ | Project-level config | no | no | yes | **no** |
130
+ | Hook `timeout` unit | - | - | milliseconds | **seconds** (max 600) |
131
+ | Command path | absolute required | `${CLAUDE_PLUGIN_ROOT}` | absolute required | absolute required |
132
+ | Extra step by the user | hook trust | - | - | - |
133
+
134
+ Kimi Code is the only one with no project-level config, so ACC edits the user's global
135
+ `config.toml` - as a delimited block it owns, because ACC ships without dependencies and a
136
+ hand-written TOML round-tripper would take the user's comments and formatting with it.
137
+
138
+ Codex needs four things before a hook runs, not one: the plugin directory, a parseable
139
+ marketplace, both `[marketplaces.…]` and `[plugins."…"]` registered in its config, and the
140
+ plugin copied into `plugins/cache/<marketplace>/<plugin>/<version>/`. ACC does all four -
141
+ that last copy is exactly and only what `codex plugin add` does, measured by diffing the
142
+ home around it. Hook trust remains a manual step, which is the client's security model.
143
+
144
+ ## What a participant declares about itself
145
+
146
+ Every session records `enforcement` (`guarded` | `advisory`) and `lifecycle`
147
+ (`managed` | `manual`), taken from the adapter's proven capabilities rather than from the
148
+ harness name. Both default to the weaker reading, so a generic MCP client or a human at
149
+ the CLI reads as advisory and manual.
150
+
151
+ A workspace reports `protection: guarded` only when every live session can be stopped. One
152
+ MCP client, one Kimi session, or one Codex session on a shell-editing model, and a guarded
153
+ claim is advice - so the workspace says `advisory`, whatever its claims were declared as.
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "@agents-can-communicate/adapter-claude-code",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./src/adapter.mjs"
8
+ },
9
+ "files": [
10
+ "src/",
11
+ "plugin/"
12
+ ]
13
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "agents-can-communicate",
3
+ "version": "0.1.0",
4
+ "description": "Coordinate this Claude Code session with other AI agent sessions working in the same workspace: shared presence, resource claims, typed messages, and handoffs."
5
+ }
@@ -0,0 +1,56 @@
1
+ {
2
+ "description": "Attach this session to the local coordination plane.",
3
+ "hooks": {
4
+ "SessionStart": [
5
+ {
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/acc-hook.sh\" sessionStart"
10
+ }
11
+ ]
12
+ }
13
+ ],
14
+ "UserPromptSubmit": [
15
+ {
16
+ "hooks": [
17
+ {
18
+ "type": "command",
19
+ "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/acc-hook.sh\" beforeTurn"
20
+ }
21
+ ]
22
+ }
23
+ ],
24
+ "PreToolUse": [
25
+ {
26
+ "matcher": "Write|Edit|Bash",
27
+ "hooks": [
28
+ {
29
+ "type": "command",
30
+ "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/acc-hook.sh\" guard"
31
+ }
32
+ ]
33
+ }
34
+ ],
35
+ "Stop": [
36
+ {
37
+ "hooks": [
38
+ {
39
+ "type": "command",
40
+ "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/acc-hook.sh\" finish"
41
+ }
42
+ ]
43
+ }
44
+ ],
45
+ "SessionEnd": [
46
+ {
47
+ "hooks": [
48
+ {
49
+ "type": "command",
50
+ "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/acc-hook.sh\" sessionEnd"
51
+ }
52
+ ]
53
+ }
54
+ ]
55
+ }
56
+ }