@cordfuse/crosstalk 5.0.0-alpha.7 → 6.0.0-alpha.10
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 +298 -0
- package/GUIDE-PROMPTS.md +132 -0
- package/README.md +139 -0
- package/bin/crosstalk.js +51 -80
- package/package.json +9 -5
- package/src/activation.ts +104 -0
- package/src/actor.ts +29 -4
- package/src/attach.ts +1 -1
- package/src/channel.ts +8 -21
- package/src/chat.ts +52 -115
- package/src/dispatch.ts +288 -660
- package/src/dlq.ts +89 -136
- package/src/init.ts +23 -42
- package/src/open.ts +55 -31
- package/src/replies.ts +59 -0
- package/src/send.ts +87 -72
- package/src/state.ts +173 -0
- package/src/status.ts +18 -57
- package/src/stop.ts +37 -0
- package/src/transport.ts +81 -198
- package/src/turnq.ts +64 -32
- package/src/upgrade.ts +9 -11
- package/src/wake.ts +5 -6
- package/template/CLAUDE.md +12 -2
- package/template/gitignore +4 -0
- package/template/upstream/CROSSTALK-VERSION +1 -1
- package/template/upstream/CROSSTALK.md +172 -463
- package/template/upstream/OPERATOR.md +9 -9
- package/template/upstream/PROTOCOL.md +64 -244
- package/template/upstream/actors/concierge.md +24 -118
- package/src/cursor.ts +0 -48
- package/template/.amazonq/rules/crosstalk.md +0 -2
- package/template/.continue/rules/crosstalk.md +0 -7
- package/template/.cursor/rules/crosstalk.mdc +0 -7
- package/template/.github/copilot-instructions.md +0 -2
- package/template/.windsurfrules +0 -2
- package/template/AGENTS.md +0 -2
- package/template/ANTIGRAVITY.md +0 -2
- package/template/GEMINI.md +0 -2
- package/template/OPENCODE.md +0 -2
- package/template/QWEN.md +0 -2
- package/template/README.md +0 -22
- package/template/local/CROSSTALK.md +0 -4
- package/template/upstream/JITTER.md +0 -24
- package/template/upstream/actors/cloud-architect.md +0 -83
- package/template/upstream/actors/devops-engineer.md +0 -83
- package/template/upstream/actors/documentation-engineer.md +0 -107
- package/template/upstream/actors/infrastructure-engineer.md +0 -83
- package/template/upstream/actors/junior-developer.md +0 -83
- package/template/upstream/actors/precise-generalist.md +0 -48
- package/template/upstream/actors/product-manager.md +0 -83
- package/template/upstream/actors/qa-engineer.md +0 -83
- package/template/upstream/actors/security-engineer.md +0 -92
- package/template/upstream/actors/senior-generalist-engineer.md +0 -111
- package/template/upstream/actors/senior-software-engineer.md +0 -94
- package/template/upstream/actors/skeptic.md +0 -89
- package/template/upstream/actors/technical-writer.md +0 -89
- package/template/upstream/actors/ux-designer.md +0 -83
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
# Crosstalk
|
|
2
2
|
|
|
3
|
-
Version:
|
|
3
|
+
Version: 6.0
|
|
4
4
|
|
|
5
5
|
Crosstalk is a shared file format over git that lets humans and AI agents communicate
|
|
6
|
-
asynchronously. The git repository is the message bus. No special
|
|
7
|
-
to participate beyond git itself.
|
|
6
|
+
asynchronously across machines. The git repository is the message bus. No special
|
|
7
|
+
software is required to participate beyond git itself.
|
|
8
|
+
|
|
9
|
+
Design rule for this spec: every feature must be explainable in one sentence.
|
|
10
|
+
The runtime records facts at write time; it never reconstructs them by inference
|
|
11
|
+
at read time.
|
|
8
12
|
|
|
9
13
|
---
|
|
10
14
|
|
|
11
15
|
## Participants
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
**Humans** — operators and users who post messages directly or via a chat session with an agent. They read replies in the channel and drive the conversation.
|
|
16
|
-
|
|
17
|
-
**Machines** — agents (Claude, Codex, agy, etc.) that process messages and reply autonomously. A machine participant is invoked on a schedule or by a human and acts on any unread messages addressed to it.
|
|
17
|
+
**Humans** — operators who post messages directly or via a chat tool and read replies.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
**Machines** — agents (Claude, Codex, etc.) invoked by a dispatcher to process
|
|
20
|
+
messages addressed to them and reply.
|
|
20
21
|
|
|
21
|
-
The most common
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
The most common machine participant is a **worker**: `to: concierge` means
|
|
23
|
+
"I need something done." The worker acts and replies. The sender may be human
|
|
24
|
+
or machine; the worker does not distinguish.
|
|
24
25
|
|
|
25
26
|
---
|
|
26
27
|
|
|
@@ -28,562 +29,270 @@ Any participant name can fill this role. The operator defines what the worker ac
|
|
|
28
29
|
|
|
29
30
|
```
|
|
30
31
|
<transport>/
|
|
31
|
-
upstream/
|
|
32
|
-
CROSSTALK.md
|
|
33
|
-
CROSSTALK-VERSION
|
|
34
|
-
PROTOCOL.md
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
local/
|
|
39
|
-
CROSSTALK.md
|
|
40
|
-
actors/
|
|
41
|
-
<name>.md
|
|
32
|
+
upstream/ # runtime-managed: spec, agent orientation, defaults
|
|
33
|
+
CROSSTALK.md # this file
|
|
34
|
+
CROSSTALK-VERSION # must be exactly: 6.0
|
|
35
|
+
PROTOCOL.md # agent orientation prompt
|
|
36
|
+
actors/<name>.md # default actor profiles
|
|
37
|
+
local/ # operator-owned: never touched by the runtime
|
|
38
|
+
actors/<name>.md # custom actor profiles (override upstream by name)
|
|
42
39
|
hosts/
|
|
43
|
-
<alias>.md
|
|
40
|
+
<alias>.md # one per machine running a dispatcher
|
|
44
41
|
data/
|
|
45
|
-
channels
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
DD/
|
|
50
|
-
HHMMSSsssZ-<hex>.md
|
|
51
|
-
memories/
|
|
52
|
-
<YYYYMMDDTHHMMSSsssZ-hex>.md
|
|
42
|
+
channels/<uuid>/
|
|
43
|
+
CHANNEL.md # optional channel metadata
|
|
44
|
+
YYYY/MM/DD/HHMMSSmmmZ-<hex>.md # messages
|
|
45
|
+
memories/<stamp>-<hex>.md # shared persistent notes
|
|
53
46
|
```
|
|
54
47
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
-
|
|
59
|
-
|
|
60
|
-
- `data/channels/` — one subdirectory per channel, identified by a UUID v4. Discovered by listing the directory at runtime; no configuration needed. To create a channel, create the directory and commit — any participant may do this.
|
|
61
|
-
- `data/channels/<guid>/CHANNEL.md` — optional channel metadata file. Declares name, description, and parent channel for subchannels.
|
|
62
|
-
- `data/memories/` — shared memory files, readable and writable by any participant.
|
|
48
|
+
That is the complete committed surface. **Dispatcher bookkeeping (cursors, dead-letter
|
|
49
|
+
queue, error logs, lock state) is machine-local state and never lives in the repo** —
|
|
50
|
+
it is kept under `$CROSSTALK_STATE_DIR` (default `~/.local/state/crosstalk/<transport-id>/`,
|
|
51
|
+
where `<transport-id>` is derived from the origin URL). The repo carries conversation;
|
|
52
|
+
each machine carries its own progress through it.
|
|
63
53
|
|
|
64
54
|
---
|
|
65
55
|
|
|
66
56
|
## Channels
|
|
67
57
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
An optional `CHANNEL.md` at `data/channels/<guid>/CHANNEL.md` declares human-readable metadata for the channel. Agents load it on entry to understand the channel's purpose and context.
|
|
58
|
+
A channel is a UUID v4 directory under `data/channels/`. Any participant may create
|
|
59
|
+
one by creating the directory and committing. An optional `CHANNEL.md` declares
|
|
60
|
+
metadata:
|
|
73
61
|
|
|
74
62
|
```
|
|
75
63
|
---
|
|
76
64
|
name: dogfood-sprint
|
|
77
65
|
created_by: steve
|
|
78
|
-
created_at: 2026-
|
|
66
|
+
created_at: 2026-06-09T17:00:00.000Z
|
|
67
|
+
parent: <uuid> # optional — makes this a subchannel
|
|
79
68
|
---
|
|
80
69
|
|
|
81
|
-
|
|
82
|
-
Monte Carlo pi experiments, and multi-agent protocol testing.
|
|
83
|
-
|
|
84
|
-
Participants: steve, concierge, architect, qa, security, scrum-master.
|
|
70
|
+
Free-form description. Serves as a mini system prompt scoped to the channel.
|
|
85
71
|
```
|
|
86
72
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|---|---|---|
|
|
91
|
-
| `name` | yes | human-readable label; unique within the transport |
|
|
92
|
-
| `created_by` | no | actor name who created the channel |
|
|
93
|
-
| `created_at` | no | ISO 8601 UTC |
|
|
94
|
-
| `parent` | no | GUID of the parent channel — see Subchannels |
|
|
73
|
+
`name` is required and unique within the transport; the other fields are optional.
|
|
74
|
+
A subchannel is just a channel with a `parent:`; it reports back by posting to the
|
|
75
|
+
parent channel. There is no close signal — a finished channel simply goes quiet.
|
|
95
76
|
|
|
96
|
-
|
|
77
|
+
---
|
|
97
78
|
|
|
98
|
-
|
|
79
|
+
## Messages
|
|
99
80
|
|
|
100
|
-
|
|
81
|
+
Every message is a markdown file with YAML frontmatter:
|
|
101
82
|
|
|
102
83
|
```
|
|
103
84
|
---
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
85
|
+
from: alice
|
|
86
|
+
to: concierge
|
|
87
|
+
type: text
|
|
88
|
+
timestamp: 2026-06-09T19:00:00.000Z
|
|
108
89
|
---
|
|
109
90
|
|
|
110
|
-
|
|
111
|
-
each throw 1B darts and post results here.
|
|
91
|
+
Message body here.
|
|
112
92
|
```
|
|
113
93
|
|
|
114
|
-
|
|
94
|
+
### Frontmatter fields
|
|
115
95
|
|
|
116
|
-
|
|
96
|
+
| Field | Required | Notes |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `from` | yes | logical actor name — unverified; trust boundary is repo access |
|
|
99
|
+
| `to` | yes | name, list of names, or `all`; may carry `@host` suffix (see Routing) |
|
|
100
|
+
| `type` | yes | always `text` in 6.0 |
|
|
101
|
+
| `timestamp` | yes | ISO 8601 UTC |
|
|
102
|
+
| `re` | no | relPath (or list of relPaths) of the message(s) this one answers — **written by the runtime, never by hand** |
|
|
103
|
+
| `tier` | no | requested model tier for the recipient (see Host files) |
|
|
117
104
|
|
|
118
|
-
|
|
105
|
+
Readers must ignore unknown fields.
|
|
119
106
|
|
|
120
|
-
|
|
107
|
+
### The `re:` field — causality is recorded, not inferred
|
|
121
108
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
109
|
+
A message **without** `re:` is a new task. A message **with** `re:` is a reply to the
|
|
110
|
+
message(s) at the listed relPath(s) (paths relative to the channel directory). Like
|
|
111
|
+
`to:`, the field is a string for one target and a list for several — a reply that
|
|
112
|
+
answers a batch records **every** message it answers, so batching never makes an
|
|
113
|
+
answered message look unanswered.
|
|
125
114
|
|
|
126
|
-
|
|
115
|
+
The runtime sets `re:` from facts it directly observes:
|
|
127
116
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
type: actor
|
|
136
|
-
alias: Apex
|
|
137
|
-
---
|
|
117
|
+
- When an actor answers via stdout, the runtime writes the reply with `re:` listing
|
|
118
|
+
every message in the dispatched batch from that asker.
|
|
119
|
+
- When a dispatched actor uses `crosstalk send`, the runtime injects the triggering
|
|
120
|
+
relPath(s) into the environment and `send` records them automatically. An actor can
|
|
121
|
+
suppress this (`--new`) to start genuinely new work.
|
|
122
|
+
- Messages written by operators (chat tools, hand-authored) carry no `re:` — they
|
|
123
|
+
are new tasks by definition.
|
|
138
124
|
|
|
139
|
-
|
|
140
|
-
|
|
125
|
+
Actors never compute or hand-write `re:`. Because the field is set by the machinery
|
|
126
|
+
that already knows the answer, a confused or dishonest actor cannot mislabel a reply
|
|
127
|
+
as a task or vice versa.
|
|
141
128
|
|
|
142
|
-
|
|
143
|
-
You are Apex. Precise, curious, direct. You think clearly and speak plainly.
|
|
144
|
-
```
|
|
129
|
+
### Filenames
|
|
145
130
|
|
|
146
|
-
|
|
131
|
+
`data/channels/<uuid>/YYYY/MM/DD/HHMMSSmmmZ-<hex>.md` — current UTC time plus a
|
|
132
|
+
hex suffix of at least 8 characters from a CSPRNG. Filenames sort chronologically
|
|
133
|
+
and are collision-free by construction, so concurrent writers on different machines
|
|
134
|
+
never produce git conflicts in message files.
|
|
147
135
|
|
|
148
|
-
|
|
149
|
-
|---|---|---|---|
|
|
150
|
-
| `name` | yes | slug | must match the filename stem |
|
|
151
|
-
| `description` | yes | short string | one-line summary; used by peer agents and operator tooling |
|
|
152
|
-
| `metadata.author` | no | string | who maintains this actor |
|
|
153
|
-
| `metadata.domain` | no | string | e.g. `general`, `engineering`, `health` |
|
|
154
|
-
| `metadata.type` | no | `actor` | always `actor` for participant files |
|
|
155
|
-
| `metadata.alias` | no | string | human-readable name the actor introduces itself by |
|
|
156
|
-
|
|
157
|
-
The body is free-form markdown. It serves as the actor's system prompt — behavioral
|
|
158
|
-
context loaded by the runtime when dispatching to this participant.
|
|
136
|
+
---
|
|
159
137
|
|
|
160
|
-
|
|
138
|
+
## Activation — when does a message wake its addressee?
|
|
161
139
|
|
|
162
|
-
|
|
163
|
-
It is the default participant when no custom actor is configured. Operators override it
|
|
164
|
-
by placing a file with the same `name:` in `local/actors/`.
|
|
140
|
+
One rule:
|
|
165
141
|
|
|
166
|
-
|
|
142
|
+
> **A message wakes its addressee if it has no `re:` (a new task), or any `re:`
|
|
143
|
+
> entry points at a message the addressee sent.**
|
|
167
144
|
|
|
168
|
-
|
|
169
|
-
The worker handles actor management on behalf of the operator via natural language in any channel.
|
|
145
|
+
Consequences:
|
|
170
146
|
|
|
171
|
-
|
|
147
|
+
- Tasks always wake the actor they address.
|
|
148
|
+
- A reply wakes whoever asked the question, and no one else.
|
|
149
|
+
- A reply addressed to someone who never asked (an FYI, a broadcast copy) is visible
|
|
150
|
+
in the channel but does not wake them — fan-in cannot oscillate.
|
|
151
|
+
- Self-sent messages never wake their sender.
|
|
172
152
|
|
|
173
|
-
|
|
153
|
+
There are no other wake conditions and no inference. The dispatcher evaluates this
|
|
154
|
+
rule with two field reads.
|
|
174
155
|
|
|
175
|
-
|
|
156
|
+
---
|
|
176
157
|
|
|
177
|
-
|
|
158
|
+
## Delivery semantics
|
|
178
159
|
|
|
179
|
-
**
|
|
160
|
+
**At-least-once.** Each dispatcher tracks a per-actor, per-channel cursor (local
|
|
161
|
+
state, not in the repo) recording the git commit the channel was last scanned at —
|
|
162
|
+
"new" means *added to git since that commit*, never "later filename timestamp",
|
|
163
|
+
because messages reach origin in push order, not timestamp order. If a machine
|
|
164
|
+
crashes mid-tick, the next tick re-dispatches anything not yet past the cursor;
|
|
165
|
+
a duplicate reply may land in the channel.
|
|
180
166
|
|
|
181
|
-
|
|
167
|
+
For idempotent work (lookups, computation, advice) duplicates are harmless. For
|
|
168
|
+
non-idempotent side effects, the actor must check the channel for evidence of prior
|
|
169
|
+
completion before acting. Crosstalk does not provide exactly-once semantics.
|
|
182
170
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
4. Prompts: *"Which would you like to add? Name one or more — or say 'all'."*
|
|
188
|
-
5. Proceeds to the add flow for each selected actor
|
|
171
|
+
**Batched delivery.** When a dispatcher wakes an actor, it hands over ALL pending
|
|
172
|
+
messages addressed to that actor in that channel in a single invocation. One
|
|
173
|
+
activation drains the mailbox — a coordinator that fanned out to 10 peers wakes
|
|
174
|
+
once and sees all 10 replies together.
|
|
189
175
|
|
|
190
|
-
**
|
|
176
|
+
The transport is an **append-only log**. No retraction, no deletion at the protocol
|
|
177
|
+
level. Retention is the operator's concern at the git/storage layer.
|
|
191
178
|
|
|
192
|
-
|
|
179
|
+
---
|
|
193
180
|
|
|
194
|
-
|
|
195
|
-
1. If a name was given directly, fetches `https://raw.githubusercontent.com/cordfuse/agent-assets/main/actors/{name}.md`
|
|
196
|
-
2. If a role or description was given, matches against the README roster and confirms with the operator before fetching: *"Closest match: [Name] — [description]. Add them? (yes / browse more)"*
|
|
197
|
-
3. Writes to `local/actors/{name}.md`
|
|
198
|
-
4. Commits: `actors: add {name} from agent-assets`
|
|
199
|
-
5. Confirms: *"[Name] added. They're now available as a participant."*
|
|
181
|
+
## Actors
|
|
200
182
|
|
|
201
|
-
|
|
202
|
-
|
|
183
|
+
Each participant has a profile at `local/actors/<name>.md` (operator-owned) or
|
|
184
|
+
`upstream/actors/<name>.md` (defaults; `local/` wins on name collision). The body
|
|
185
|
+
is the actor's system prompt.
|
|
203
186
|
|
|
204
|
-
|
|
187
|
+
```
|
|
188
|
+
---
|
|
189
|
+
name: concierge
|
|
190
|
+
description: "General-purpose worker and coordinator."
|
|
191
|
+
---
|
|
205
192
|
|
|
206
|
-
|
|
193
|
+
## System Prompt
|
|
194
|
+
You are the general-purpose worker in this Crosstalk transport. ...
|
|
195
|
+
```
|
|
207
196
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
2. For each, fetches the latest from `https://raw.githubusercontent.com/cordfuse/agent-assets/main/actors/{name}.md`
|
|
211
|
-
3. Overwrites only cordfuse-authored files — never touches operator-created actors
|
|
212
|
-
4. Commits all updates in a single commit: `actors: sync from agent-assets`
|
|
213
|
-
5. Reports: *"Updated N actors from agent-assets: [list]."* If nothing changed: *"All actors are current."*
|
|
197
|
+
`name` (matching the filename stem) and `description` are required; the rest of the
|
|
198
|
+
frontmatter is free. Actors are added, edited, and removed by committing files.
|
|
214
199
|
|
|
215
200
|
---
|
|
216
201
|
|
|
217
202
|
## Host files
|
|
218
203
|
|
|
219
|
-
A host file at `hosts/<alias>.md` declares
|
|
220
|
-
|
|
221
|
-
### Host file format
|
|
204
|
+
A host file at `hosts/<alias>.md` declares one machine running a dispatcher and the
|
|
205
|
+
actors it serves. Each operator commits and maintains their own.
|
|
222
206
|
|
|
223
207
|
```
|
|
224
208
|
---
|
|
225
209
|
alias: cachy
|
|
226
210
|
hostname: steve-cachyos
|
|
227
211
|
actors:
|
|
212
|
+
concierge:
|
|
213
|
+
claude: claude --print --dangerously-skip-permissions
|
|
228
214
|
junior-developer:
|
|
229
215
|
haiku:
|
|
230
216
|
cli: claude --model claude-haiku-4-5 --print --dangerously-skip-permissions
|
|
231
217
|
count: 5
|
|
232
|
-
flash: gemini --model gemini-2.5-flash -p --yolo
|
|
233
|
-
senior-developer:
|
|
234
|
-
opus: claude --model claude-opus-4-7 --print --dangerously-skip-permissions
|
|
235
218
|
---
|
|
236
219
|
```
|
|
237
220
|
|
|
238
|
-
### Host frontmatter fields
|
|
239
|
-
|
|
240
221
|
| Field | Required | Notes |
|
|
241
222
|
|---|---|---|
|
|
242
|
-
| `alias` | yes |
|
|
243
|
-
| `hostname` | no | OS hostname
|
|
244
|
-
| `
|
|
245
|
-
| `actors` | yes | map of actor names to tier declarations |
|
|
246
|
-
|
|
247
|
-
### Tier declarations
|
|
248
|
-
|
|
249
|
-
Each actor entry maps tier names to CLI commands. A tier is a named model/provider slot:
|
|
223
|
+
| `alias` | yes | the host's name in `actor@host` addressing |
|
|
224
|
+
| `hostname` | no | OS hostname, used for dispatcher auto-detection |
|
|
225
|
+
| `actors` | yes | actor → tier map |
|
|
250
226
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
count: 5 # parallel workers; default: 1
|
|
257
|
-
flash: gemini --model gemini-2.5-flash -p --yolo # shorthand — count defaults to 1
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
- Tier names are operator-defined strings (`haiku`, `flash`, `opus`, etc.). The concierge uses them for routing decisions.
|
|
261
|
-
- The `cli:` string is the shell command the runtime uses to invoke the agent.
|
|
262
|
-
- `count:` sets the number of parallel workers for this tier. Omit or set to `1` for a single worker. The shorthand (bare string) is equivalent to `count: 1`.
|
|
263
|
-
- Multiple tiers of the same actor on the same host form a single instance group for dispatch purposes. The total group size is the sum of all tier counts for that actor.
|
|
264
|
-
|
|
265
|
-
### Actor@host addressing
|
|
266
|
-
|
|
267
|
-
Messages may target a specific host by appending `@<alias>` to the actor name:
|
|
268
|
-
|
|
269
|
-
```yaml
|
|
270
|
-
to: junior-developer@cachy # only cachy's runtime dispatches
|
|
271
|
-
to: senior-developer@mac # only mac's runtime dispatches
|
|
272
|
-
to: concierge # all hosts with a concierge actor dispatch (bare name = broadcast)
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
Bare actor names are backward-compatible — any host with that actor services the message, subject to the instance group selection rule.
|
|
276
|
-
|
|
277
|
-
### Host routing table
|
|
227
|
+
A **tier** is a named CLI slot. The bare-string shorthand means `count: 1`; the
|
|
228
|
+
object form adds `count:` (parallel invocations) per tier. Tier names are
|
|
229
|
+
operator-defined labels (`haiku`, `opus`, `flash`); senders may request one with
|
|
230
|
+
the `tier:` message field, and the dispatcher falls back to the first declared
|
|
231
|
+
tier when the requested one doesn't exist.
|
|
278
232
|
|
|
279
|
-
On
|
|
280
|
-
|
|
281
|
-
### Startup behavior
|
|
282
|
-
|
|
283
|
-
On runtime startup, the runtime scans `hosts/` to find its own host file. Auto-detection precedence:
|
|
284
|
-
|
|
285
|
-
1. **Explicit override:** if `host:` is set in the local config, find the file where `alias:` matches exactly.
|
|
286
|
-
2. **Username + hostname:** if `username:` and `hostname:` both match the machine's `os.userInfo().username` and `os.hostname()`. Use this for multi-user hosts where multiple operators share a machine.
|
|
287
|
-
3. **Bare hostname:** if only `hostname:` matches. Backward-compatible fallback for single-user machines.
|
|
288
|
-
4. **No match:** log clearly, idle without dispatching, and do not crash. The log message must include the identity attempted and the path scanned so the operator can diagnose the mismatch.
|
|
289
|
-
|
|
290
|
-
A user can always override their alias by setting `host: <alias>` in their local config — this bypasses all auto-detection.
|
|
291
|
-
|
|
292
|
-
**Tier validation:** if a message is addressed `to: actor@host` and this runtime is the target host but has no matching actor declared, log a warning and skip. Do not crash.
|
|
293
|
-
|
|
294
|
-
### Host file ownership
|
|
295
|
-
|
|
296
|
-
Each operator commits only their own host file. The protocol does not enforce this — it is a convention. Trust at the repository level: any participant with write access can modify any host file. Operators who need stronger guarantees must secure the repository itself.
|
|
233
|
+
On startup a dispatcher finds its own host file by matching `hostname:` (or via an
|
|
234
|
+
explicit `--host <alias>` override). No match → log clearly and idle; never crash.
|
|
297
235
|
|
|
298
236
|
---
|
|
299
237
|
|
|
300
|
-
##
|
|
301
|
-
|
|
302
|
-
Your `name` and `email` are declared in `local/CROSSTALK.md`, which uses this format:
|
|
303
|
-
|
|
304
|
-
```
|
|
305
|
-
---
|
|
306
|
-
name: concierge
|
|
307
|
-
email: concierge@crosstalk.local
|
|
308
|
-
---
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
Set the matching git identity once by running these commands inside the transport repo:
|
|
312
|
-
|
|
313
|
-
```
|
|
314
|
-
git config user.name "concierge"
|
|
315
|
-
git config user.email "concierge@crosstalk.local"
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
Do not use `--global` — this must be repo-local so it does not overwrite the
|
|
319
|
-
operator's own git identity. Names are free-form: `alice`, `concierge`, `ops-bot`.
|
|
320
|
-
No taxonomy, no hierarchy. Names need not be unique — see Instance groups.
|
|
321
|
-
|
|
322
|
-
### Instance groups
|
|
323
|
-
|
|
324
|
-
A name may be shared by multiple participants. They form an **instance group**: addressable by the shared name, but only one instance dispatches per message.
|
|
325
|
-
|
|
326
|
-
When a message is addressed to a shared name, every instance reads it. To avoid duplicate dispatch, each instance independently applies the same deterministic selection rule:
|
|
327
|
-
|
|
328
|
-
```
|
|
329
|
-
index = sha256(message-relPath) read as 32-bit big-endian unsigned, mod group-size
|
|
330
|
-
chosen = instance at position `index` (0-based, in the group's local ordering)
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
The instance whose own position equals `index` dispatches; the rest skip and advance their cursor. Only the chosen instance posts a reply and receipt.
|
|
334
|
-
|
|
335
|
-
**The selection function is normative** — all runtimes must use this exact computation so that instances reach the same decision without coordination.
|
|
336
|
-
|
|
337
|
-
**Local ordering is runtime-defined.** Single-operator scope: the runtime orders instances by config position (or any other stable local ordering) and is consistent within its own process. Multi-operator scope (instances spread across operators sharing one transport): not yet specified — runtimes that span multiple operators may double-dispatch until a roster-discovery mechanism is added.
|
|
338
|
-
|
|
339
|
-
Single-instance semantics are recovered by giving each participant a unique name. Operators who don't want instance groups simply don't share names.
|
|
340
|
-
|
|
341
|
-
### Identity model
|
|
342
|
-
|
|
343
|
-
`from:` identifies the logical actor. The git commit author identifies the session
|
|
344
|
-
that wrote the file.
|
|
345
|
-
|
|
346
|
-
`from:` and the git commit author are both unverified strings. Identity is not enforced at the protocol level — the trust boundary is repo access. Operators who need verified identity must secure the repository itself.
|
|
238
|
+
## Routing
|
|
347
239
|
|
|
348
|
-
|
|
240
|
+
The `to:` field accepts:
|
|
349
241
|
|
|
350
|
-
|
|
351
|
-
|
|
242
|
+
- `to: concierge` — bare name. Every host that declares the actor dispatches it.
|
|
243
|
+
- `to: junior-developer@cachy` — narrowed to the host whose `alias` is `cachy`;
|
|
244
|
+
other hosts skip it (and log the skip, so wrong-host routes are visible).
|
|
245
|
+
- `to: [a, b@mac]` — lists mix freely.
|
|
246
|
+
- `to: all` — every participant.
|
|
352
247
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
- The git commit author is the worker's git identity
|
|
248
|
+
The actor name is everything before the `@`; the host alias is everything after.
|
|
249
|
+
The `re:` activation rule ignores host suffixes — only addressing honors them.
|
|
356
250
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
Sub-agents do not require actor files. An `actors/<name>.md` may exist to document the sub-agent, but it is not required for the pattern to work.
|
|
361
|
-
|
|
362
|
-
```
|
|
363
|
-
---
|
|
364
|
-
from: architect
|
|
365
|
-
via: concierge
|
|
366
|
-
to: steve
|
|
367
|
-
type: text
|
|
368
|
-
timestamp: 2026-05-24T14:00:00.000Z
|
|
369
|
-
---
|
|
370
|
-
|
|
371
|
-
Sub-agent response here.
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
### Peer agents
|
|
375
|
-
|
|
376
|
-
Each participant runs as its own independent process with its own git clone, git identity, and scheduler. No `via:` field — every message is committed directly by the actor that wrote it. Push conflicts are resolved by exponential backoff (see On session open, step 6).
|
|
377
|
-
|
|
378
|
-
**Sub-agents vs peer agents:**
|
|
379
|
-
|
|
380
|
-
| | Sub-agents | Peer agents |
|
|
381
|
-
|---|---|---|
|
|
382
|
-
| Parallelism | Yes — worker fans out concurrently | Yes — independent processes |
|
|
383
|
-
| Model diversity | Yes — worker picks model per invocation | Yes — set at deployment per process |
|
|
384
|
-
| Git authorship | Single committer (the worker) | One committer per actor |
|
|
385
|
-
| Failure isolation | Worker is a single point of failure | Agents fail independently |
|
|
386
|
-
| Deployment | One scheduler, one clone | One scheduler and clone per agent |
|
|
387
|
-
| Push conflicts | None — single writer | Possible — resolved by backoff |
|
|
388
|
-
|
|
389
|
-
Use sub-agents when a single scheduled job should drive all actor activity and git authorship per actor is not required. Use peer agents when agents are deployed across different machines or providers, or when independent failure domains and per-actor git authorship matter.
|
|
251
|
+
Use bare names for work-pool patterns where any machine will do; use `@host` when
|
|
252
|
+
the orchestration depends on which machine runs the work. This addressing is the
|
|
253
|
+
entirety of Crosstalk's multi-host model.
|
|
390
254
|
|
|
391
255
|
---
|
|
392
256
|
|
|
393
|
-
##
|
|
257
|
+
## Identity and trust
|
|
394
258
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
from: alice
|
|
400
|
-
to: concierge
|
|
401
|
-
type: text
|
|
402
|
-
timestamp: 2026-05-23T19:00:00.000Z
|
|
403
|
-
---
|
|
404
|
-
|
|
405
|
-
Message body here.
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
Read message files directly with your agent's native file-reading capability — the format is plain markdown with YAML frontmatter, intended to be inspected as text. No parser, interpreter, or shelling out for `cat`/`sed`/`awk` is required.
|
|
409
|
-
|
|
410
|
-
### Frontmatter fields
|
|
411
|
-
|
|
412
|
-
| Field | Required | Values | Notes |
|
|
413
|
-
|---|---|---|---|
|
|
414
|
-
| `from` | yes | free-form name | logical actor identity — see Identity model |
|
|
415
|
-
| `to` | yes | name, list of names, or `all` | `all` targets every participant |
|
|
416
|
-
| `type` | yes | `text`, `read` | see Message types |
|
|
417
|
-
| `timestamp` | yes | ISO 8601 UTC | |
|
|
418
|
-
| `ref` | conditional | relPath of original message | required when `type: read`; path relative to `data/channels/<guid>/` |
|
|
419
|
-
| `via` | no | free-form name | session that committed on behalf of `from` — see Sub-agents |
|
|
420
|
-
|
|
421
|
-
Operators may add fields — readers must ignore unknown fields.
|
|
422
|
-
|
|
423
|
-
### To field
|
|
424
|
-
|
|
425
|
-
- Single recipient: `to: concierge`
|
|
426
|
-
- Multiple recipients: `to: [concierge, ops-bot]`
|
|
427
|
-
- Broadcast: `to: all`
|
|
428
|
-
- Host-targeted: `to: junior-developer@cachy` — only the named host dispatches; see Host files
|
|
429
|
-
- Senders do not receive their own messages. Self-exclusion applies at scan time — agents skip any message where `from:` matches their own name.
|
|
430
|
-
- A name may be shared by multiple participants — see Instance groups.
|
|
431
|
-
|
|
432
|
-
### Message types
|
|
433
|
-
|
|
434
|
-
**`type: text`** — a normal message. Body is the message content.
|
|
435
|
-
|
|
436
|
-
**`type: read`** — a read receipt. The `ref` field contains the relPath of the
|
|
437
|
-
acknowledged message. Body is empty or omitted.
|
|
438
|
-
|
|
439
|
-
```
|
|
440
|
-
---
|
|
441
|
-
from: concierge
|
|
442
|
-
to: alice
|
|
443
|
-
type: read
|
|
444
|
-
timestamp: 2026-05-23T19:02:00.000Z
|
|
445
|
-
ref: 2026/05/23/190100000Z-b2c3d4e5.md
|
|
446
|
-
---
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
Read receipts are optional. Senders cannot require them. Address the receipt to the
|
|
450
|
-
original sender (`from:` of the original message) regardless of whether the original
|
|
451
|
-
was targeted, listed, or broadcast. Never address a receipt to `all`.
|
|
452
|
-
|
|
453
|
-
Each addressed recipient in a list-targeted message sends its own independent receipt to the original sender.
|
|
454
|
-
|
|
455
|
-
Never post a receipt for a `type: read` message.
|
|
456
|
-
|
|
457
|
-
A receipt with a `ref:` pointing to a nonexistent message path is treated as malformed — skip silently.
|
|
458
|
-
|
|
459
|
-
---
|
|
460
|
-
|
|
461
|
-
## Privacy and trust model
|
|
462
|
-
|
|
463
|
-
`to` is a routing hint, not an access control boundary. Every message is visible to anyone with repo access. There are no whisper or ephemeral message types.
|
|
464
|
-
|
|
465
|
-
`from:` is an unverified declaration — the protocol does not enforce identity. The trust boundary is repo access: anyone who can push to the repository can write any `from:` value. Operators who require confidentiality or verified identity must secure the repository itself.
|
|
466
|
-
|
|
467
|
-
---
|
|
468
|
-
|
|
469
|
-
## Append-only log
|
|
470
|
-
|
|
471
|
-
The transport is an immutable, append-only log. There is no `type: retract` or channel-close message. Deletion is out of scope — operators who need retention limits must manage that at the git or storage layer.
|
|
472
|
-
|
|
473
|
-
---
|
|
474
|
-
|
|
475
|
-
## Sessions
|
|
476
|
-
|
|
477
|
-
At startup, generate a random GUID. This is your session ID for this invocation.
|
|
478
|
-
Use it when writing memory files. Sessions are self-issued — no registry, no daemon.
|
|
259
|
+
`from:` is an unverified string. The trust boundary is repository access: anyone who
|
|
260
|
+
can push can claim any name. `to:` is a routing hint, not access control — every
|
|
261
|
+
message is visible to anyone with repo access. Operators who need confidentiality
|
|
262
|
+
or verified identity must secure the repository itself.
|
|
479
263
|
|
|
480
264
|
---
|
|
481
265
|
|
|
482
266
|
## Memories
|
|
483
267
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
### Memory file format
|
|
487
|
-
|
|
488
|
-
Filename: `YYYYMMDDTHHMMSSsssZ-<hex>.md` — UTC millisecond timestamp + hex suffix
|
|
489
|
-
for collision resistance. Sorts chronologically. The hex suffix must be at least
|
|
490
|
-
8 characters, generated from a CSPRNG.
|
|
268
|
+
Shared persistent notes any participant may read or write, at
|
|
269
|
+
`data/memories/YYYYMMDDTHHMMSSmmmZ-<hex>.md`:
|
|
491
270
|
|
|
492
271
|
```
|
|
493
272
|
---
|
|
494
273
|
from: concierge
|
|
495
|
-
timestamp: 2026-
|
|
274
|
+
timestamp: 2026-06-09T19:00:00.000Z
|
|
496
275
|
subject: Steve prefers TypeScript for all new tooling
|
|
497
|
-
scope: global
|
|
498
|
-
|
|
499
|
-
session: 7f3a2b1c-9e4d-4f8a-b6c2-1d5e8f0a3c7b
|
|
276
|
+
scope: global # or a channel uuid
|
|
277
|
+
supersedes: <filename> # optional — replaces an earlier memory
|
|
500
278
|
---
|
|
501
279
|
|
|
502
|
-
|
|
280
|
+
Body.
|
|
503
281
|
```
|
|
504
282
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
| Field | Required | Values | Notes |
|
|
508
|
-
|---|---|---|---|
|
|
509
|
-
| `from` | yes | free-form name | logical actor identity |
|
|
510
|
-
| `via` | no | free-form name | session that committed on behalf of `from` — see Sub-agents |
|
|
511
|
-
| `timestamp` | yes | ISO 8601 UTC | |
|
|
512
|
-
| `subject` | yes | short string | descriptive label; used for filtering, not update detection |
|
|
513
|
-
| `scope` | no | `global` or channel GUID | `global` if omitted |
|
|
514
|
-
| `tags` | no | list of strings | free-form filtering |
|
|
515
|
-
| `session` | no | session GUID | ties memory to the writing session |
|
|
516
|
-
| `supersedes` | no | filename of original memory | declares this memory replaces an earlier one — see Updating memories |
|
|
517
|
-
|
|
518
|
-
Memory files are agent-agnostic — any participant may read or write them. Do not
|
|
519
|
-
use your model's native memory system; `data/memories/` is the memory system.
|
|
520
|
-
|
|
521
|
-
### Updating memories
|
|
522
|
-
|
|
523
|
-
To update a memory, write a new memory file with `supersedes: <filename-of-original>`. When loading memories, collect all filenames referenced by any `supersedes` field. Skip any memory whose filename appears in that set — it has been superseded. If two memories both supersede the same file, the newer timestamp wins. For same-timestamp ties, lexicographic filename order is the tiebreaker.
|
|
524
|
-
|
|
525
|
-
`subject` is descriptive only — it plays no role in update detection.
|
|
526
|
-
|
|
527
|
-
The `scope` field must be either `global` or a channel GUID from `data/channels/`. Session GUIDs are not valid scope values.
|
|
528
|
-
|
|
529
|
-
---
|
|
530
|
-
|
|
531
|
-
## On session open
|
|
532
|
-
|
|
533
|
-
1. Read `local/CROSSTALK.md`. If absent, halt and notify the operator. If `name` or `email` is still `CONFIGURE`, halt and notify the operator. Do not load memories, process messages, or write to `data/`. (Writing to `local/` to establish identity — e.g. via an operator-driven onboarding flow — is permitted, since identity establishment is the only way out of this state.)
|
|
534
|
-
2. Read `upstream/CROSSTALK-VERSION`. If missing or not exactly `5.0`, halt and notify the operator. Do not load memories, process messages, or write to `data/`.
|
|
535
|
-
2a. Read `hosts/` if present. Build a routing table of available actors per host. If the directory is absent or empty, routing table is empty — proceed normally.
|
|
536
|
-
3. Run `git pull` to sync the transport.
|
|
537
|
-
4. Load global memories from `data/memories/` where `scope` is `global` or absent. Filter by `subject` and `tags`.
|
|
538
|
-
5. For each channel in `data/channels/`:
|
|
539
|
-
a. Load memories scoped to this channel GUID.
|
|
540
|
-
b. Scan for unread messages addressed to you (see Reading messages).
|
|
541
|
-
c. Process and reply. Only act on `type: text` messages — `type: read` messages require no reply and no further action.
|
|
542
|
-
d. Channel processing order across multiple channels is implementation-defined.
|
|
543
|
-
6. Push immediately. If rejected, read backoff config from `local/JITTER.md` (falls back to `upstream/JITTER.md`). If neither file exists, use defaults: base=100ms, ceiling=5000ms. Run `git pull --rebase`, wait `random(0, min(ceiling, base * 2^attempt))`, then retry. After rebasing, push what was already composed — do not re-scan. Message filenames are unique — rebase conflicts should not occur.
|
|
283
|
+
When loading, skip any memory named in another memory's `supersedes:`. Agents use
|
|
284
|
+
`data/memories/` instead of their model-native memory systems.
|
|
544
285
|
|
|
545
286
|
---
|
|
546
287
|
|
|
547
|
-
##
|
|
548
|
-
|
|
549
|
-
For each message file in `data/channels/<guid>/`, parsed chronologically:
|
|
550
|
-
|
|
551
|
-
1. Check `to:` — process if your name appears (bare or as `name@your-host-alias`) or `to` is `all`; otherwise skip. If the target includes a `@host` suffix that does not match your host alias, skip. If your name is shared with other participants (an instance group), apply the deterministic selection rule and skip unless you are the chosen member — see Instance groups.
|
|
552
|
-
2. Check for an existing `type: read` receipt from you with a matching `ref:`. If found, skip — already processed.
|
|
553
|
-
3. If unread: process it, post a reply, then post a `type: read` receipt.
|
|
554
|
-
|
|
555
|
-
Your `type: read` receipts are your cursor. No external state needed. On first run, all messages are unread — process them all.
|
|
556
|
-
|
|
557
|
-
Each addressed agent receipts independently. A receipt from another agent does not mark a message as read for you.
|
|
558
|
-
|
|
559
|
-
Actions must be idempotent. If a push fails after processing but before the receipt is committed, the message will be reprocessed on the next run.
|
|
560
|
-
|
|
561
|
-
---
|
|
562
|
-
|
|
563
|
-
## Writing messages
|
|
564
|
-
|
|
565
|
-
Create `data/channels/<guid>/YYYY/MM/DD/HHMMSSsssZ-<hex>.md` with the current UTC time.
|
|
566
|
-
The hex suffix must be at least 8 characters, generated from a CSPRNG.
|
|
567
|
-
Include required frontmatter: `from`, `to`, `type`, `timestamp`. Commit and push
|
|
568
|
-
under your configured git identity.
|
|
569
|
-
|
|
570
|
-
Write all replies for the session before pushing. Push once per session, not once per message.
|
|
571
|
-
|
|
572
|
-
---
|
|
573
|
-
|
|
574
|
-
## What to ignore
|
|
575
|
-
|
|
576
|
-
### Skip silently
|
|
577
|
-
|
|
578
|
-
- Messages not addressed to you and not `to: all`.
|
|
579
|
-
- Messages you have already acknowledged with a `type: read` receipt.
|
|
580
|
-
- `type: read` messages — never post a receipt for one.
|
|
581
|
-
- Files outside `data/channels/` and `data/memories/`.
|
|
582
|
-
- Unknown frontmatter fields.
|
|
583
|
-
- Your model's native memory system.
|
|
288
|
+
## Coordination
|
|
584
289
|
|
|
585
|
-
|
|
290
|
+
Git is self-coordinating: filenames are collision-free and non-fast-forward pushes
|
|
291
|
+
are rejected and retried with `git pull --rebase`. A transport therefore works with
|
|
292
|
+
no coordinator at all.
|
|
586
293
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
294
|
+
Dispatchers MAY use a turn coordinator (cordfuse/turnq) to reduce push contention —
|
|
295
|
+
locally via file lock, or across hosts via a shared turnq server. Coordination is
|
|
296
|
+
**advisory**: a dispatcher waits for its turn with a bounded timeout and proceeds
|
|
297
|
+
anyway on timeout or coordinator failure, letting git arbitrate. A coordinator
|
|
298
|
+
outage may cost push retries; it can never stall message processing.
|