@cordfuse/crosstalkd 7.0.0-alpha.1
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.
- package/GUIDE-CLI.md +315 -0
- package/GUIDE-PROMPTS.md +107 -0
- package/README.md +118 -0
- package/bin/crosstalkd.js +101 -0
- package/package.json +48 -0
- package/src/activation.ts +104 -0
- package/src/api.ts +430 -0
- package/src/channel.ts +202 -0
- package/src/dispatch.ts +430 -0
- package/src/dispatchers.ts +91 -0
- package/src/filenames.ts +28 -0
- package/src/frontmatter.ts +26 -0
- package/src/init.ts +108 -0
- package/src/invoke.ts +148 -0
- package/src/models.ts +86 -0
- package/src/replies.ts +73 -0
- package/src/run.ts +236 -0
- package/src/state.ts +159 -0
- package/src/status.ts +84 -0
- package/src/stop.ts +37 -0
- package/src/transport.ts +236 -0
- package/src/workflow.ts +458 -0
- package/template/CLAUDE.md +10 -0
- package/template/CROSSTALK-VERSION +1 -0
- package/template/CROSSTALK.md +242 -0
- package/template/PROTOCOL.md +66 -0
- package/template/README.md +69 -0
- package/template/data/models.yaml +27 -0
- package/template/gitignore +4 -0
package/GUIDE-CLI.md
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# Crosstalk CLI Guide
|
|
2
|
+
|
|
3
|
+
> **This guide is being rewritten for v7.0.0-alpha.1's engine/client split.** The body below describes the original pre-split surface — host commands like `crosstalk dispatch` and `crosstalk stop` no longer exist. Live surface is documented inline via `crosstalk --help` (host client, 12 subcommands) and `crosstalkd --help` inside the container (engine, 7 subcommands).
|
|
4
|
+
>
|
|
5
|
+
> **Quick mapping for operators upgrading from a pre-split build:**
|
|
6
|
+
> - `crosstalk init <dir>` — works (now also auto-`git init`s on `main`)
|
|
7
|
+
> - `crosstalk up` / `down` / `restart` / `pull` / `logs` — new (manage the engine container)
|
|
8
|
+
> - `crosstalk dispatch` — gone from host; engine runs inside the container, managed via `crosstalk up`
|
|
9
|
+
> - `crosstalk stop` — gone from host; `crosstalk down` stops the container
|
|
10
|
+
> - `crosstalk run` / `replies` / `status` / `channel` — same surface, now talks to the engine via HTTP API on 127.0.0.1
|
|
11
|
+
> - `crosstalk chat [--agent X] [--login] [--shell]` — new (single interactive entry point — daily agent chat, OAuth flow with browser-open, sysadmin shell)
|
|
12
|
+
>
|
|
13
|
+
> A proper rewrite of this guide ships in v7.0.0-alpha.2.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
> **Original (pre-split) reference — preserved for now:**
|
|
18
|
+
|
|
19
|
+
Quick reference for the `crosstalk` CLI. Run `crosstalk <subcommand> --help` for flags.
|
|
20
|
+
|
|
21
|
+
Requires: Node.js ≥ 20. All subcommands (except `init` and `version`) must be run from inside a transport (a directory containing `CROSSTALK-VERSION`).
|
|
22
|
+
|
|
23
|
+
The whole surface is **eight subcommands**: `init`, `run`, `dispatch`, `stop`, `status`, `replies`, `channel`, `version`.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
29
|
+
### `crosstalk init <dir>`
|
|
30
|
+
|
|
31
|
+
Scaffold a new transport in the given directory:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
mkdir my-transport && cd my-transport
|
|
35
|
+
git init
|
|
36
|
+
crosstalk init .
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Copies the template into the directory:
|
|
40
|
+
|
|
41
|
+
- `CROSSTALK-VERSION` — protocol version (`7`). The runtime checks this on every boot and refuses to operate on incompatible transports.
|
|
42
|
+
- `PROTOCOL.md` — agent orientation file; the dispatcher prepends this to every model invocation.
|
|
43
|
+
- `CROSSTALK.md` — the protocol specification for humans.
|
|
44
|
+
- `data/models.yaml` — the model registry seed. Edit this to add or remove model entries.
|
|
45
|
+
- `local/actors/orchestrator.md` — the orchestrator persona file (interprets workflow documents).
|
|
46
|
+
- `CLAUDE.md` — agent orientation for repo-savvy CLIs landing inside the transport.
|
|
47
|
+
|
|
48
|
+
What `init` does **not** do:
|
|
49
|
+
|
|
50
|
+
- No host file is generated. v7 has no host files at all — machine identity comes from `--alias` at dispatch boot.
|
|
51
|
+
- No first channel is created. Make one yourself with `crosstalk channel <name>`.
|
|
52
|
+
- No git remote is configured.
|
|
53
|
+
|
|
54
|
+
Commit and push to your git remote when you have one; that repo is now the message bus. Single-machine and local-only is fine — you can skip the remote entirely while you're trying things out.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Models
|
|
59
|
+
|
|
60
|
+
Crosstalk is **agent-agnostic**. A "model" in v7 is a named entry in `data/models.yaml` mapping a name to a shell command that invokes some agent CLI in non-interactive mode.
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
# data/models.yaml
|
|
64
|
+
sonnet: claude --print --model claude-sonnet-4 --dangerously-skip-permissions
|
|
65
|
+
haiku: claude --print --model claude-haiku-4-5 --dangerously-skip-permissions
|
|
66
|
+
opus: claude --print --model claude-opus-4 --dangerously-skip-permissions
|
|
67
|
+
codex-o3: codex exec --model o3
|
|
68
|
+
gemini-pro: gemini --skip-trust --yolo -p --model gemini-1.5-pro
|
|
69
|
+
qwen3-coder: qwen --yolo --model qwen3-coder
|
|
70
|
+
opencode: opencode -p
|
|
71
|
+
agy: agy -p
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### PATH-based self-selection
|
|
75
|
+
|
|
76
|
+
Each dispatcher reads `data/models.yaml` at boot, takes the first token of each entry (`claude`, `codex`, `gemini`, …), and checks PATH. **Only models whose CLI is installed locally are claimed.** No declarations, no host files.
|
|
77
|
+
|
|
78
|
+
A laptop with `claude` and `gemini` installed claims `sonnet`, `haiku`, `opus`, `gemini-pro`. A server with only `claude` claims just the Claude entries.
|
|
79
|
+
|
|
80
|
+
### The dispatch contract
|
|
81
|
+
|
|
82
|
+
When the dispatcher invokes a model it:
|
|
83
|
+
|
|
84
|
+
1. Composes `[persona-file body] + [pending messages]` and **appends it as the last argument** to the shell command.
|
|
85
|
+
2. Reads the CLI's **stdout** as the reply body; expects exit code **0**.
|
|
86
|
+
|
|
87
|
+
Two things to get right per model:
|
|
88
|
+
|
|
89
|
+
- **Non-interactive flag.** Each agent has a flag that stops it from pausing for confirmation or opening a TUI. Without it, every dispatch hangs.
|
|
90
|
+
- **Trailing-argument shape.** The dispatcher tacks the prompt on the end of whatever you put in the command. `claude --print` becomes `claude --print "<prompt>"`. Every modern agent CLI accepts this shape.
|
|
91
|
+
|
|
92
|
+
> For prompts larger than 64 KB, the dispatcher falls back to writing the prompt to **stdin** automatically.
|
|
93
|
+
|
|
94
|
+
### Recipes
|
|
95
|
+
|
|
96
|
+
Starting points — **verify flags against your installed CLI version**; vendors change them.
|
|
97
|
+
|
|
98
|
+
| Agent | Example entry | Notes |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| Claude Code | `sonnet: claude --print --dangerously-skip-permissions` | accepts the prompt as a positional arg |
|
|
101
|
+
| OpenAI Codex | `codex-o3: codex exec --model o3` | `exec` is non-interactive. Auth: `OPENAI_API_KEY` in env is not sufficient on its own — Codex caches mode in `~/.codex/auth.json`; run `printenv OPENAI_API_KEY \| codex login --with-api-key` once per fresh volume. |
|
|
102
|
+
| Gemini CLI | `gemini-pro: gemini --skip-trust --yolo -p` | `--skip-trust` is required for headless dispatch; `--yolo` skips approvals; `-p` must be last so it captures the appended prompt. |
|
|
103
|
+
| Qwen Code | `qwen3-coder: qwen --yolo` | `--yolo` skips approvals; prompt is appended as positional. |
|
|
104
|
+
| opencode | `opencode: opencode -p` | `-p` = one-shot, OAuth via `opencode auth login`. Auth persists in `~/.local/share/opencode/auth.json`. |
|
|
105
|
+
| Antigravity | `agy: agy -p` | `-p` is non-interactive. OAuth-only in 1.0.7 — no API-key env-var path; complete the device-code sign-in once interactively per fresh volume. |
|
|
106
|
+
|
|
107
|
+
You can mix agents in one transport — different models can run entirely different vendors. Senders never know or care which agent answered.
|
|
108
|
+
|
|
109
|
+
> **Privacy note for shared infrastructure.** The prompt is passed as an argv arg, which means it's visible to anything that can read the dispatcher process's `/proc/<pid>/cmdline`. On a single-user laptop or dedicated dispatcher VM that's a non-issue; on multi-user machines, plan accordingly.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Actors
|
|
114
|
+
|
|
115
|
+
An actor is an optional persona file at `local/actors/<name>.md`. Plain markdown, no required frontmatter. The full body is prepended to a model's prompt as system context when `--as <name>` is passed to `run`.
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
crosstalk run --type primitive --to sonnet --as junior-developer "implement this"
|
|
119
|
+
crosstalk run --type primitive --to opus --as senior-architect "review this"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Actors are **not bound** to machines or models. Any model can be invoked with any actor. There are no declarations, no tier wiring, no host file lookup.
|
|
123
|
+
|
|
124
|
+
The seed transport ships one actor: **`orchestrator`** — the persona that teaches a model how to interpret workflow markdown documents. Used implicitly when a workflow is dispatched.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## `crosstalk run`
|
|
129
|
+
|
|
130
|
+
The unified dispatch verb. Two modes: `primitive` (atomic single send, optionally fanned-out) and `workflow` (orchestrator-interpreted multi-step document).
|
|
131
|
+
|
|
132
|
+
### Primitive
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
crosstalk run --type primitive --to sonnet@laptop "What is the capital of France?"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Prints `Sent: <relPath>` — save that path if you need to check for replies.
|
|
139
|
+
|
|
140
|
+
| Flag | Required | Notes |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| `--type primitive` | yes | distinguishes from workflow |
|
|
143
|
+
| `--to <model>[@<machine>]` | yes | bare model name reaches any dispatcher claiming it; `@<machine>` pins to one alias |
|
|
144
|
+
| `--as <actor>` | no | persona file from `local/actors/<actor>.md`, prepended as system context |
|
|
145
|
+
| `--fanout <n>` | no, default 1 | parallel instances of the same send; all N replies share `from:`, differentiated by relPath |
|
|
146
|
+
| `--channel <name-or-uuid>` | only if multiple channels exist | auto-detected when transport has exactly one channel |
|
|
147
|
+
| `--from <name>` | no | defaults to `$USER` / `operator` |
|
|
148
|
+
| `--new` | no | suppress automatic `re:` linking (start new work, not a reply) |
|
|
149
|
+
| `<body>` | yes | inline string, file path, or `-` for stdin |
|
|
150
|
+
|
|
151
|
+
### Workflow
|
|
152
|
+
|
|
153
|
+
```sh
|
|
154
|
+
crosstalk run --type workflow workflows/review-and-synthesize.md
|
|
155
|
+
crosstalk run --type workflow - # stdin
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The document's frontmatter must declare:
|
|
159
|
+
|
|
160
|
+
```yaml
|
|
161
|
+
---
|
|
162
|
+
type: workflow
|
|
163
|
+
to: opus@cachy
|
|
164
|
+
as: orchestrator
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
1. Fan out 3 junior developers running sonnet@laptop.
|
|
168
|
+
2. Send the drafts to a reviewer running opus@cachy.
|
|
169
|
+
3. Return the synthesis to the original requester.
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
At dispatch the runtime:
|
|
173
|
+
|
|
174
|
+
1. Creates a new child channel parented to the operator's current channel.
|
|
175
|
+
2. Writes the workflow document as a message into the parent channel addressed to the declared model with `as: orchestrator`.
|
|
176
|
+
3. Adds `child_channel: <uuid>` to the dispatched message's frontmatter; the dispatcher injects this as `CROSSTALK_CHILD_CHANNEL` when invoking the orchestrator.
|
|
177
|
+
4. The orchestrator persona interprets the steps and fires sub-`crosstalk run --type primitive` calls into the child channel.
|
|
178
|
+
|
|
179
|
+
| Flag | Required | Notes |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| `--type workflow` | yes | |
|
|
182
|
+
| `--channel <name-or-uuid>` | only if multiple channels exist | runs the workflow inside an existing channel instead of auto-creating a child |
|
|
183
|
+
| `<file-or-->` | yes | path to a workflow markdown document, or `-` to read from stdin |
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## `crosstalk dispatch --alias <name>`
|
|
188
|
+
|
|
189
|
+
Start the dispatch loop in the current transport. Runs forever, polling for new messages, invoking claimed models.
|
|
190
|
+
|
|
191
|
+
```sh
|
|
192
|
+
crosstalk dispatch --alias laptop &
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
| Flag | Required | Notes |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| `--alias <name>` | yes | this machine's identity within the bus; appears in `from:` of replies and routing targets in `to:` |
|
|
198
|
+
| `--poll <seconds>` | no, default 30 | quiet-tick interval; active ticks drop to 1s automatically |
|
|
199
|
+
| `--json` | no | structured JSON log lines |
|
|
200
|
+
| `--log-file <path>` | no | mirror logs to a file |
|
|
201
|
+
| `--once` | no | run one tick then exit (useful for testing) |
|
|
202
|
+
|
|
203
|
+
Dispatcher state (cursor, heartbeat, pidfile, wake.signal) is stored at `~/.config/crosstalk/state/<transport-name>/` — never in the transport repo. Four files, machine-global.
|
|
204
|
+
|
|
205
|
+
### Stopping
|
|
206
|
+
|
|
207
|
+
```sh
|
|
208
|
+
crosstalk stop
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Reads the pidfile, sends SIGTERM.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Observability
|
|
216
|
+
|
|
217
|
+
### `crosstalk status`
|
|
218
|
+
|
|
219
|
+
Prints:
|
|
220
|
+
|
|
221
|
+
- Transport root and protocol version
|
|
222
|
+
- Channels (name → uuid)
|
|
223
|
+
- Dispatcher heartbeat (last tick timestamp, current cursor SHA)
|
|
224
|
+
- Claimed models on this machine (from `data/models.yaml` PATH-filter)
|
|
225
|
+
|
|
226
|
+
```sh
|
|
227
|
+
crosstalk status
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### `crosstalk replies <relPath>...`
|
|
231
|
+
|
|
232
|
+
Show which of your dispatched messages have replies. Matches via the runtime-written `re:` field — ground truth, not body-parsing.
|
|
233
|
+
|
|
234
|
+
```sh
|
|
235
|
+
crosstalk replies 2026/06/10/130847758Z-780a8b90.md
|
|
236
|
+
crosstalk replies path1.md path2.md path3.md # multiple, space-separated
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Channels
|
|
242
|
+
|
|
243
|
+
### `crosstalk channel <name>`
|
|
244
|
+
|
|
245
|
+
Create a new channel and print its UUID.
|
|
246
|
+
|
|
247
|
+
```sh
|
|
248
|
+
crosstalk channel sprint-42
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Errors on name collision (strict mode — duplicate names rejected at create time). Errors if `<name>` matches a UUID regex (reserved for the actual UUID).
|
|
252
|
+
|
|
253
|
+
### `crosstalk channel <name-or-uuid> --rename <new>`
|
|
254
|
+
|
|
255
|
+
Rename the channel. UUID stays; only the label changes. Errors on collision.
|
|
256
|
+
|
|
257
|
+
```sh
|
|
258
|
+
crosstalk channel sprint-42 --rename sprint-43
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### `crosstalk channel <name-or-uuid> --delete`
|
|
262
|
+
|
|
263
|
+
Hard delete the channel. `rm -rf data/channels/<uuid>/`, commit. Requires typed-name confirmation:
|
|
264
|
+
|
|
265
|
+
```sh
|
|
266
|
+
crosstalk channel sprint-42 --delete
|
|
267
|
+
# prompt: type "sprint-42" to confirm deletion of channel sprint-42 (<uuid>):
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
No archive, no restore, no `--force` flag. History remains in `git log` if you ever need it back (`git revert` or `git checkout <commit> -- data/channels/<uuid>/`).
|
|
271
|
+
|
|
272
|
+
For scripted deletion, pipe stdin: `echo sprint-42 | crosstalk channel sprint-42 --delete`.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## `crosstalk version`
|
|
277
|
+
|
|
278
|
+
Print the installed runtime version.
|
|
279
|
+
|
|
280
|
+
```sh
|
|
281
|
+
crosstalk version
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Common patterns
|
|
287
|
+
|
|
288
|
+
**One-shot task from a script:**
|
|
289
|
+
|
|
290
|
+
```sh
|
|
291
|
+
crosstalk channel "batch-$(date +%s)" > /tmp/chan.txt
|
|
292
|
+
crosstalk run --type primitive --to sonnet --channel "batch-$(date +%s)" "Summarise this file: ..."
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Check if fan-out replies are all in:**
|
|
296
|
+
|
|
297
|
+
```sh
|
|
298
|
+
crosstalk replies path1.md path2.md path3.md path4.md path5.md
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Send a multi-line prompt from a file:**
|
|
302
|
+
|
|
303
|
+
```sh
|
|
304
|
+
crosstalk run --type primitive --to sonnet ./prompt.md
|
|
305
|
+
# or
|
|
306
|
+
cat prompt.md | crosstalk run --type primitive --to sonnet -
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**Troubleshooting a stuck dispatcher:**
|
|
310
|
+
|
|
311
|
+
```sh
|
|
312
|
+
crosstalk status # check heartbeat freshness and last cursor SHA
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
If failures land in the channel as `failed: true` replies, inspect the channel directly — no separate DLQ subsystem.
|
package/GUIDE-PROMPTS.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Crosstalk Natural-Language Guide
|
|
2
|
+
|
|
3
|
+
> **Updated for v7.0.0-alpha.1's engine/client split.** Interactive mode is now via `crosstalk chat` (host client) — runs the agent CLI inside the engine container with the operator's TTY attached and the transport bind-mounted in. The agent sees the same `PROTOCOL.md` it would have natively.
|
|
4
|
+
|
|
5
|
+
You don't have to memorise the `crosstalk` CLI to use Crosstalk. You can just **talk to it.**
|
|
6
|
+
|
|
7
|
+
The interactive Crosstalk pattern in v7: **`cd` into a transport directory and run `crosstalk chat`.** That opens a PTY-attached `docker exec` session into the engine container, running your default agent CLI (`claude`; use `--agent <name>` for others). The transport's `PROTOCOL.md` orients the agent — it learns the layout, the six core nouns, and which `crosstalkd` commands (the in-container engine) it can run on your behalf. From there you speak in plain language; the agent translates into commands, waits for answers, and tells you what came back.
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
cd ~/my-transport && crosstalk chat
|
|
11
|
+
> ask sonnet on cachy to summarise report.md
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The agent CLI owns the (container's) terminal; Crosstalk owns the bus; they meet at the bind-mounted filesystem.
|
|
15
|
+
|
|
16
|
+
> **New to Crosstalk?** The [main README](README.md) has the six-word glossary (Transport / Machine / Message / Model / Actor / Channel) plus a local-only quickstart. Worth a 60-second read first.
|
|
17
|
+
|
|
18
|
+
This is the companion to the **[CLI guide](GUIDE-CLI.md)**. Same system, two front doors: type commands, or just speak.
|
|
19
|
+
|
|
20
|
+
> The natural-language interface is **agent-agnostic**, like the rest of Crosstalk. Whatever coding agent you already run — Claude Code, Codex, Gemini, Qwen Code, opencode, Antigravity — can be your interactive front door, as long as it can read `PROTOCOL.md` and run shell commands.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Starting an interactive session
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
cd ~/my-transport
|
|
28
|
+
claude # or: gemini --skip-trust, codex, agy, opencode, qwen
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
When the agent CLI starts, it reads `PROTOCOL.md` and any `CLAUDE.md` / `GEMINI.md` / equivalent orientation file at the transport root. Those files tell the agent it's inside a Crosstalk transport, list the six nouns, and document the `crosstalk` subcommands it can use to send messages on your behalf.
|
|
32
|
+
|
|
33
|
+
You don't need to teach the agent anything. Start talking.
|
|
34
|
+
|
|
35
|
+
> **Interactive ≠ being a model.** When you're talking to the agent this way, *you* (through the agent) are the human's front door. You are not a model in the bus and you do not process incoming messages. Leave message processing to the running dispatcher (`crosstalk dispatch --alias <name>`). Don't start a dispatcher from inside this session — it would compete with the one already running.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## The phrasebook
|
|
40
|
+
|
|
41
|
+
Say it however feels natural; these are the intents the interactive agent recognises and what it does for you. The default `orchestrator` actor is referenced in some examples — it's the persona file `crosstalk init` ships, used for interpreting workflow documents. Substitute any other model name (`sonnet`, `opus`, `codex-o3`, …) or actor name you've wired up.
|
|
42
|
+
|
|
43
|
+
| You say… | What happens |
|
|
44
|
+
|---|---|
|
|
45
|
+
| "ask sonnet to summarise `report.md`" | sends a primitive to `sonnet`, waits, surfaces the reply |
|
|
46
|
+
| "ask sonnet on cachy to look for typos" | sends to `sonnet@cachy`, waits, reports back |
|
|
47
|
+
| "fan out three drafts running sonnet" | runs `crosstalk run --type primitive --to sonnet --fanout 3 "..."`, surfaces all three replies when they arrive |
|
|
48
|
+
| "in the *sprint-42* channel, ask opus to plan the work" | scopes the message to that channel by name |
|
|
49
|
+
| "run this workflow" (with file path or pasted document) | dispatches a workflow document via `crosstalk run --type workflow`; auto-creates a child channel for the chatter |
|
|
50
|
+
| "is the dispatcher alive?" / "check status" | runs `crosstalk status`, explains the heartbeat plainly |
|
|
51
|
+
| "what failed?" | inspects the current channel for `failed: true` replies and summarises |
|
|
52
|
+
| "make a channel called *release-7*" | runs `crosstalk channel release-7`, tells you the UUID |
|
|
53
|
+
| "what channels exist?" | runs `crosstalk status`, lists channels by their human names |
|
|
54
|
+
| "rename this channel to *release-8*" | runs `crosstalk channel <current> --rename release-8` |
|
|
55
|
+
| "delete the *batch-old* channel" | runs `crosstalk channel batch-old --delete`, types the confirmation |
|
|
56
|
+
| "tweak the orchestrator persona to be more terse" | edits `local/actors/orchestrator.md`, commits, pushes |
|
|
57
|
+
| "add a new model called fable" | adds a line to `data/models.yaml`, commits, pushes |
|
|
58
|
+
| "pull the latest" | runs `git pull --rebase` |
|
|
59
|
+
|
|
60
|
+
There is no "what's in the DLQ?" — v7 has no DLQ. Failures land in the channel as normal messages with `failed: true` + `error:` in frontmatter; "what failed?" asks the agent to grep the current channel for those.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Coordinating a team in plain language
|
|
65
|
+
|
|
66
|
+
The real power is workflows. Describe the goal and let the orchestrator dispatch:
|
|
67
|
+
|
|
68
|
+
> **You:** "Ask the orchestrator on cachy to fan out three drafts of the release notes via sonnet, then have opus review and synthesize them."
|
|
69
|
+
|
|
70
|
+
The interactive agent translates that into a workflow document, runs `crosstalk run --type workflow -` (stdin), and the runtime auto-creates a child channel for the work. The orchestrator on cachy receives the workflow, parses the prose, fires the sub-primitives into the child channel, waits for replies via `re:` links, aggregates, and posts the synthesis back to you in the parent channel. You see:
|
|
71
|
+
|
|
72
|
+
> *(waiting for orchestrator…)*
|
|
73
|
+
> **opus@cachy:** Synthesis attached. All three drafts emphasized X; consolidated and tightened. …
|
|
74
|
+
|
|
75
|
+
You never see the orchestration — only the result. To drill into how the work happened, ask "show me the child channel for that workflow."
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## What to expect
|
|
80
|
+
|
|
81
|
+
- **It's asynchronous.** Each round-trip is roughly 5–30 seconds — your message is committed and pushed, the recipient's dispatcher polls, the agent runs, its reply is committed and pulled back. The interactive agent will say something like *"(waiting for sonnet…)"* and surface the answer when it arrives.
|
|
82
|
+
- **Replies are matched by fact, not by claim.** Crosstalk records which message each reply answers via the `re:` field, so "has it replied yet?" is always answered from ground truth — a busy or confused model can't fake completion.
|
|
83
|
+
- **At-least-once delivery.** For anything with side effects (sending an email, deploying), the interactive agent — and the models — check the channel for prior completion before repeating. For lookups and advice, the occasional duplicate is harmless.
|
|
84
|
+
- **Failures are loud.** A failed dispatch lands as a normal reply with `failed: true` + `error: <text>`. The interactive agent will surface failures the same way it surfaces successes — no separate place to check.
|
|
85
|
+
- **If nothing comes back** (~10 minutes), the interactive agent will offer to check `crosstalk status` for the dispatcher heartbeat. Usually it's a dispatcher that's down or a model whose CLI isn't wired correctly (see [GUIDE-CLI.md → Models](GUIDE-CLI.md#models)).
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Tips for good results
|
|
90
|
+
|
|
91
|
+
- **Name the machine when it matters.** "ask sonnet" reaches sonnet on whichever dispatcher claims it first; "ask sonnet on cachy" pins it to one machine. Use the machine when *where* the work runs is part of the ask.
|
|
92
|
+
- **Name the channel for threaded work.** If you don't, and there's more than one channel, the agent will ask which one rather than guess.
|
|
93
|
+
- **Be explicit for non-idempotent actions.** "deploy to staging" is a side effect; say so clearly and the model will verify it hasn't already happened.
|
|
94
|
+
- **You don't need to know UUIDs or paths — ever.** If the agent starts showing you raw file paths or git output, tell it to "talk to me like a person"; hiding that machinery is its job.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## When to drop to the CLI
|
|
99
|
+
|
|
100
|
+
Natural language covers day-to-day operation. Reach for the **[CLI guide](GUIDE-CLI.md)** when you're:
|
|
101
|
+
|
|
102
|
+
- scripting or automating (cron jobs, CI),
|
|
103
|
+
- editing `data/models.yaml` to wire a new agent,
|
|
104
|
+
- running the dispatcher itself (`crosstalk dispatch --alias <name>`), or
|
|
105
|
+
- inspecting state at a low level (cursor SHA, heartbeat freshness).
|
|
106
|
+
|
|
107
|
+
Both drive the exact same transport — use whichever is faster for the task in front of you.
|
package/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# @cordfuse/crosstalkd — engine
|
|
2
|
+
|
|
3
|
+
> **README is mid-rewrite.** v7 splits the runtime into an in-container daemon (`@cordfuse/crosstalkd`, this package) and a host-side client (`@cordfuse/crosstalk`, separate package landing in P2). Operators should NOT install this package directly — install `@cordfuse/crosstalk` on the host instead. This package is baked into the `crosstalk-server` container image; operators never interact with `crosstalkd` directly. The body of this README below pre-dates the split and is being rewritten in the docs-consolidation pass (P6).
|
|
4
|
+
|
|
5
|
+
> Part of the [Crosstalk repo](https://github.com/cordfuse/crosstalk) — the root README has the full problem statement, design philosophy, and repository layout. The other tier in the same repo is the [transport template](../transport/).
|
|
6
|
+
|
|
7
|
+
The `crosstalkd` daemon binary. Installs as a single Node.js binary; runs as the long-lived process inside the `crosstalk-server` container. Provides every protocol-layer verb in the Crosstalk protocol: scaffold a transport, dispatch primitives and workflows, run the per-machine loop, manage channels.
|
|
8
|
+
|
|
9
|
+
> **What Crosstalk is.** Crosstalk is an agent-agnostic swarm communication protocol built on git. A git repo is the message bus. Any CLI that accepts a prompt and prints a reply can be a model; messages are committed files; coordination is peer-to-peer with no broker. Full background: **[github.com/cordfuse/crosstalk](https://github.com/cordfuse/crosstalk#why-crosstalk-exists)**.
|
|
10
|
+
|
|
11
|
+
> **v7 status.** This package is in active development. The quickstart below describes the v7.0 surface and works end-to-end at the v7.0.0-alpha.1 tag. Branch state precedes runtime readiness — check `crosstalkd version` if in doubt.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm install -g @cordfuse/crosstalk
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Requires Node.js ≥ 20. Works on Linux and macOS. Windows is untested.
|
|
22
|
+
|
|
23
|
+
Verify:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
crosstalk version
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quickstart (local-only — no remote git required)
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
# 1. Install an agent CLI. Easiest is Claude Code:
|
|
33
|
+
curl -fsSL https://claude.ai/install.sh | bash
|
|
34
|
+
claude # one-time interactive login
|
|
35
|
+
|
|
36
|
+
# 2. Scaffold a transport in a fresh directory.
|
|
37
|
+
mkdir my-bus && cd my-bus && git init
|
|
38
|
+
crosstalk init .
|
|
39
|
+
git add -A && git commit -m "initial transport"
|
|
40
|
+
|
|
41
|
+
# 3. Start the dispatcher with this machine's alias.
|
|
42
|
+
crosstalk dispatch --alias laptop &
|
|
43
|
+
# To stop it later: crosstalk stop
|
|
44
|
+
|
|
45
|
+
# 4. Create a channel.
|
|
46
|
+
crosstalk channel general
|
|
47
|
+
|
|
48
|
+
# 5. Ask sonnet a question.
|
|
49
|
+
crosstalk run --type primitive --to sonnet "What is the capital of France?"
|
|
50
|
+
|
|
51
|
+
# 6. Check for replies.
|
|
52
|
+
crosstalk replies <relPath printed by step 5>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The dispatcher reads `data/models.yaml`, checks PATH for the first token of each entry, and claims the models whose CLI is installed locally. Step 5 succeeds because `claude` is on PATH and the `sonnet` entry's command starts with `claude`.
|
|
56
|
+
|
|
57
|
+
To use a different agent, edit `data/models.yaml` — one line per model. No host file to author. No tier wiring. To add a second machine, configure a git remote and have the other machine clone and run `crosstalk dispatch --alias <its-name>`.
|
|
58
|
+
|
|
59
|
+
## Subcommand reference
|
|
60
|
+
|
|
61
|
+
Eight subcommands. Full flag reference: **[GUIDE-CLI.md](./GUIDE-CLI.md)**.
|
|
62
|
+
|
|
63
|
+
| Subcommand | Purpose |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `crosstalk init <dir>` | Scaffold a new transport into the given directory (copies the [template](../transport/)). |
|
|
66
|
+
| `crosstalk run --type primitive\|workflow` | Dispatch a single message or a workflow document. |
|
|
67
|
+
| `crosstalk dispatch --alias <name>` | Run the per-machine dispatch loop with this identity. |
|
|
68
|
+
| `crosstalk stop` | Stop the running dispatcher on this machine cleanly. Reads pidfile, sends SIGTERM. |
|
|
69
|
+
| `crosstalk status` | Print channels (name → uuid), dispatcher heartbeat, claimed models. |
|
|
70
|
+
| `crosstalk replies <relPath>...` | Check which dispatched messages have replies. |
|
|
71
|
+
| `crosstalk channel <name> [--rename \| --delete]` | Channel operations. |
|
|
72
|
+
| `crosstalk version` | Print runtime version. |
|
|
73
|
+
|
|
74
|
+
For natural-language operator use ("ask the orchestrator to fan out three drafts") see **[GUIDE-PROMPTS.md](./GUIDE-PROMPTS.md)**.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## What ships in this package
|
|
79
|
+
|
|
80
|
+
- `bin/crosstalk.js` — the subcommand dispatcher; `npm install -g` links it as `crosstalk`.
|
|
81
|
+
- `src/` — TypeScript source; the runtime is executed via [tsx](https://www.npmjs.com/package/tsx) at run time (no compile step at install).
|
|
82
|
+
- `template/` — a snapshot of the [transport template](../transport/) at publish time. `crosstalk init` copies from here.
|
|
83
|
+
- `GUIDE-CLI.md`, `GUIDE-PROMPTS.md` — operator guides published alongside this package.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Local development
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
cd runtime
|
|
91
|
+
npm install
|
|
92
|
+
npm run build # tsc --noEmit type-check
|
|
93
|
+
node bin/crosstalk.js version # invoke the dev binary directly
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The runtime is type-checked but not transpiled — `tsx` runs the TypeScript sources directly at invocation time. No build step is required for an end-user install.
|
|
97
|
+
|
|
98
|
+
To work on the source while testing against a local transport, point the binary at the dev path:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
cd /path/to/your/transport
|
|
102
|
+
node /path/to/crosstalk/runtime/bin/crosstalk.js status
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Dependencies
|
|
108
|
+
|
|
109
|
+
- [`tsx`](https://www.npmjs.com/package/tsx) — run TypeScript directly without a build step.
|
|
110
|
+
- [`yaml`](https://www.npmjs.com/package/yaml) — frontmatter and `data/models.yaml` parsing.
|
|
111
|
+
|
|
112
|
+
That's it. v7 deliberately ships with two runtime dependencies. The advisory-locking subsystem (`@cordfuse/turnq`) and the pty layer (`@lydell/node-pty`) that v6 carried are both gone — git rebase-retry handles concurrent writers; the pty was only needed for the deleted `attach` subcommand.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
MIT. See [LICENSE](https://github.com/cordfuse/crosstalk/blob/main/LICENSE) in the repo.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// crosstalkd — the daemon binary inside the crosstalk-server container.
|
|
3
|
+
//
|
|
4
|
+
// Operators talk to crosstalkd via the host-side `crosstalk` client
|
|
5
|
+
// (@cordfuse/crosstalk), which shells out via `docker exec`. This binary
|
|
6
|
+
// is the in-container subcommand dispatcher: walks up from cwd to find
|
|
7
|
+
// a Crosstalk transport (identified by CROSSTALK-VERSION at the
|
|
8
|
+
// transport root), then runs the requested subcommand from the engine's
|
|
9
|
+
// installed source via tsx. All args after the subcommand are forwarded.
|
|
10
|
+
// `init` is the one subcommand that runs outside a transport.
|
|
11
|
+
|
|
12
|
+
import { existsSync, statSync } from 'fs';
|
|
13
|
+
import { resolve, join, dirname } from 'path';
|
|
14
|
+
import { spawnSync, spawn } from 'child_process';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
import { createRequire } from 'module';
|
|
17
|
+
|
|
18
|
+
const SUBCOMMANDS = [
|
|
19
|
+
'dispatch', 'stop', 'run', 'replies', 'status', 'init', 'channel',
|
|
20
|
+
];
|
|
21
|
+
const STANDALONE_SUBCOMMANDS = new Set(['init']);
|
|
22
|
+
|
|
23
|
+
function findTransportRoot(startDir) {
|
|
24
|
+
let dir = resolve(startDir);
|
|
25
|
+
while (true) {
|
|
26
|
+
const versionFile = join(dir, 'CROSSTALK-VERSION');
|
|
27
|
+
if (existsSync(versionFile) && statSync(versionFile).isFile()) return dir;
|
|
28
|
+
const parent = dirname(dir);
|
|
29
|
+
if (parent === dir) return null;
|
|
30
|
+
dir = parent;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function printUsage(exitCode = 0) {
|
|
35
|
+
process.stdout.write(
|
|
36
|
+
`Usage: crosstalkd <subcommand> [args...]\n\nSubcommands:\n` +
|
|
37
|
+
SUBCOMMANDS.map((s) => ` ${s}`).join('\n') +
|
|
38
|
+
`\n\nThis is the in-container daemon binary. Operators normally\n` +
|
|
39
|
+
`interact via the host-side 'crosstalk' client, which shells out\n` +
|
|
40
|
+
`here via docker exec.\n\n` +
|
|
41
|
+
`Most subcommands require you to be inside a Crosstalk transport\n` +
|
|
42
|
+
`(a directory containing CROSSTALK-VERSION). 'init' can run\n` +
|
|
43
|
+
`from anywhere to scaffold a new transport.\n`,
|
|
44
|
+
);
|
|
45
|
+
process.exit(exitCode);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const argv = process.argv.slice(2);
|
|
49
|
+
if (argv.length === 0 || argv[0] === '-h' || argv[0] === '--help') printUsage(0);
|
|
50
|
+
|
|
51
|
+
if (argv[0] === '--version' || argv[0] === 'version') {
|
|
52
|
+
const require = createRequire(import.meta.url);
|
|
53
|
+
const { version } = require('../package.json');
|
|
54
|
+
process.stdout.write(`${version}\n`);
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const cmd = argv[0];
|
|
59
|
+
if (!SUBCOMMANDS.includes(cmd)) {
|
|
60
|
+
console.error(`crosstalkd: unknown subcommand '${cmd}'\n`);
|
|
61
|
+
printUsage(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
65
|
+
const srcFile = join(thisDir, '..', 'src', `${cmd}.ts`);
|
|
66
|
+
|
|
67
|
+
let cwd = process.cwd();
|
|
68
|
+
if (!STANDALONE_SUBCOMMANDS.has(cmd)) {
|
|
69
|
+
const root = findTransportRoot(cwd);
|
|
70
|
+
if (!root) {
|
|
71
|
+
console.error(
|
|
72
|
+
`crosstalkd ${cmd}: not inside a Crosstalk transport ` +
|
|
73
|
+
`(no CROSSTALK-VERSION found from ${cwd} upward).`,
|
|
74
|
+
);
|
|
75
|
+
process.exit(2);
|
|
76
|
+
}
|
|
77
|
+
cwd = root;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const require = createRequire(import.meta.url);
|
|
81
|
+
const tsxCli = require.resolve('tsx/cli');
|
|
82
|
+
|
|
83
|
+
// dispatch is long-running: use async spawn so SIGTERM/SIGINT forwarded to
|
|
84
|
+
// the tsx child kills the whole chain cleanly. All other subcommands are
|
|
85
|
+
// short-lived and spawnSync is fine.
|
|
86
|
+
if (cmd === 'dispatch') {
|
|
87
|
+
const child = spawn(process.execPath, [tsxCli, srcFile, ...argv.slice(1)], {
|
|
88
|
+
cwd,
|
|
89
|
+
stdio: 'inherit',
|
|
90
|
+
});
|
|
91
|
+
const forward = (sig) => { try { child.kill(sig); } catch {} };
|
|
92
|
+
process.on('SIGTERM', () => forward('SIGTERM'));
|
|
93
|
+
process.on('SIGINT', () => forward('SIGTERM'));
|
|
94
|
+
child.on('exit', (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
95
|
+
} else {
|
|
96
|
+
const r = spawnSync(process.execPath, [tsxCli, srcFile, ...argv.slice(1)], {
|
|
97
|
+
cwd,
|
|
98
|
+
stdio: 'inherit',
|
|
99
|
+
});
|
|
100
|
+
process.exit(r.status ?? 1);
|
|
101
|
+
}
|