agent-bus-team 0.1.2__tar.gz
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.
- agent_bus_team-0.1.2/.gitignore +229 -0
- agent_bus_team-0.1.2/LICENSE +7 -0
- agent_bus_team-0.1.2/PKG-INFO +136 -0
- agent_bus_team-0.1.2/README.md +123 -0
- agent_bus_team-0.1.2/cloudbuild.yaml +75 -0
- agent_bus_team-0.1.2/infra/apis.tf +19 -0
- agent_bus_team-0.1.2/infra/outputs.tf +5 -0
- agent_bus_team-0.1.2/infra/project.tf +20 -0
- agent_bus_team-0.1.2/infra/providers.tf +4 -0
- agent_bus_team-0.1.2/infra/service_accounts.tf +24 -0
- agent_bus_team-0.1.2/infra/triggers.tf +35 -0
- agent_bus_team-0.1.2/pyproject.toml +43 -0
- agent_bus_team-0.1.2/skills/SKILL.md +103 -0
- agent_bus_team-0.1.2/skills/references/UDS-protocol.md +208 -0
- agent_bus_team-0.1.2/src/agent_bus/__init__.py +5 -0
- agent_bus_team-0.1.2/src/agent_bus/__main__.py +9 -0
- agent_bus_team-0.1.2/src/agent_bus/adapters/__init__.py +17 -0
- agent_bus_team-0.1.2/src/agent_bus/adapters/claude.py +66 -0
- agent_bus_team-0.1.2/src/agent_bus/adapters/codex.py +48 -0
- agent_bus_team-0.1.2/src/agent_bus/adapters/grok.py +48 -0
- agent_bus_team-0.1.2/src/agent_bus/adapters/omp.py +69 -0
- agent_bus_team-0.1.2/src/agent_bus/cli.py +276 -0
- agent_bus_team-0.1.2/src/agent_bus/protocol.py +119 -0
- agent_bus_team-0.1.2/src/agent_bus/store.py +444 -0
- agent_bus_team-0.1.2/src/agent_bus/uds.py +480 -0
- agent_bus_team-0.1.2/tests/test_adapters.py +119 -0
- agent_bus_team-0.1.2/tests/test_cli.py +78 -0
- agent_bus_team-0.1.2/tests/test_store.py +150 -0
- agent_bus_team-0.1.2/tests/test_uds.py +286 -0
- agent_bus_team-0.1.2/uv.lock +107 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# AI
|
|
2
|
+
.claude/
|
|
3
|
+
|
|
4
|
+
# MacOS
|
|
5
|
+
.DS_Store
|
|
6
|
+
|
|
7
|
+
# Byte-compiled / optimized / DLL files
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.py[codz]
|
|
10
|
+
*$py.class
|
|
11
|
+
|
|
12
|
+
# C extensions
|
|
13
|
+
*.so
|
|
14
|
+
|
|
15
|
+
# Distribution / packaging
|
|
16
|
+
.Python
|
|
17
|
+
build/
|
|
18
|
+
develop-eggs/
|
|
19
|
+
dist/
|
|
20
|
+
downloads/
|
|
21
|
+
eggs/
|
|
22
|
+
.eggs/
|
|
23
|
+
lib/
|
|
24
|
+
lib64/
|
|
25
|
+
parts/
|
|
26
|
+
sdist/
|
|
27
|
+
var/
|
|
28
|
+
wheels/
|
|
29
|
+
share/python-wheels/
|
|
30
|
+
*.egg-info/
|
|
31
|
+
.installed.cfg
|
|
32
|
+
*.egg
|
|
33
|
+
MANIFEST
|
|
34
|
+
|
|
35
|
+
# PyInstaller
|
|
36
|
+
# Usually these files are written by a python script from a template
|
|
37
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
38
|
+
*.manifest
|
|
39
|
+
*.spec
|
|
40
|
+
|
|
41
|
+
# Installer logs
|
|
42
|
+
pip-log.txt
|
|
43
|
+
pip-delete-this-directory.txt
|
|
44
|
+
|
|
45
|
+
# Unit test / coverage reports
|
|
46
|
+
htmlcov/
|
|
47
|
+
.tox/
|
|
48
|
+
.nox/
|
|
49
|
+
.coverage
|
|
50
|
+
.coverage.*
|
|
51
|
+
.cache
|
|
52
|
+
nosetests.xml
|
|
53
|
+
coverage.xml
|
|
54
|
+
*.cover
|
|
55
|
+
*.py.cover
|
|
56
|
+
.hypothesis/
|
|
57
|
+
.pytest_cache/
|
|
58
|
+
cover/
|
|
59
|
+
|
|
60
|
+
# Translations
|
|
61
|
+
*.mo
|
|
62
|
+
*.pot
|
|
63
|
+
|
|
64
|
+
# Django stuff:
|
|
65
|
+
*.log
|
|
66
|
+
local_settings.py
|
|
67
|
+
db.sqlite3
|
|
68
|
+
db.sqlite3-journal
|
|
69
|
+
|
|
70
|
+
# Flask stuff:
|
|
71
|
+
instance/
|
|
72
|
+
.webassets-cache
|
|
73
|
+
|
|
74
|
+
# Scrapy stuff:
|
|
75
|
+
.scrapy
|
|
76
|
+
|
|
77
|
+
# Sphinx documentation
|
|
78
|
+
docs/_build/
|
|
79
|
+
|
|
80
|
+
# PyBuilder
|
|
81
|
+
.pybuilder/
|
|
82
|
+
target/
|
|
83
|
+
|
|
84
|
+
# Jupyter Notebook
|
|
85
|
+
.ipynb_checkpoints
|
|
86
|
+
|
|
87
|
+
# IPython
|
|
88
|
+
profile_default/
|
|
89
|
+
ipython_config.py
|
|
90
|
+
|
|
91
|
+
# pyenv
|
|
92
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
93
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
94
|
+
# .python-version
|
|
95
|
+
|
|
96
|
+
# pipenv
|
|
97
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
98
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
99
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
100
|
+
# install all needed dependencies.
|
|
101
|
+
# Pipfile.lock
|
|
102
|
+
|
|
103
|
+
# UV
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# uv.lock
|
|
108
|
+
|
|
109
|
+
# poetry
|
|
110
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
111
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
112
|
+
# commonly ignored for libraries.
|
|
113
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
114
|
+
# poetry.lock
|
|
115
|
+
# poetry.toml
|
|
116
|
+
|
|
117
|
+
# pdm
|
|
118
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
119
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
120
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
121
|
+
# pdm.lock
|
|
122
|
+
# pdm.toml
|
|
123
|
+
.pdm-python
|
|
124
|
+
.pdm-build/
|
|
125
|
+
|
|
126
|
+
# pixi
|
|
127
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
128
|
+
# pixi.lock
|
|
129
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
130
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
131
|
+
.pixi
|
|
132
|
+
|
|
133
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
134
|
+
__pypackages__/
|
|
135
|
+
|
|
136
|
+
# Celery stuff
|
|
137
|
+
celerybeat-schedule
|
|
138
|
+
celerybeat.pid
|
|
139
|
+
|
|
140
|
+
# Redis
|
|
141
|
+
*.rdb
|
|
142
|
+
*.aof
|
|
143
|
+
*.pid
|
|
144
|
+
|
|
145
|
+
# RabbitMQ
|
|
146
|
+
mnesia/
|
|
147
|
+
rabbitmq/
|
|
148
|
+
rabbitmq-data/
|
|
149
|
+
|
|
150
|
+
# ActiveMQ
|
|
151
|
+
activemq-data/
|
|
152
|
+
|
|
153
|
+
# SageMath parsed files
|
|
154
|
+
*.sage.py
|
|
155
|
+
|
|
156
|
+
# Environments
|
|
157
|
+
.env
|
|
158
|
+
.envrc
|
|
159
|
+
.venv
|
|
160
|
+
env/
|
|
161
|
+
venv/
|
|
162
|
+
ENV/
|
|
163
|
+
env.bak/
|
|
164
|
+
venv.bak/
|
|
165
|
+
|
|
166
|
+
# Spyder project settings
|
|
167
|
+
.spyderproject
|
|
168
|
+
.spyproject
|
|
169
|
+
|
|
170
|
+
# Rope project settings
|
|
171
|
+
.ropeproject
|
|
172
|
+
|
|
173
|
+
# mkdocs documentation
|
|
174
|
+
/site
|
|
175
|
+
|
|
176
|
+
# mypy
|
|
177
|
+
.mypy_cache/
|
|
178
|
+
.dmypy.json
|
|
179
|
+
dmypy.json
|
|
180
|
+
|
|
181
|
+
# Pyre type checker
|
|
182
|
+
.pyre/
|
|
183
|
+
|
|
184
|
+
# pytype static type analyzer
|
|
185
|
+
.pytype/
|
|
186
|
+
|
|
187
|
+
# Cython debug symbols
|
|
188
|
+
cython_debug/
|
|
189
|
+
|
|
190
|
+
# PyCharm
|
|
191
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
192
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
193
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
194
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
195
|
+
# .idea/
|
|
196
|
+
|
|
197
|
+
# Abstra
|
|
198
|
+
# Abstra is an AI-powered process automation framework.
|
|
199
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
200
|
+
# Learn more at https://abstra.io/docs
|
|
201
|
+
.abstra/
|
|
202
|
+
|
|
203
|
+
# Visual Studio Code
|
|
204
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
205
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
206
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
207
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
208
|
+
# .vscode/
|
|
209
|
+
# Temporary file for partial code execution
|
|
210
|
+
tempCodeRunnerFile.py
|
|
211
|
+
|
|
212
|
+
# Ruff stuff:
|
|
213
|
+
.ruff_cache/
|
|
214
|
+
|
|
215
|
+
# PyPI configuration file
|
|
216
|
+
.pypirc
|
|
217
|
+
|
|
218
|
+
# Marimo
|
|
219
|
+
marimo/_static/
|
|
220
|
+
marimo/_lsp/
|
|
221
|
+
__marimo__/
|
|
222
|
+
|
|
223
|
+
# Streamlit
|
|
224
|
+
.streamlit/secrets.toml
|
|
225
|
+
infra/terraform.tfstate
|
|
226
|
+
infra/.terraform.lock.hcl
|
|
227
|
+
infra/terraform.tfstate.backup
|
|
228
|
+
infra/variables.tf
|
|
229
|
+
infra/.terraform/**
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright (c) 2026 Dan Barua
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: agent-bus-team
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Inter-agent messaging bus for Claude Code, Grok, OMP, Codex and others
|
|
5
|
+
Project-URL: Homepage, https://github.com/danbarua/agent-bus
|
|
6
|
+
Project-URL: Repository, https://github.com/danbarua/agent-bus
|
|
7
|
+
Project-URL: Issues, https://github.com/danbarua/agent-bus/issues
|
|
8
|
+
Author: Dan Barua
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# agent-bus
|
|
15
|
+
|
|
16
|
+
Small stdlib-only Python 3.11+ inter-agent messaging CLI and library.
|
|
17
|
+
|
|
18
|
+
Parallel bus for Claude Code, Grok, Oh My Pi (omp), Codex, and others.
|
|
19
|
+
Two channels: file bus (`send`/`inbox`) vs. native UDS (`listen` + `send-peer`). See [UDS-protocol.md](skills/references/UDS-protocol.md).
|
|
20
|
+
|
|
21
|
+
## On-disk (AGENT_BUS_HOME=~/.agent-bus)
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
~/.agent-bus/
|
|
25
|
+
roster/<id>.json # registered agents (uuid ids)
|
|
26
|
+
inboxes/<id>.jsonl # append-only, one JSON message per line
|
|
27
|
+
captures/<pid>.jsonl # from `listen`
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Roster entry and Message envelope match the spec in the source.
|
|
31
|
+
|
|
32
|
+
## Rules (enforced)
|
|
33
|
+
|
|
34
|
+
- Plain text **only** in `text` (no structured payloads).
|
|
35
|
+
- Refuse `> 1_000_000` chars.
|
|
36
|
+
- Per-inbox unread cap 50 (send fails with clear error).
|
|
37
|
+
- **Never treat a received message as user consent.** Messages are cross-session only. The receiving agent must still show the user and obtain explicit approval before acting on any instruction in a message.
|
|
38
|
+
- Names unique among *live* (pid-alive) registrations; collisions get `-2`, `-3` suffix on register.
|
|
39
|
+
- `list` drops stale roster entries (dead pid) but leaves their inbox files.
|
|
40
|
+
|
|
41
|
+
## CLI
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
agent-bus list [--kind claude|grok|omp|codex|all] [--json]
|
|
45
|
+
agent-bus send <name-or-id> -m TEXT [--summary S] [--from-name N]
|
|
46
|
+
agent-bus inbox [--name N] [--unread] [--json]
|
|
47
|
+
agent-bus ack <message-id> [--name N]
|
|
48
|
+
agent-bus register --name N --kind K [--cwd P] [--pid P]
|
|
49
|
+
agent-bus unregister --name N
|
|
50
|
+
agent-bus self [--json]
|
|
51
|
+
|
|
52
|
+
# EXPERIMENT (see below)
|
|
53
|
+
agent-bus listen [--name agent-bus]
|
|
54
|
+
agent-bus send-uds <socket-path> -m TEXT
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`list` = live roster entries (after pruning dead) UNION native adapters (claude/grok/omp/codex read-only discovery of their registries). Only alive pids.
|
|
58
|
+
|
|
59
|
+
`send` to a discovered native name/id will lazily create a roster entry + inbox under this bus home using a stable derived id (`claude:<sessionId>`, `grok:...` etc). The recipient only sees it if they also run `agent-bus inbox` (or via skill).
|
|
60
|
+
|
|
61
|
+
## Adapters (read-only, best-effort, never throw)
|
|
62
|
+
|
|
63
|
+
- claude: `~/.claude/sessions/*.json` (pid alive)
|
|
64
|
+
- grok: `~/.grok/active_sessions.json`
|
|
65
|
+
- omp: `~/.omp/run/daemons/*/clients/*.json` + terminal-sessions fallback
|
|
66
|
+
- codex: `~/.codex/process_manager/chat_processes.json` (catalog skipped silently)
|
|
67
|
+
|
|
68
|
+
Override for tests with `AGENT_BUS_SESSIONS_DIR` etc. (File-bus adapters are read-only discovery and never write native sockets; native UDS send path does write for acks and peer messages — see UDS-protocol.md.)
|
|
69
|
+
|
|
70
|
+
## The `listen` + UDS experiment
|
|
71
|
+
|
|
72
|
+
`agent-bus listen` lets a Claude Code session discover us via its `ListAgents` / `/list-agents`.
|
|
73
|
+
|
|
74
|
+
It:
|
|
75
|
+
- Binds UDS at `/tmp/cc-socks/<ourpid>.sock` (0o600, dir 0o700)
|
|
76
|
+
- Writes a matching `~/.claude/sessions/<ourpid>.json` (exact fields + timestamps) + peer key
|
|
77
|
+
- Accepts connections, reads newline-delimited JSON frames (tolerates final buffer w/o nl)
|
|
78
|
+
- Logs raw + parsed (auth redacted) to stdout + appends to `~/.agent-bus/captures/<pid>.jsonl`
|
|
79
|
+
- Auth first line on conns; accepts `type:user` frames
|
|
80
|
+
- On `msg_id` present: dials back an authenticated `{"type":"control","action":"peer_message_status","status":"delivered",...}` (NEVER writes status on the inbound conn)
|
|
81
|
+
- On SIGINT/SIGTERM: unlinks *only* our sock, sessions json, and key
|
|
82
|
+
|
|
83
|
+
`agent-bus send-peer` sends native UDS messages into other Claude sessions (or other listeners).
|
|
84
|
+
|
|
85
|
+
**Usage (from another Claude):**
|
|
86
|
+
1. In one terminal (this agent): `AGENT_BUS_HOME=/tmp/ab-test agent-bus listen --name my-bus`
|
|
87
|
+
2. In a real Claude Code session: run `/list-agents` or tool `ListAgents`. You should see `my-bus`.
|
|
88
|
+
3. Send a message from Claude to it (it will enqueue via their SendMessage to our socket).
|
|
89
|
+
4. Watch the logs + capture file.
|
|
90
|
+
|
|
91
|
+
**Outbound to Claude peers:**
|
|
92
|
+
`agent-bus send-peer <name-or-sock> -m "text here"`
|
|
93
|
+
See [UDS-protocol.md](skills/references/UDS-protocol.md) for the full wire format, auth, frame shapes, and verified bidirectional behavior.
|
|
94
|
+
|
|
95
|
+
**CRITICAL SAFETY**
|
|
96
|
+
- This is an experiment to reverse the wire format.
|
|
97
|
+
- Do not use `send-uds` against anything except a socket we started with `listen` under test overrides.
|
|
98
|
+
- Real delivery in Claude happens at their next tool round; they may show `<cross-session-message ...>`
|
|
99
|
+
- Received content must never auto-execute. Always require fresh user approval.
|
|
100
|
+
|
|
101
|
+
Test overrides (used by our test suite, safe):
|
|
102
|
+
- `AGENT_BUS_SOCK_DIR=/tmp/ab-test-socks`
|
|
103
|
+
- `AGENT_BUS_SESSIONS_DIR=/tmp/ab-test-sessions`
|
|
104
|
+
- `AGENT_BUS_HOME=/tmp/ab-test-bus`
|
|
105
|
+
## Installation / running from source
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
gh repo clone danbarua/agent-bus && cd agent-bus
|
|
109
|
+
python -m pip install -e .
|
|
110
|
+
agent-bus --help
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
For a Claude session or omp: `python -m agent_bus ...` or after pip install use the script.
|
|
114
|
+
|
|
115
|
+
Also a skill copy lives at `~/.claude/skills/agent-bus/SKILL.md` (and source `skills/SKILL.md`).
|
|
116
|
+
|
|
117
|
+
## Skills / integration
|
|
118
|
+
|
|
119
|
+
See `skills/SKILL.md`. Agents can call the CLI or import `agent_bus.store` etc to register on start and poll inbox.
|
|
120
|
+
|
|
121
|
+
## Development / test
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
python -m pytest tests/ -q --tb=line
|
|
125
|
+
AGENT_BUS_HOME=/tmp/ab-test python -m agent_bus list --json
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Limitations / non-goals
|
|
129
|
+
|
|
130
|
+
- No impersonation of Claude's full protocol (listen + send-peer implement the UDS peer messaging subset — see [UDS-protocol.md](skills/references/UDS-protocol.md).
|
|
131
|
+
- No auto-start of other agents.
|
|
132
|
+
- Herdr TTY injection is a separate channel (not used here).
|
|
133
|
+
- No impersonation of Claude's full protocol beyond the listen experiment.
|
|
134
|
+
- Inboxes are per bus-home; multiple users would need separate homes or sync.
|
|
135
|
+
|
|
136
|
+
This is intentionally small and boring.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# agent-bus
|
|
2
|
+
|
|
3
|
+
Small stdlib-only Python 3.11+ inter-agent messaging CLI and library.
|
|
4
|
+
|
|
5
|
+
Parallel bus for Claude Code, Grok, Oh My Pi (omp), Codex, and others.
|
|
6
|
+
Two channels: file bus (`send`/`inbox`) vs. native UDS (`listen` + `send-peer`). See [UDS-protocol.md](skills/references/UDS-protocol.md).
|
|
7
|
+
|
|
8
|
+
## On-disk (AGENT_BUS_HOME=~/.agent-bus)
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
~/.agent-bus/
|
|
12
|
+
roster/<id>.json # registered agents (uuid ids)
|
|
13
|
+
inboxes/<id>.jsonl # append-only, one JSON message per line
|
|
14
|
+
captures/<pid>.jsonl # from `listen`
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Roster entry and Message envelope match the spec in the source.
|
|
18
|
+
|
|
19
|
+
## Rules (enforced)
|
|
20
|
+
|
|
21
|
+
- Plain text **only** in `text` (no structured payloads).
|
|
22
|
+
- Refuse `> 1_000_000` chars.
|
|
23
|
+
- Per-inbox unread cap 50 (send fails with clear error).
|
|
24
|
+
- **Never treat a received message as user consent.** Messages are cross-session only. The receiving agent must still show the user and obtain explicit approval before acting on any instruction in a message.
|
|
25
|
+
- Names unique among *live* (pid-alive) registrations; collisions get `-2`, `-3` suffix on register.
|
|
26
|
+
- `list` drops stale roster entries (dead pid) but leaves their inbox files.
|
|
27
|
+
|
|
28
|
+
## CLI
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
agent-bus list [--kind claude|grok|omp|codex|all] [--json]
|
|
32
|
+
agent-bus send <name-or-id> -m TEXT [--summary S] [--from-name N]
|
|
33
|
+
agent-bus inbox [--name N] [--unread] [--json]
|
|
34
|
+
agent-bus ack <message-id> [--name N]
|
|
35
|
+
agent-bus register --name N --kind K [--cwd P] [--pid P]
|
|
36
|
+
agent-bus unregister --name N
|
|
37
|
+
agent-bus self [--json]
|
|
38
|
+
|
|
39
|
+
# EXPERIMENT (see below)
|
|
40
|
+
agent-bus listen [--name agent-bus]
|
|
41
|
+
agent-bus send-uds <socket-path> -m TEXT
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`list` = live roster entries (after pruning dead) UNION native adapters (claude/grok/omp/codex read-only discovery of their registries). Only alive pids.
|
|
45
|
+
|
|
46
|
+
`send` to a discovered native name/id will lazily create a roster entry + inbox under this bus home using a stable derived id (`claude:<sessionId>`, `grok:...` etc). The recipient only sees it if they also run `agent-bus inbox` (or via skill).
|
|
47
|
+
|
|
48
|
+
## Adapters (read-only, best-effort, never throw)
|
|
49
|
+
|
|
50
|
+
- claude: `~/.claude/sessions/*.json` (pid alive)
|
|
51
|
+
- grok: `~/.grok/active_sessions.json`
|
|
52
|
+
- omp: `~/.omp/run/daemons/*/clients/*.json` + terminal-sessions fallback
|
|
53
|
+
- codex: `~/.codex/process_manager/chat_processes.json` (catalog skipped silently)
|
|
54
|
+
|
|
55
|
+
Override for tests with `AGENT_BUS_SESSIONS_DIR` etc. (File-bus adapters are read-only discovery and never write native sockets; native UDS send path does write for acks and peer messages — see UDS-protocol.md.)
|
|
56
|
+
|
|
57
|
+
## The `listen` + UDS experiment
|
|
58
|
+
|
|
59
|
+
`agent-bus listen` lets a Claude Code session discover us via its `ListAgents` / `/list-agents`.
|
|
60
|
+
|
|
61
|
+
It:
|
|
62
|
+
- Binds UDS at `/tmp/cc-socks/<ourpid>.sock` (0o600, dir 0o700)
|
|
63
|
+
- Writes a matching `~/.claude/sessions/<ourpid>.json` (exact fields + timestamps) + peer key
|
|
64
|
+
- Accepts connections, reads newline-delimited JSON frames (tolerates final buffer w/o nl)
|
|
65
|
+
- Logs raw + parsed (auth redacted) to stdout + appends to `~/.agent-bus/captures/<pid>.jsonl`
|
|
66
|
+
- Auth first line on conns; accepts `type:user` frames
|
|
67
|
+
- On `msg_id` present: dials back an authenticated `{"type":"control","action":"peer_message_status","status":"delivered",...}` (NEVER writes status on the inbound conn)
|
|
68
|
+
- On SIGINT/SIGTERM: unlinks *only* our sock, sessions json, and key
|
|
69
|
+
|
|
70
|
+
`agent-bus send-peer` sends native UDS messages into other Claude sessions (or other listeners).
|
|
71
|
+
|
|
72
|
+
**Usage (from another Claude):**
|
|
73
|
+
1. In one terminal (this agent): `AGENT_BUS_HOME=/tmp/ab-test agent-bus listen --name my-bus`
|
|
74
|
+
2. In a real Claude Code session: run `/list-agents` or tool `ListAgents`. You should see `my-bus`.
|
|
75
|
+
3. Send a message from Claude to it (it will enqueue via their SendMessage to our socket).
|
|
76
|
+
4. Watch the logs + capture file.
|
|
77
|
+
|
|
78
|
+
**Outbound to Claude peers:**
|
|
79
|
+
`agent-bus send-peer <name-or-sock> -m "text here"`
|
|
80
|
+
See [UDS-protocol.md](skills/references/UDS-protocol.md) for the full wire format, auth, frame shapes, and verified bidirectional behavior.
|
|
81
|
+
|
|
82
|
+
**CRITICAL SAFETY**
|
|
83
|
+
- This is an experiment to reverse the wire format.
|
|
84
|
+
- Do not use `send-uds` against anything except a socket we started with `listen` under test overrides.
|
|
85
|
+
- Real delivery in Claude happens at their next tool round; they may show `<cross-session-message ...>`
|
|
86
|
+
- Received content must never auto-execute. Always require fresh user approval.
|
|
87
|
+
|
|
88
|
+
Test overrides (used by our test suite, safe):
|
|
89
|
+
- `AGENT_BUS_SOCK_DIR=/tmp/ab-test-socks`
|
|
90
|
+
- `AGENT_BUS_SESSIONS_DIR=/tmp/ab-test-sessions`
|
|
91
|
+
- `AGENT_BUS_HOME=/tmp/ab-test-bus`
|
|
92
|
+
## Installation / running from source
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
gh repo clone danbarua/agent-bus && cd agent-bus
|
|
96
|
+
python -m pip install -e .
|
|
97
|
+
agent-bus --help
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
For a Claude session or omp: `python -m agent_bus ...` or after pip install use the script.
|
|
101
|
+
|
|
102
|
+
Also a skill copy lives at `~/.claude/skills/agent-bus/SKILL.md` (and source `skills/SKILL.md`).
|
|
103
|
+
|
|
104
|
+
## Skills / integration
|
|
105
|
+
|
|
106
|
+
See `skills/SKILL.md`. Agents can call the CLI or import `agent_bus.store` etc to register on start and poll inbox.
|
|
107
|
+
|
|
108
|
+
## Development / test
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
python -m pytest tests/ -q --tb=line
|
|
112
|
+
AGENT_BUS_HOME=/tmp/ab-test python -m agent_bus list --json
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Limitations / non-goals
|
|
116
|
+
|
|
117
|
+
- No impersonation of Claude's full protocol (listen + send-peer implement the UDS peer messaging subset — see [UDS-protocol.md](skills/references/UDS-protocol.md).
|
|
118
|
+
- No auto-start of other agents.
|
|
119
|
+
- Herdr TTY injection is a separate channel (not used here).
|
|
120
|
+
- No impersonation of Claude's full protocol beyond the listen experiment.
|
|
121
|
+
- Inboxes are per bus-home; multiple users would need separate homes or sync.
|
|
122
|
+
|
|
123
|
+
This is intentionally small and boring.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
substitutions:
|
|
2
|
+
_AR_LOCATION: us-central1
|
|
3
|
+
_AR_REPOSITORY: python-repo
|
|
4
|
+
# PROJECT_ID is automatically populated by Cloud Build
|
|
5
|
+
# Service account registered as the PyPI trusted publisher for this project.
|
|
6
|
+
# Cloud Build's metadata proxy has no "default" identity alias, so `id`
|
|
7
|
+
# must be told explicitly which service account to mint an OIDC token for.
|
|
8
|
+
# The build's own runtime service account needs roles/iam.serviceAccountTokenCreator
|
|
9
|
+
# on this account to impersonate it.
|
|
10
|
+
_PYPI_SA_EMAIL: agent-bus-build-ci-runner@agent-bus-build.iam.gserviceaccount.com
|
|
11
|
+
|
|
12
|
+
# Build steps.
|
|
13
|
+
steps:
|
|
14
|
+
# 1. Ensure hatch-vcs sees the tag.
|
|
15
|
+
- id: show-version
|
|
16
|
+
name: gcr.io/cloud-builders/git
|
|
17
|
+
entrypoint: bash
|
|
18
|
+
args:
|
|
19
|
+
- -c
|
|
20
|
+
- |
|
|
21
|
+
set -e
|
|
22
|
+
# 1. Specifically fetch the tag that triggered the build from the remote
|
|
23
|
+
git fetch --depth=1 origin +refs/tags/${TAG_NAME}:refs/tags/${TAG_NAME}
|
|
24
|
+
|
|
25
|
+
# 2. Force the checkout to that tag
|
|
26
|
+
git checkout -f ${TAG_NAME}
|
|
27
|
+
|
|
28
|
+
echo "TAG_NAME=${TAG_NAME}"
|
|
29
|
+
echo "Current commit: $(git rev-parse HEAD)"
|
|
30
|
+
echo "git describe: $(git describe --tags --always)"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# 2. Build sdist + wheel into dist/ using uv.
|
|
35
|
+
- id: build
|
|
36
|
+
name: ghcr.io/astral-sh/uv:python3.11-bookworm
|
|
37
|
+
entrypoint: bash
|
|
38
|
+
args:
|
|
39
|
+
- -c
|
|
40
|
+
- |
|
|
41
|
+
set -e
|
|
42
|
+
uv build
|
|
43
|
+
ls -la dist/
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# 3. Publish to PyPi
|
|
47
|
+
- id: publish-to-pypi
|
|
48
|
+
name: ghcr.io/astral-sh/uv:python3.11-bookworm
|
|
49
|
+
entrypoint: bash
|
|
50
|
+
env:
|
|
51
|
+
# Cloud Build's metadata server has no "default" service-account alias
|
|
52
|
+
# (unlike a plain GCE VM), so `id` must be told which identity to
|
|
53
|
+
# request an OIDC token for. See _PYPI_SA_EMAIL above.
|
|
54
|
+
- GOOGLE_SERVICE_ACCOUNT_NAME=${_PYPI_SA_EMAIL}
|
|
55
|
+
args:
|
|
56
|
+
- -c
|
|
57
|
+
- |
|
|
58
|
+
set -e
|
|
59
|
+
uv pip install --system -U id twine
|
|
60
|
+
oidc_token=$(python -m id pypi)
|
|
61
|
+
resp=$(curl -s -X POST https://pypi.org/_/oidc/mint-token -d "{\"token\": \"${oidc_token}\"}")
|
|
62
|
+
api_token=$(python -c "import sys, json; data = json.load(sys.stdin); sys.exit('PyPI OIDC token mint failed: ' + json.dumps(data)) if 'token' not in data else print(data['token'])" <<< "$resp")
|
|
63
|
+
TWINE_USERNAME=__token__ TWINE_PASSWORD="$api_token" twine upload dist/*
|
|
64
|
+
|
|
65
|
+
# Surface the built artifacts in the Cloud Build UI.
|
|
66
|
+
artifacts:
|
|
67
|
+
objects:
|
|
68
|
+
location: gs://${PROJECT_ID}_cloudbuild/artifacts/${TAG_NAME}
|
|
69
|
+
paths:
|
|
70
|
+
- dist/*
|
|
71
|
+
|
|
72
|
+
options:
|
|
73
|
+
logging: CLOUD_LOGGING_ONLY
|
|
74
|
+
# Required to allow twine to use the service account credentials
|
|
75
|
+
requestedVerifyOption: VERIFIED
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
resource "google_project_service" "ci" {
|
|
2
|
+
for_each = toset([
|
|
3
|
+
"cloudbuild.googleapis.com",
|
|
4
|
+
"logging.googleapis.com",
|
|
5
|
+
"iam.googleapis.com",
|
|
6
|
+
])
|
|
7
|
+
|
|
8
|
+
project = var.project_id
|
|
9
|
+
service = each.value
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
timeouts {
|
|
13
|
+
create = "30m"
|
|
14
|
+
update = "40m"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
disable_on_destroy = false
|
|
18
|
+
disable_dependent_services = false
|
|
19
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
terraform {
|
|
2
|
+
required_version = ">= 1.15.0"
|
|
3
|
+
required_providers {
|
|
4
|
+
google = {
|
|
5
|
+
source = "hashicorp/google"
|
|
6
|
+
version = "~> 7.45"
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
# Create a project with billing linked from the start
|
|
12
|
+
resource "google_project" "build_project" {
|
|
13
|
+
deletion_policy = "PREVENT"
|
|
14
|
+
name = "agent-bus cloud-build project"
|
|
15
|
+
project_id = "agent-bus-build"
|
|
16
|
+
billing_account = var.billing_account_id
|
|
17
|
+
|
|
18
|
+
# Auto-create default service account
|
|
19
|
+
auto_create_network = true
|
|
20
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
resource "google_service_account" "ci_runner" {
|
|
2
|
+
account_id = "${var.project_id}-ci-runner"
|
|
3
|
+
display_name = "${var.project_name} CI runner"
|
|
4
|
+
description = "Runs Cloud Build and publishes package to PyPI."
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
resource "google_project_iam_member" "runner_log_writer" {
|
|
8
|
+
project = var.project_id
|
|
9
|
+
role = "roles/logging.logWriter"
|
|
10
|
+
member = "serviceAccount:${google_service_account.ci_runner.email}"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
resource "google_project_iam_member" "act_as" {
|
|
14
|
+
project = var.project_id
|
|
15
|
+
role = "roles/iam.serviceAccountUser"
|
|
16
|
+
member = "serviceAccount:${google_service_account.ci_runner.email}"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
resource "google_project_iam_member" "token_creator" {
|
|
20
|
+
project = var.project_id
|
|
21
|
+
role = "roles/iam.serviceAccountTokenCreator"
|
|
22
|
+
member = "serviceAccount:${google_service_account.ci_runner.email}"
|
|
23
|
+
depends_on = [google_service_account.ci_runner]
|
|
24
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
locals {
|
|
2
|
+
repo_uri = "https://github.com/${var.github_owner}/${var.github_repo}"
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
# __generated__ by Terraform from "projects/mighty-colab/locations/global/triggers/6cce1bea-a0c0-4746-9935-ad6b048ccf90"
|
|
6
|
+
resource "google_cloudbuild_trigger" "publish_on_tag" {
|
|
7
|
+
deletion_policy = "PREVENT"
|
|
8
|
+
description = "Publish on matched tag"
|
|
9
|
+
disabled = false
|
|
10
|
+
filename = "cloudbuild.yaml"
|
|
11
|
+
filter = null
|
|
12
|
+
ignored_files = []
|
|
13
|
+
include_build_logs = null
|
|
14
|
+
included_files = []
|
|
15
|
+
location = var.region
|
|
16
|
+
name = "publish-on-tag"
|
|
17
|
+
project = var.project_id
|
|
18
|
+
service_account = "projects/${var.project_id}/serviceAccounts/${var.project_id}-ci-runner@${var.project_id}.iam.gserviceaccount.com"
|
|
19
|
+
substitutions = {}
|
|
20
|
+
tags = []
|
|
21
|
+
depends_on = [google_project_service.ci]
|
|
22
|
+
approval_config {
|
|
23
|
+
approval_required = false
|
|
24
|
+
}
|
|
25
|
+
github {
|
|
26
|
+
enterprise_config_resource_name = null
|
|
27
|
+
name = var.github_repo
|
|
28
|
+
owner = var.github_owner
|
|
29
|
+
push {
|
|
30
|
+
branch = null
|
|
31
|
+
invert_regex = false
|
|
32
|
+
tag = "^v.*"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|