agent-baton-cli 0.1.0__tar.gz → 0.2.0__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_baton_cli-0.2.0/.dockerignore +6 -0
- agent_baton_cli-0.2.0/.github/workflows/release.yml +62 -0
- agent_baton_cli-0.2.0/CHANGELOG.md +25 -0
- agent_baton_cli-0.2.0/Dockerfile +27 -0
- agent_baton_cli-0.1.0/README.md → agent_baton_cli-0.2.0/PKG-INFO +74 -2
- agent_baton_cli-0.1.0/PKG-INFO → agent_baton_cli-0.2.0/README.md +52 -13
- agent_baton_cli-0.2.0/integrations/mcp/README.md +60 -0
- agent_baton_cli-0.2.0/pyproject.toml +42 -0
- {agent_baton_cli-0.1.0 → agent_baton_cli-0.2.0}/src/agent_baton/__init__.py +1 -1
- {agent_baton_cli-0.1.0 → agent_baton_cli-0.2.0}/src/agent_baton/cli.py +52 -10
- agent_baton_cli-0.2.0/src/agent_baton/mcp.py +309 -0
- agent_baton_cli-0.2.0/src/agent_baton/quickstart.py +109 -0
- {agent_baton_cli-0.1.0 → agent_baton_cli-0.2.0}/src/agent_baton/schema.py +2 -1
- {agent_baton_cli-0.1.0 → agent_baton_cli-0.2.0}/src/agent_baton/server.py +4 -0
- {agent_baton_cli-0.1.0 → agent_baton_cli-0.2.0}/src/agent_baton/verify.py +82 -6
- agent_baton_cli-0.2.0/tests/test_baton.py +578 -0
- agent_baton_cli-0.1.0/.github/workflows/release.yml +0 -30
- agent_baton_cli-0.1.0/pyproject.toml +0 -29
- agent_baton_cli-0.1.0/tests/test_baton.py +0 -232
- {agent_baton_cli-0.1.0 → agent_baton_cli-0.2.0}/.github/workflows/ci.yml +0 -0
- {agent_baton_cli-0.1.0 → agent_baton_cli-0.2.0}/.gitignore +0 -0
- {agent_baton_cli-0.1.0 → agent_baton_cli-0.2.0}/LICENSE +0 -0
- {agent_baton_cli-0.1.0 → agent_baton_cli-0.2.0}/docs/demo/demo.gif +0 -0
- {agent_baton_cli-0.1.0 → agent_baton_cli-0.2.0}/docs/demo/make_demo.py +0 -0
- {agent_baton_cli-0.1.0 → agent_baton_cli-0.2.0}/integrations/aider/CONVENTIONS.md +0 -0
- {agent_baton_cli-0.1.0 → agent_baton_cli-0.2.0}/integrations/claude-code/skills/baton/SKILL.md +0 -0
- {agent_baton_cli-0.1.0 → agent_baton_cli-0.2.0}/integrations/codex/AGENTS.md +0 -0
- {agent_baton_cli-0.1.0 → agent_baton_cli-0.2.0}/integrations/cursor/baton.mdc +0 -0
- {agent_baton_cli-0.1.0 → agent_baton_cli-0.2.0}/src/agent_baton/__main__.py +0 -0
- {agent_baton_cli-0.1.0 → agent_baton_cli-0.2.0}/src/agent_baton/client.py +0 -0
- {agent_baton_cli-0.1.0 → agent_baton_cli-0.2.0}/src/agent_baton/store.py +0 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
# Push a tag like v0.1.0 to publish to PyPI and ghcr.io/varnholt/agent-baton.
|
|
4
|
+
# Uses PyPI trusted publishing and the workflow's GITHUB_TOKEN: no token is stored in this repo.
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
tags: ["v*"]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
pypi:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
environment: pypi
|
|
14
|
+
permissions:
|
|
15
|
+
id-token: write
|
|
16
|
+
contents: write
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: astral-sh/setup-uv@v6
|
|
20
|
+
- run: uv run --group dev pytest -q
|
|
21
|
+
- name: check tag matches version
|
|
22
|
+
run: |
|
|
23
|
+
v=$(grep -m1 '^version' pyproject.toml | cut -d'"' -f2)
|
|
24
|
+
test "v$v" = "$GITHUB_REF_NAME" || { echo "tag $GITHUB_REF_NAME != version $v"; exit 1; }
|
|
25
|
+
- run: uv build
|
|
26
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
27
|
+
- name: GitHub release
|
|
28
|
+
env:
|
|
29
|
+
GH_TOKEN: ${{ github.token }}
|
|
30
|
+
run: gh release create "$GITHUB_REF_NAME" dist/* --generate-notes
|
|
31
|
+
|
|
32
|
+
image:
|
|
33
|
+
# Runs after the PyPI job, so tests and the tag/version check have passed.
|
|
34
|
+
needs: pypi
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
permissions:
|
|
37
|
+
contents: read
|
|
38
|
+
packages: write
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v4
|
|
41
|
+
- name: version from tag
|
|
42
|
+
id: v
|
|
43
|
+
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
|
|
44
|
+
- uses: docker/setup-qemu-action@v3
|
|
45
|
+
- uses: docker/setup-buildx-action@v3
|
|
46
|
+
- uses: docker/login-action@v3
|
|
47
|
+
with:
|
|
48
|
+
registry: ghcr.io
|
|
49
|
+
username: ${{ github.actor }}
|
|
50
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
51
|
+
- uses: docker/build-push-action@v6
|
|
52
|
+
with:
|
|
53
|
+
context: .
|
|
54
|
+
platforms: linux/amd64,linux/arm64
|
|
55
|
+
push: true
|
|
56
|
+
tags: |
|
|
57
|
+
ghcr.io/varnholt/agent-baton:${{ steps.v.outputs.version }}
|
|
58
|
+
ghcr.io/varnholt/agent-baton:latest
|
|
59
|
+
labels: |
|
|
60
|
+
org.opencontainers.image.source=https://github.com/varnholt/agent-baton
|
|
61
|
+
org.opencontainers.image.version=${{ steps.v.outputs.version }}
|
|
62
|
+
org.opencontainers.image.licenses=MIT
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
- `build_finished` facts can be verified against GitHub Actions. Evidence is the run
|
|
6
|
+
URL (`https://github.com/<owner>/<repo>/actions/runs/<id>`, optionally with
|
|
7
|
+
`/attempts/<n>` or `/job/<id>`); `build` is the run id, `job` is the workflow file
|
|
8
|
+
stem or name. The verifier is picked by the evidence host; Jenkins is unchanged.
|
|
9
|
+
- `baton quickstart`: a local single-user server for a first try. Creates a
|
|
10
|
+
database and token in the per-user data directory, prints the env lines for
|
|
11
|
+
bash and PowerShell, and serves on 127.0.0.1 only.
|
|
12
|
+
- `baton mcp`: an MCP stdio server with `baton_publish`, `baton_wait`,
|
|
13
|
+
`baton_facts` and `baton_types`. Standard library only.
|
|
14
|
+
- Docker image for the server, published to `ghcr.io/varnholt/agent-baton` on release.
|
|
15
|
+
|
|
16
|
+
## 0.1.1
|
|
17
|
+
|
|
18
|
+
- PyPI metadata: project URLs, keywords and classifiers. No code changes.
|
|
19
|
+
|
|
20
|
+
## 0.1.0
|
|
21
|
+
|
|
22
|
+
- First release, on PyPI as `agent-baton-cli`: typed facts with strict field
|
|
23
|
+
patterns, an HTTP + SQLite server with long-poll waits, receiver-side
|
|
24
|
+
verification against GitHub, Bitbucket Cloud and Jenkins, and instructions for
|
|
25
|
+
Claude Code, Codex, Cursor and Aider.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# baton fact server.
|
|
2
|
+
# docker build -t agent-baton .
|
|
3
|
+
# docker run --rm -v baton-data:/data agent-baton baton member add alice --db /data/baton.db
|
|
4
|
+
# docker run -d -p 8765:8765 -v baton-data:/data agent-baton
|
|
5
|
+
# Serves plain HTTP: put it behind a TLS proxy, or pass --tls-cert/--tls-key.
|
|
6
|
+
FROM python:3.13-slim
|
|
7
|
+
|
|
8
|
+
ENV PYTHONDONTWRITEBYTECODE=1 \
|
|
9
|
+
PYTHONUNBUFFERED=1 \
|
|
10
|
+
PIP_NO_CACHE_DIR=1 \
|
|
11
|
+
PIP_DISABLE_PIP_VERSION_CHECK=1
|
|
12
|
+
|
|
13
|
+
WORKDIR /src
|
|
14
|
+
COPY pyproject.toml README.md LICENSE ./
|
|
15
|
+
COPY src ./src
|
|
16
|
+
RUN pip install . && rm -rf /src
|
|
17
|
+
|
|
18
|
+
RUN useradd --system --uid 10001 --home-dir /data --shell /usr/sbin/nologin baton \
|
|
19
|
+
&& mkdir -p /data && chown baton:baton /data
|
|
20
|
+
|
|
21
|
+
USER baton
|
|
22
|
+
WORKDIR /data
|
|
23
|
+
VOLUME /data
|
|
24
|
+
EXPOSE 8765
|
|
25
|
+
|
|
26
|
+
# No HEALTHCHECK: every route, /v1/health included, needs a member token.
|
|
27
|
+
CMD ["baton", "serve", "--db", "/data/baton.db", "--host", "0.0.0.0"]
|
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: agent-baton-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Pass verified facts between coding agents, so none of them sits waiting on a human relay.
|
|
5
|
+
Project-URL: Homepage, https://github.com/varnholt/agent-baton
|
|
6
|
+
Project-URL: Source, https://github.com/varnholt/agent-baton
|
|
7
|
+
Project-URL: Issues, https://github.com/varnholt/agent-baton/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/varnholt/agent-baton/blob/main/CHANGELOG.md
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai-agents,claude-code,codex,coding-agents,llm-security,multi-agent,prompt-injection
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Topic :: Software Development
|
|
19
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
1
23
|
# agent-baton
|
|
2
24
|
|
|
3
25
|
[](https://github.com/varnholt/agent-baton/actions/workflows/ci.yml)
|
|
@@ -44,7 +66,7 @@ errors and confusion. baton is built so that does not happen:
|
|
|
44
66
|
| Risk | What baton does |
|
|
45
67
|
|---|---|
|
|
46
68
|
| One agent instructs another | **Facts, not messages.** Every field is a short string matching a strict pattern. There is no free-text field to put a prompt in. |
|
|
47
|
-
| A confused or malicious peer lies | **The receiver checks the source.** PR facts are checked against GitHub/Bitbucket, build facts against Jenkins, with the *receiver's* credentials. A contradicted fact exits non-zero. |
|
|
69
|
+
| A confused or malicious peer lies | **The receiver checks the source.** PR facts are checked against GitHub/Bitbucket, build facts against Jenkins or GitHub Actions, with the *receiver's* credentials. A contradicted fact exits non-zero. |
|
|
48
70
|
| Fake evidence (`https://evil.example/...`) | Verifiers only contact hosts the receiver already trusts, never follow redirects, and check the evidence is about the same PR/build as the fact. |
|
|
49
71
|
| Impersonation | The publisher is set by the server from the token, never from the payload. |
|
|
50
72
|
| "Who did that?" | Append-only log: every fact has publisher, timestamp and evidence. |
|
|
@@ -58,10 +80,25 @@ useful pointers, never grounds for an irreversible action.
|
|
|
58
80
|
|---|---|---|---|
|
|
59
81
|
| `pr_merged` | repo, pr | commit | GitHub, Bitbucket Cloud |
|
|
60
82
|
| `pr_state` | repo, pr | state (`open/merged/declined/closed`) | GitHub, Bitbucket Cloud |
|
|
61
|
-
| `build_finished` | job, build | result (`SUCCESS/FAILURE/...`) | Jenkins (`JENKINS_URL`) |
|
|
83
|
+
| `build_finished` | job, build | result (`SUCCESS/FAILURE/...`) | Jenkins (`JENKINS_URL`), GitHub Actions |
|
|
62
84
|
| `working_on` | repo, path | branch, state (`started/stopped`) | - |
|
|
63
85
|
| `hint` | topic | tag (slug) | - |
|
|
64
86
|
|
|
87
|
+
The verifier is chosen by the evidence host. For GitHub Actions the evidence is the
|
|
88
|
+
run URL, `https://github.com/<owner>/<repo>/actions/runs/<run_id>` (a trailing
|
|
89
|
+
`/attempts/<n>` checks that attempt; `/job/<id>` is accepted and checks the whole run).
|
|
90
|
+
`build` must be the run id. `job` names the workflow, case-insensitive: the workflow
|
|
91
|
+
file name without extension (`.github/workflows/ci.yml` -> `ci`) or the workflow's
|
|
92
|
+
display name, optionally prefixed with `<repo>/` or `<owner>/<repo>/`, which must then
|
|
93
|
+
match the evidence. The run must be `completed`; its conclusion maps to `success` ->
|
|
94
|
+
`SUCCESS`, `failure`/`timed_out`/`startup_failure` -> `FAILURE`, `cancelled` -> `ABORTED`,
|
|
95
|
+
`skipped`/`neutral` -> `NOT_BUILT`.
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
baton publish build_finished -s job=ci -s build=19121611211 -v result=SUCCESS \
|
|
99
|
+
-e https://github.com/acme/api/actions/runs/19121611211
|
|
100
|
+
```
|
|
101
|
+
|
|
65
102
|
Add your own with `baton serve --types types.json` (always delivered as unverifiable):
|
|
66
103
|
|
|
67
104
|
```json
|
|
@@ -74,8 +111,31 @@ Add your own with `baton serve --types types.json` (always delivered as unverifi
|
|
|
74
111
|
|
|
75
112
|
Pure Python 3.10+, no dependencies.
|
|
76
113
|
|
|
114
|
+
### 60-second quickstart
|
|
115
|
+
|
|
77
116
|
```
|
|
78
117
|
pip install agent-baton-cli # or: uv tool install agent-baton-cli
|
|
118
|
+
baton quickstart
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`quickstart` creates a database and a token for you in your per-user data directory
|
|
122
|
+
(`~/.local/share/baton`, or `%LOCALAPPDATA%\baton` on Windows), prints the
|
|
123
|
+
`BATON_URL`/`BATON_TOKEN` lines for bash and PowerShell, and serves on
|
|
124
|
+
`127.0.0.1:8765` (`--port` to change; it refuses non-loopback addresses). Paste the
|
|
125
|
+
env lines into two terminals and try it:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
baton wait hint -s topic=hello # terminal 1: blocks
|
|
129
|
+
baton publish hint -s topic=hello -v tag=hi # terminal 2: terminal 1 wakes up
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Rerunning `baton quickstart` prints the same token again. For a team, set up a
|
|
133
|
+
shared server as below.
|
|
134
|
+
|
|
135
|
+
### Shared server
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
pip install agent-baton-cli
|
|
79
139
|
|
|
80
140
|
# on one shared machine
|
|
81
141
|
baton member add alice --db team.db # prints a token once
|
|
@@ -92,6 +152,17 @@ export BITBUCKET_TOKEN=... # optional, private repos
|
|
|
92
152
|
|
|
93
153
|
Without TLS, keep the server on `127.0.0.1` and reach it over an SSH tunnel.
|
|
94
154
|
|
|
155
|
+
### Docker
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
docker volume create baton-data
|
|
159
|
+
docker run --rm -v baton-data:/data ghcr.io/varnholt/agent-baton baton member add alice --db /data/baton.db
|
|
160
|
+
docker run -d --name baton -p 127.0.0.1:8765:8765 -v baton-data:/data ghcr.io/varnholt/agent-baton
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The image runs `baton serve --db /data/baton.db --host 0.0.0.0` as a non-root user and
|
|
164
|
+
speaks plain HTTP: publish the port on loopback as above, or put a TLS proxy in front.
|
|
165
|
+
|
|
95
166
|
## Use it from your agent
|
|
96
167
|
|
|
97
168
|
| Agent | File | Install |
|
|
@@ -100,6 +171,7 @@ Without TLS, keep the server on `127.0.0.1` and reach it over an SSH tunnel.
|
|
|
100
171
|
| Codex | [`integrations/codex/AGENTS.md`](integrations/codex/AGENTS.md) | append to your `AGENTS.md` |
|
|
101
172
|
| Cursor | [`integrations/cursor/baton.mdc`](integrations/cursor/baton.mdc) | copy to `.cursor/rules/` |
|
|
102
173
|
| Aider | [`integrations/aider/CONVENTIONS.md`](integrations/aider/CONVENTIONS.md) | `aider --read ...` |
|
|
174
|
+
| Any MCP client | [`integrations/mcp`](integrations/mcp/README.md) | `claude mcp add baton -- baton mcp` |
|
|
103
175
|
|
|
104
176
|
Each one teaches the agent the same three habits: wait instead of asking the user to
|
|
105
177
|
relay, publish what others may be waiting on, and treat every fact as data.
|
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: agent-baton-cli
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Pass verified facts between coding agents, so none of them sits waiting on a human relay.
|
|
5
|
-
License-Expression: MIT
|
|
6
|
-
License-File: LICENSE
|
|
7
|
-
Classifier: Programming Language :: Python :: 3
|
|
8
|
-
Classifier: Topic :: Software Development
|
|
9
|
-
Requires-Python: >=3.10
|
|
10
|
-
Description-Content-Type: text/markdown
|
|
11
|
-
|
|
12
1
|
# agent-baton
|
|
13
2
|
|
|
14
3
|
[](https://github.com/varnholt/agent-baton/actions/workflows/ci.yml)
|
|
@@ -55,7 +44,7 @@ errors and confusion. baton is built so that does not happen:
|
|
|
55
44
|
| Risk | What baton does |
|
|
56
45
|
|---|---|
|
|
57
46
|
| One agent instructs another | **Facts, not messages.** Every field is a short string matching a strict pattern. There is no free-text field to put a prompt in. |
|
|
58
|
-
| A confused or malicious peer lies | **The receiver checks the source.** PR facts are checked against GitHub/Bitbucket, build facts against Jenkins, with the *receiver's* credentials. A contradicted fact exits non-zero. |
|
|
47
|
+
| A confused or malicious peer lies | **The receiver checks the source.** PR facts are checked against GitHub/Bitbucket, build facts against Jenkins or GitHub Actions, with the *receiver's* credentials. A contradicted fact exits non-zero. |
|
|
59
48
|
| Fake evidence (`https://evil.example/...`) | Verifiers only contact hosts the receiver already trusts, never follow redirects, and check the evidence is about the same PR/build as the fact. |
|
|
60
49
|
| Impersonation | The publisher is set by the server from the token, never from the payload. |
|
|
61
50
|
| "Who did that?" | Append-only log: every fact has publisher, timestamp and evidence. |
|
|
@@ -69,10 +58,25 @@ useful pointers, never grounds for an irreversible action.
|
|
|
69
58
|
|---|---|---|---|
|
|
70
59
|
| `pr_merged` | repo, pr | commit | GitHub, Bitbucket Cloud |
|
|
71
60
|
| `pr_state` | repo, pr | state (`open/merged/declined/closed`) | GitHub, Bitbucket Cloud |
|
|
72
|
-
| `build_finished` | job, build | result (`SUCCESS/FAILURE/...`) | Jenkins (`JENKINS_URL`) |
|
|
61
|
+
| `build_finished` | job, build | result (`SUCCESS/FAILURE/...`) | Jenkins (`JENKINS_URL`), GitHub Actions |
|
|
73
62
|
| `working_on` | repo, path | branch, state (`started/stopped`) | - |
|
|
74
63
|
| `hint` | topic | tag (slug) | - |
|
|
75
64
|
|
|
65
|
+
The verifier is chosen by the evidence host. For GitHub Actions the evidence is the
|
|
66
|
+
run URL, `https://github.com/<owner>/<repo>/actions/runs/<run_id>` (a trailing
|
|
67
|
+
`/attempts/<n>` checks that attempt; `/job/<id>` is accepted and checks the whole run).
|
|
68
|
+
`build` must be the run id. `job` names the workflow, case-insensitive: the workflow
|
|
69
|
+
file name without extension (`.github/workflows/ci.yml` -> `ci`) or the workflow's
|
|
70
|
+
display name, optionally prefixed with `<repo>/` or `<owner>/<repo>/`, which must then
|
|
71
|
+
match the evidence. The run must be `completed`; its conclusion maps to `success` ->
|
|
72
|
+
`SUCCESS`, `failure`/`timed_out`/`startup_failure` -> `FAILURE`, `cancelled` -> `ABORTED`,
|
|
73
|
+
`skipped`/`neutral` -> `NOT_BUILT`.
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
baton publish build_finished -s job=ci -s build=19121611211 -v result=SUCCESS \
|
|
77
|
+
-e https://github.com/acme/api/actions/runs/19121611211
|
|
78
|
+
```
|
|
79
|
+
|
|
76
80
|
Add your own with `baton serve --types types.json` (always delivered as unverifiable):
|
|
77
81
|
|
|
78
82
|
```json
|
|
@@ -85,8 +89,31 @@ Add your own with `baton serve --types types.json` (always delivered as unverifi
|
|
|
85
89
|
|
|
86
90
|
Pure Python 3.10+, no dependencies.
|
|
87
91
|
|
|
92
|
+
### 60-second quickstart
|
|
93
|
+
|
|
88
94
|
```
|
|
89
95
|
pip install agent-baton-cli # or: uv tool install agent-baton-cli
|
|
96
|
+
baton quickstart
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`quickstart` creates a database and a token for you in your per-user data directory
|
|
100
|
+
(`~/.local/share/baton`, or `%LOCALAPPDATA%\baton` on Windows), prints the
|
|
101
|
+
`BATON_URL`/`BATON_TOKEN` lines for bash and PowerShell, and serves on
|
|
102
|
+
`127.0.0.1:8765` (`--port` to change; it refuses non-loopback addresses). Paste the
|
|
103
|
+
env lines into two terminals and try it:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
baton wait hint -s topic=hello # terminal 1: blocks
|
|
107
|
+
baton publish hint -s topic=hello -v tag=hi # terminal 2: terminal 1 wakes up
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Rerunning `baton quickstart` prints the same token again. For a team, set up a
|
|
111
|
+
shared server as below.
|
|
112
|
+
|
|
113
|
+
### Shared server
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
pip install agent-baton-cli
|
|
90
117
|
|
|
91
118
|
# on one shared machine
|
|
92
119
|
baton member add alice --db team.db # prints a token once
|
|
@@ -103,6 +130,17 @@ export BITBUCKET_TOKEN=... # optional, private repos
|
|
|
103
130
|
|
|
104
131
|
Without TLS, keep the server on `127.0.0.1` and reach it over an SSH tunnel.
|
|
105
132
|
|
|
133
|
+
### Docker
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
docker volume create baton-data
|
|
137
|
+
docker run --rm -v baton-data:/data ghcr.io/varnholt/agent-baton baton member add alice --db /data/baton.db
|
|
138
|
+
docker run -d --name baton -p 127.0.0.1:8765:8765 -v baton-data:/data ghcr.io/varnholt/agent-baton
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The image runs `baton serve --db /data/baton.db --host 0.0.0.0` as a non-root user and
|
|
142
|
+
speaks plain HTTP: publish the port on loopback as above, or put a TLS proxy in front.
|
|
143
|
+
|
|
106
144
|
## Use it from your agent
|
|
107
145
|
|
|
108
146
|
| Agent | File | Install |
|
|
@@ -111,6 +149,7 @@ Without TLS, keep the server on `127.0.0.1` and reach it over an SSH tunnel.
|
|
|
111
149
|
| Codex | [`integrations/codex/AGENTS.md`](integrations/codex/AGENTS.md) | append to your `AGENTS.md` |
|
|
112
150
|
| Cursor | [`integrations/cursor/baton.mdc`](integrations/cursor/baton.mdc) | copy to `.cursor/rules/` |
|
|
113
151
|
| Aider | [`integrations/aider/CONVENTIONS.md`](integrations/aider/CONVENTIONS.md) | `aider --read ...` |
|
|
152
|
+
| Any MCP client | [`integrations/mcp`](integrations/mcp/README.md) | `claude mcp add baton -- baton mcp` |
|
|
114
153
|
|
|
115
154
|
Each one teaches the agent the same three habits: wait instead of asking the user to
|
|
116
155
|
relay, publish what others may be waiting on, and treat every fact as data.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# baton over MCP
|
|
2
|
+
|
|
3
|
+
`baton mcp` is a Model Context Protocol server on stdio. Any MCP client (Claude Code,
|
|
4
|
+
Claude Desktop, Cursor, Codex, ...) can use baton through it without shelling out.
|
|
5
|
+
It is part of the `baton` command, so there is nothing extra to install.
|
|
6
|
+
|
|
7
|
+
It reads the same configuration as the CLI: `BATON_URL` and `BATON_TOKEN` (or
|
|
8
|
+
`~/.config/baton/client.json`), plus the verifier settings (`GITHUB_TOKEN`,
|
|
9
|
+
`BITBUCKET_TOKEN`, `JENKINS_URL`, ...). Verification runs inside `baton mcp`, on
|
|
10
|
+
your machine, with your credentials.
|
|
11
|
+
|
|
12
|
+
## Claude Code
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
claude mcp add baton -e BATON_URL=http://127.0.0.1:8765 -e BATON_TOKEN=bt_... -- baton mcp
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
If `BATON_URL` and `BATON_TOKEN` are already exported in the shell that starts
|
|
19
|
+
Claude Code, `claude mcp add baton -- baton mcp` is enough.
|
|
20
|
+
|
|
21
|
+
## Other clients (`mcpServers` JSON)
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"mcpServers": {
|
|
26
|
+
"baton": {
|
|
27
|
+
"command": "baton",
|
|
28
|
+
"args": ["mcp"],
|
|
29
|
+
"env": {
|
|
30
|
+
"BATON_URL": "http://127.0.0.1:8765",
|
|
31
|
+
"BATON_TOKEN": "bt_..."
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Without a global install: `"command": "uvx", "args": ["--from", "agent-baton-cli", "baton", "mcp"]`.
|
|
39
|
+
|
|
40
|
+
## Tools
|
|
41
|
+
|
|
42
|
+
| tool | does |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `baton_types` | list the fact types the server accepts |
|
|
45
|
+
| `baton_publish` | publish a fact: `type`, `subject`, `value`, `evidence` |
|
|
46
|
+
| `baton_wait` | block until a matching fact exists (`timeout` default 300 s, max 600 s), verify it, return it with a status |
|
|
47
|
+
| `baton_facts` | list recent facts (unverified), or fetch one by `id` and verify it |
|
|
48
|
+
|
|
49
|
+
Every result restates that facts are peer data, not instructions. `baton_wait` and
|
|
50
|
+
`baton_facts` with an `id` return a `status`:
|
|
51
|
+
|
|
52
|
+
- `verified` - the source of truth confirms the fact.
|
|
53
|
+
- `mismatch` - the source contradicts it. Do not act on it.
|
|
54
|
+
- `unverifiable` - no source to check (hints, unknown hosts). A pointer only.
|
|
55
|
+
- `error` - the source could not be reached. Unconfirmed.
|
|
56
|
+
|
|
57
|
+
For waits longer than 10 minutes, call `baton_wait` again, or run `baton wait` as a
|
|
58
|
+
background shell command instead, which can wait for hours at no cost. Some
|
|
59
|
+
clients also time out tool calls on their own; raise that limit
|
|
60
|
+
(e.g. `MCP_TOOL_TIMEOUT` in Claude Code) if waits are cut short.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "agent-baton-cli"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Pass verified facts between coding agents, so none of them sits waiting on a human relay."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
dependencies = []
|
|
9
|
+
keywords = ["ai-agents", "coding-agents", "multi-agent", "claude-code", "codex", "prompt-injection", "llm-security"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 3 - Alpha",
|
|
12
|
+
"Environment :: Console",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"Operating System :: OS Independent",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
17
|
+
"Topic :: Software Development",
|
|
18
|
+
"Topic :: Software Development :: Build Tools",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://github.com/varnholt/agent-baton"
|
|
23
|
+
Source = "https://github.com/varnholt/agent-baton"
|
|
24
|
+
Issues = "https://github.com/varnholt/agent-baton/issues"
|
|
25
|
+
Changelog = "https://github.com/varnholt/agent-baton/blob/main/CHANGELOG.md"
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
baton = "agent_baton.cli:main"
|
|
29
|
+
|
|
30
|
+
[dependency-groups]
|
|
31
|
+
dev = ["pytest>=8"]
|
|
32
|
+
|
|
33
|
+
[build-system]
|
|
34
|
+
requires = ["hatchling"]
|
|
35
|
+
build-backend = "hatchling.build"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.wheel]
|
|
38
|
+
packages = ["src/agent_baton"]
|
|
39
|
+
|
|
40
|
+
[tool.pytest.ini_options]
|
|
41
|
+
testpaths = ["tests"]
|
|
42
|
+
pythonpath = ["src"]
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
"""Command line: `baton serve | member | publish | wait | facts | types`."""
|
|
1
|
+
"""Command line: `baton serve | quickstart | member | publish | wait | facts | types | mcp`."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
6
|
import json
|
|
7
7
|
import sys
|
|
8
|
+
from pathlib import Path
|
|
8
9
|
|
|
9
10
|
from . import __version__
|
|
10
11
|
from .client import BatonError, Client
|
|
@@ -43,17 +44,17 @@ def _print_fact(fact: dict, check: dict | None) -> None:
|
|
|
43
44
|
# commands ------------------------------------------------------------------
|
|
44
45
|
|
|
45
46
|
|
|
46
|
-
def
|
|
47
|
+
def _run_server(store, types, host: str, port: int, tls_cert: str | None = None, tls_key: str | None = None) -> int:
|
|
47
48
|
from .server import serve
|
|
48
|
-
from .store import Store
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
try:
|
|
51
|
+
srv = serve(store, types, host, port, tls_cert, tls_key)
|
|
52
|
+
except OSError as e:
|
|
53
|
+
store.close()
|
|
54
|
+
print(f"baton: cannot serve on {host}:{port}: {e}", file=sys.stderr)
|
|
55
|
+
return EXIT_ERROR
|
|
56
|
+
scheme = "https" if tls_cert else "http"
|
|
57
|
+
print(f"baton {__version__} serving {scheme}://{host}:{port} ({len(types)} fact types)", flush=True)
|
|
57
58
|
try:
|
|
58
59
|
srv.serve_forever()
|
|
59
60
|
except KeyboardInterrupt:
|
|
@@ -64,6 +65,38 @@ def cmd_serve(a) -> int:
|
|
|
64
65
|
return EXIT_OK
|
|
65
66
|
|
|
66
67
|
|
|
68
|
+
def cmd_serve(a) -> int:
|
|
69
|
+
from .store import Store
|
|
70
|
+
|
|
71
|
+
types = load_types(a.types)
|
|
72
|
+
store = Store(a.db, retention_days=a.retention_days)
|
|
73
|
+
if not store.members():
|
|
74
|
+
print("warning: no members yet - add one with `baton member add <name> --db ...`", file=sys.stderr)
|
|
75
|
+
return _run_server(store, types, a.host, a.port, a.tls_cert, a.tls_key)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def cmd_quickstart(a) -> int:
|
|
79
|
+
from . import quickstart as qs
|
|
80
|
+
from .store import Store
|
|
81
|
+
|
|
82
|
+
qs.check_host(a.host)
|
|
83
|
+
directory = Path(a.data_dir) if a.data_dir else qs.data_dir()
|
|
84
|
+
name = qs.member_name()
|
|
85
|
+
db, token = qs.setup(directory, name)
|
|
86
|
+
url = f"http://{a.host}:{a.port}"
|
|
87
|
+
print(f"baton quickstart: member {name!r}, database {db}")
|
|
88
|
+
print("Run these in the shell where your agent runs:\n")
|
|
89
|
+
print(qs.env_lines(url, token))
|
|
90
|
+
print("Then try: baton types (Ctrl+C stops the server; rerun to print this again)\n", flush=True)
|
|
91
|
+
return _run_server(Store(str(db)), load_types(None), a.host, a.port)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def cmd_mcp(a) -> int:
|
|
95
|
+
from .mcp import run
|
|
96
|
+
|
|
97
|
+
return run()
|
|
98
|
+
|
|
99
|
+
|
|
67
100
|
def cmd_member(a) -> int:
|
|
68
101
|
from .store import Store
|
|
69
102
|
|
|
@@ -165,6 +198,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
165
198
|
s.add_argument("--retention-days", type=int, default=30)
|
|
166
199
|
s.set_defaults(fn=cmd_serve)
|
|
167
200
|
|
|
201
|
+
s = sub.add_parser("quickstart", help="local trial: create a database and token, serve on 127.0.0.1")
|
|
202
|
+
s.add_argument("--port", type=int, default=8765)
|
|
203
|
+
s.add_argument("--host", default="127.0.0.1", help="loopback only")
|
|
204
|
+
s.add_argument("--data-dir", help="default: per-user data dir (or BATON_DATA_DIR)")
|
|
205
|
+
s.set_defaults(fn=cmd_quickstart)
|
|
206
|
+
|
|
168
207
|
s = sub.add_parser("member", help="manage member tokens (run on the server)")
|
|
169
208
|
s.add_argument("action", choices=["add", "remove", "list"])
|
|
170
209
|
s.add_argument("name", nargs="?")
|
|
@@ -202,6 +241,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
202
241
|
s.add_argument("--types-file")
|
|
203
242
|
s.add_argument("--json", action="store_true")
|
|
204
243
|
s.set_defaults(fn=cmd_types)
|
|
244
|
+
|
|
245
|
+
s = sub.add_parser("mcp", help="run an MCP stdio server exposing baton tools")
|
|
246
|
+
s.set_defaults(fn=cmd_mcp)
|
|
205
247
|
return p
|
|
206
248
|
|
|
207
249
|
|