ai-agent-bridge 0.1.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.
Files changed (28) hide show
  1. ai_agent_bridge-0.1.0/.dockerignore +7 -0
  2. ai_agent_bridge-0.1.0/.env.example +23 -0
  3. ai_agent_bridge-0.1.0/.gitignore +210 -0
  4. ai_agent_bridge-0.1.0/Dockerfile +38 -0
  5. ai_agent_bridge-0.1.0/LICENSE +21 -0
  6. ai_agent_bridge-0.1.0/PKG-INFO +247 -0
  7. ai_agent_bridge-0.1.0/README.md +223 -0
  8. ai_agent_bridge-0.1.0/docker-compose.yml +21 -0
  9. ai_agent_bridge-0.1.0/pyproject.toml +47 -0
  10. ai_agent_bridge-0.1.0/src/agent_bridge/__init__.py +91 -0
  11. ai_agent_bridge-0.1.0/src/agent_bridge/agents/__init__.py +0 -0
  12. ai_agent_bridge-0.1.0/src/agent_bridge/agents/claude/__init__.py +0 -0
  13. ai_agent_bridge-0.1.0/src/agent_bridge/agents/claude/config.py +51 -0
  14. ai_agent_bridge-0.1.0/src/agent_bridge/agents/claude/controller.py +167 -0
  15. ai_agent_bridge-0.1.0/src/agent_bridge/agents/claude/events.py +175 -0
  16. ai_agent_bridge-0.1.0/src/agent_bridge/bridge.py +40 -0
  17. ai_agent_bridge-0.1.0/src/agent_bridge/config.py +34 -0
  18. ai_agent_bridge-0.1.0/src/agent_bridge/events.py +32 -0
  19. ai_agent_bridge-0.1.0/src/agent_bridge/platforms/__init__.py +0 -0
  20. ai_agent_bridge-0.1.0/src/agent_bridge/platforms/slack/__init__.py +0 -0
  21. ai_agent_bridge-0.1.0/src/agent_bridge/platforms/slack/adapter.py +235 -0
  22. ai_agent_bridge-0.1.0/src/agent_bridge/platforms/slack/config.py +24 -0
  23. ai_agent_bridge-0.1.0/src/agent_bridge/protocols.py +35 -0
  24. ai_agent_bridge-0.1.0/src/agent_bridge/session.py +127 -0
  25. ai_agent_bridge-0.1.0/tests/__init__.py +0 -0
  26. ai_agent_bridge-0.1.0/tests/test_events.py +259 -0
  27. ai_agent_bridge-0.1.0/tests/test_session.py +214 -0
  28. ai_agent_bridge-0.1.0/uv.lock +658 -0
@@ -0,0 +1,7 @@
1
+ .git
2
+ .venv
3
+ .env
4
+ .pytest_cache
5
+ __pycache__
6
+ *.pyc
7
+ sessions.json
@@ -0,0 +1,23 @@
1
+ # Slack Bot Token (xoxb-...)
2
+ SLACK_BOT_TOKEN=
3
+
4
+ # Slack App-Level Token for Socket Mode (xapp-...)
5
+ SLACK_APP_TOKEN=
6
+
7
+ # Anthropic API Key (required for Docker / non-interactive environments)
8
+ ANTHROPIC_API_KEY=
9
+
10
+ # Directory where Claude Code will operate
11
+ CLAUDE_WORK_DIR=.
12
+
13
+ # Claude Code permission mode: acceptEdits, dangerously-skip-permissions, default, plan
14
+ CLAUDE_PERMISSION_MODE=acceptEdits
15
+
16
+ # Path to session store JSON file
17
+ SESSION_STORE_PATH=./sessions.json
18
+
19
+ # Session TTL in hours (sessions inactive longer than this are expired)
20
+ SESSION_TTL_HOURS=72
21
+
22
+ # Timeout for a single Claude invocation in seconds
23
+ CLAUDE_TIMEOUT_SECONDS=300
@@ -0,0 +1,210 @@
1
+ # Agent Bridge runtime data
2
+ sessions.json
3
+
4
+ # Byte-compiled / optimized / DLL files
5
+ __pycache__/
6
+ *.py[codz]
7
+ *$py.class
8
+
9
+ # C extensions
10
+ *.so
11
+
12
+ # Distribution / packaging
13
+ .Python
14
+ build/
15
+ develop-eggs/
16
+ dist/
17
+ downloads/
18
+ eggs/
19
+ .eggs/
20
+ lib/
21
+ lib64/
22
+ parts/
23
+ sdist/
24
+ var/
25
+ wheels/
26
+ share/python-wheels/
27
+ *.egg-info/
28
+ .installed.cfg
29
+ *.egg
30
+ MANIFEST
31
+
32
+ # PyInstaller
33
+ # Usually these files are written by a python script from a template
34
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
35
+ *.manifest
36
+ *.spec
37
+
38
+ # Installer logs
39
+ pip-log.txt
40
+ pip-delete-this-directory.txt
41
+
42
+ # Unit test / coverage reports
43
+ htmlcov/
44
+ .tox/
45
+ .nox/
46
+ .coverage
47
+ .coverage.*
48
+ .cache
49
+ nosetests.xml
50
+ coverage.xml
51
+ *.cover
52
+ *.py.cover
53
+ .hypothesis/
54
+ .pytest_cache/
55
+ cover/
56
+
57
+ # Translations
58
+ *.mo
59
+ *.pot
60
+
61
+ # Django stuff:
62
+ *.log
63
+ local_settings.py
64
+ db.sqlite3
65
+ db.sqlite3-journal
66
+
67
+ # Flask stuff:
68
+ instance/
69
+ .webassets-cache
70
+
71
+ # Scrapy stuff:
72
+ .scrapy
73
+
74
+ # Sphinx documentation
75
+ docs/_build/
76
+
77
+ # PyBuilder
78
+ .pybuilder/
79
+ target/
80
+
81
+ # Jupyter Notebook
82
+ .ipynb_checkpoints
83
+
84
+ # IPython
85
+ profile_default/
86
+ ipython_config.py
87
+
88
+ # pyenv
89
+ # For a library or package, you might want to ignore these files since the code is
90
+ # intended to run in multiple environments; otherwise, check them in:
91
+ # .python-version
92
+
93
+ # pipenv
94
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
95
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
96
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
97
+ # install all needed dependencies.
98
+ #Pipfile.lock
99
+
100
+ # UV
101
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
102
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
103
+ # commonly ignored for libraries.
104
+ #uv.lock
105
+
106
+ # poetry
107
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
108
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
109
+ # commonly ignored for libraries.
110
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
111
+ #poetry.lock
112
+ #poetry.toml
113
+
114
+ # pdm
115
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
116
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
117
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
118
+ #pdm.lock
119
+ #pdm.toml
120
+ .pdm-python
121
+ .pdm-build/
122
+
123
+ # pixi
124
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
125
+ #pixi.lock
126
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
127
+ # in the .venv directory. It is recommended not to include this directory in version control.
128
+ .pixi
129
+
130
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
131
+ __pypackages__/
132
+
133
+ # Celery stuff
134
+ celerybeat-schedule
135
+ celerybeat.pid
136
+
137
+ # SageMath parsed files
138
+ *.sage.py
139
+
140
+ # Environments
141
+ .env
142
+ .envrc
143
+ .venv
144
+ env/
145
+ venv/
146
+ ENV/
147
+ env.bak/
148
+ venv.bak/
149
+
150
+ # Spyder project settings
151
+ .spyderproject
152
+ .spyproject
153
+
154
+ # Rope project settings
155
+ .ropeproject
156
+
157
+ # mkdocs documentation
158
+ /site
159
+
160
+ # mypy
161
+ .mypy_cache/
162
+ .dmypy.json
163
+ dmypy.json
164
+
165
+ # Pyre type checker
166
+ .pyre/
167
+
168
+ # pytype static type analyzer
169
+ .pytype/
170
+
171
+ # Cython debug symbols
172
+ cython_debug/
173
+
174
+ # PyCharm
175
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
176
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
177
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
178
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
179
+ #.idea/
180
+
181
+ # Abstra
182
+ # Abstra is an AI-powered process automation framework.
183
+ # Ignore directories containing user credentials, local state, and settings.
184
+ # Learn more at https://abstra.io/docs
185
+ .abstra/
186
+
187
+ # Visual Studio Code
188
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
189
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
190
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
191
+ # you could uncomment the following to ignore the entire vscode folder
192
+ # .vscode/
193
+
194
+ # Ruff stuff:
195
+ .ruff_cache/
196
+
197
+ # PyPI configuration file
198
+ .pypirc
199
+
200
+ # Cursor
201
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
202
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
203
+ # refer to https://docs.cursor.com/context/ignore-files
204
+ .cursorignore
205
+ .cursorindexingignore
206
+
207
+ # Marimo
208
+ marimo/_static/
209
+ marimo/_lsp/
210
+ __marimo__/
@@ -0,0 +1,38 @@
1
+ FROM python:3.12-slim
2
+
3
+ # Install Node.js (required for Claude Code CLI)
4
+ RUN apt-get update && \
5
+ apt-get install -y --no-install-recommends curl git && \
6
+ curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \
7
+ apt-get install -y --no-install-recommends nodejs && \
8
+ apt-get clean && rm -rf /var/lib/apt/lists/*
9
+
10
+ # Install Claude Code CLI globally
11
+ RUN npm install -g @anthropic-ai/claude-code
12
+
13
+ # Install uv
14
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
15
+
16
+ # Set up workspace
17
+ WORKDIR /app
18
+
19
+ # Install dependencies first (cache layer)
20
+ COPY pyproject.toml uv.lock ./
21
+ RUN uv sync --frozen --no-dev --no-install-project
22
+
23
+ # Copy source code (tests excluded from production image)
24
+ COPY src/ src/
25
+ RUN uv sync --frozen --no-dev
26
+
27
+ # Claude Code work directory (mount your project here)
28
+ RUN mkdir -p /workspace
29
+ VOLUME /workspace
30
+
31
+ # Session store persistence
32
+ VOLUME /app/data
33
+
34
+ ENV CLAUDE_WORK_DIR=/workspace \
35
+ SESSION_STORE_PATH=/app/data/sessions.json \
36
+ CLAUDE_PERMISSION_MODE=acceptEdits
37
+
38
+ CMD ["uv", "run", "agent-bridge"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 htkuan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,247 @@
1
+ Metadata-Version: 2.4
2
+ Name: ai-agent-bridge
3
+ Version: 0.1.0
4
+ Summary: Modular bridge between chat platforms and AI agents
5
+ Project-URL: Repository, https://github.com/htkuan/agent-bridge
6
+ Author: htkuan
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Topic :: Communications :: Chat
14
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
15
+ Requires-Python: >=3.12
16
+ Requires-Dist: python-dotenv>=1.0.0
17
+ Provides-Extra: all
18
+ Requires-Dist: aiohttp>=3.9.0; extra == 'all'
19
+ Requires-Dist: slack-bolt>=1.21.0; extra == 'all'
20
+ Provides-Extra: slack
21
+ Requires-Dist: aiohttp>=3.9.0; extra == 'slack'
22
+ Requires-Dist: slack-bolt>=1.21.0; extra == 'slack'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # Agent Bridge
26
+
27
+ Modular bridge service that connects **chat platforms** to **AI agents**. The architecture cleanly separates platform concerns from agent concerns, making it easy to add new platforms or agents.
28
+
29
+ Currently supports **Slack** as the chat platform and **Claude Code** as the AI agent backend.
30
+
31
+ ## Architecture
32
+
33
+ ```
34
+ ┌─────────────────────────┐
35
+ │ Platform (Slack) │ Defines session semantics (thread = session)
36
+ │ platforms/slack/ │ Manages per-session locking & flow control
37
+ │ - adapter.py │ Renders agent events (stream updates, final reply)
38
+ │ - config.py │
39
+ └──────────┬──────────────┘
40
+ │ session_key, text, context
41
+
42
+ ┌─────────────────────────┐
43
+ │ Bridge │ Pure routing — no platform or agent knowledge
44
+ │ bridge.py │ session_key → session_id (via SessionManager)
45
+ │ session.py │ Forwards to agent, yields BridgeEvents back
46
+ │ events.py │ TextDelta | StatusUpdate | Completion
47
+ │ protocols.py │ AgentController + PlatformAdapter protocols
48
+ └──────────┬──────────────┘
49
+ │ session_id, prompt, is_new, context
50
+
51
+ ┌─────────────────────────┐
52
+ │ Agent (Claude Code) │ Purely invoked: load session + input → output
53
+ │ agents/claude/ │ Translates Claude stream-json → BridgeEvents
54
+ │ - controller.py │ Does not define sessions or care about rendering
55
+ │ - events.py │
56
+ │ - config.py │
57
+ └─────────────────────────┘
58
+ ```
59
+
60
+ ### Design Principles
61
+
62
+ **Platform defines session semantics.** A Slack thread is a session. A Discord channel might be a session. This is platform knowledge — the bridge and agent don't care how sessions are defined.
63
+
64
+ **Agent is purely invoked.** It receives `(session_id, prompt, is_new, context)`, loads the session, executes, and yields events. It doesn't know where the session came from or how results will be rendered.
65
+
66
+ **Bridge is pure routing.** It resolves session keys to session IDs and forwards requests/events. No platform-specific or agent-specific logic.
67
+
68
+ ### Generic Event Model
69
+
70
+ Platforms consume three event types — the common language between any agent and any platform:
71
+
72
+ | Event | Description |
73
+ |-------|-------------|
74
+ | `TextDelta` | Incremental text from the agent |
75
+ | `StatusUpdate` | Agent is performing an action (tool use, thinking, etc.) |
76
+ | `Completion` | Agent finished responding (with cost, duration, error status) |
77
+
78
+ Agent-internal events (init, thinking, tool results) are translated to these generic types within each agent module.
79
+
80
+ ### Data Flow
81
+
82
+ 1. User sends a message in Slack (via `@mention` in channel or direct message)
83
+ 2. **Slack Adapter** receives the event, constructs a session key (`slack:{channel}:{thread_ts}`)
84
+ 3. **Slack Adapter** acquires per-session lock (prevents concurrent processing)
85
+ 4. **Bridge** resolves session key → session ID via **SessionManager**
86
+ 5. **Agent (Claude Controller)** spawns `claude -p` with the session, yields `BridgeEvent`s
87
+ 6. **Slack Adapter** renders events as real-time message updates (throttled to avoid rate limits)
88
+
89
+ ### Session Management
90
+
91
+ - Each Slack thread maps to one agent session (defined by the platform)
92
+ - The bridge stores the mapping: `session_key → {session_id, created_at, last_used}`
93
+ - Mappings are persisted in a JSON file
94
+ - Sessions have a configurable TTL (default 72 hours) — expired sessions are automatically purged
95
+
96
+ ## Tech Stack
97
+
98
+ | Component | Choice | Reason |
99
+ |-----------|--------|--------|
100
+ | Language | Python 3.12+ | Type union syntax (`X \| Y`), `match` statements, modern asyncio |
101
+ | Package manager | uv | Fast, supports pyproject.toml natively |
102
+ | Slack SDK | [slack-bolt](https://github.com/slackapi/bolt-python) | Official Slack SDK, async support, Socket Mode |
103
+ | Async HTTP | aiohttp | Required by slack-bolt for async Socket Mode |
104
+ | Env config | python-dotenv | Load `.env` files |
105
+ | Testing | pytest + pytest-asyncio | Standard Python testing |
106
+ | Claude CLI | `claude -p` with `--output-format stream-json` | Non-interactive mode with real-time streaming |
107
+
108
+ ## Project Structure
109
+
110
+ ```
111
+ agent-bridge/
112
+ ├── pyproject.toml
113
+ ├── .env.example
114
+ ├── src/
115
+ │ └── agent_bridge/
116
+ │ ├── __init__.py # Entry point: wires platform + bridge + agent
117
+ │ ├── config.py # BridgeConfig (session store, TTL)
118
+ │ ├── bridge.py # Pure routing: session resolve → agent call
119
+ │ ├── events.py # TextDelta, StatusUpdate, Completion
120
+ │ ├── session.py # SessionManager (key → session_id mapping)
121
+ │ ├── protocols.py # AgentController + PlatformAdapter protocols
122
+ │ ├── agents/
123
+ │ │ └── claude/
124
+ │ │ ├── config.py # ClaudeConfig (work_dir, permissions, timeout)
125
+ │ │ ├── controller.py # Claude Code subprocess controller
126
+ │ │ └── events.py # Claude stream-json parser + BridgeEvent converter
127
+ │ └── platforms/
128
+ │ └── slack/
129
+ │ ├── config.py # SlackConfig (bot_token, app_token)
130
+ │ └── adapter.py # Slack adapter (session def, locking, rendering)
131
+ └── tests/
132
+ ├── test_events.py # Claude event parsing + BridgeEvent conversion
133
+ └── test_session.py # Session manager tests
134
+ ```
135
+
136
+ ## Setup
137
+
138
+ ### Prerequisites
139
+
140
+ - Python 3.12+
141
+ - [uv](https://docs.astral.sh/uv/)
142
+ - [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated
143
+
144
+ ### Install
145
+
146
+ ```bash
147
+ git clone <repo-url>
148
+ cd agent-bridge
149
+ uv sync
150
+ ```
151
+
152
+ ### Slack App Configuration
153
+
154
+ 1. Create a Slack App at [api.slack.com/apps](https://api.slack.com/apps)
155
+ 2. Enable **Socket Mode** and generate an App-Level Token (`xapp-...`)
156
+ 3. Add the following **Bot Token Scopes** under OAuth & Permissions:
157
+ - `app_mentions:read` — receive @mention events
158
+ - `chat:write` — send and update messages
159
+ - `im:history` — read DM messages
160
+ - `im:read` — access DM channels
161
+ 4. Subscribe to these **Events** under Event Subscriptions:
162
+ - `app_mention`
163
+ - `message.im`
164
+ 5. Install the app to your workspace and copy the **Bot User OAuth Token** (`xoxb-...`)
165
+
166
+ ### Environment Variables
167
+
168
+ ```bash
169
+ cp .env.example .env
170
+ ```
171
+
172
+ Edit `.env`:
173
+
174
+ ```bash
175
+ SLACK_BOT_TOKEN=xoxb-your-bot-token
176
+ SLACK_APP_TOKEN=xapp-your-app-level-token
177
+ CLAUDE_WORK_DIR=/path/to/your/project
178
+ CLAUDE_PERMISSION_MODE=acceptEdits
179
+ SESSION_STORE_PATH=./sessions.json
180
+ SESSION_TTL_HOURS=72
181
+ ```
182
+
183
+ | Variable | Required | Default | Description |
184
+ |----------|----------|---------|-------------|
185
+ | `SLACK_BOT_TOKEN` | Yes | — | Slack Bot User OAuth Token |
186
+ | `SLACK_APP_TOKEN` | Yes | — | Slack App-Level Token (Socket Mode) |
187
+ | `CLAUDE_WORK_DIR` | No | `.` | Working directory for Claude Code |
188
+ | `CLAUDE_PERMISSION_MODE` | No | `acceptEdits` | Claude permission mode |
189
+ | `SESSION_STORE_PATH` | No | `./sessions.json` | Path to session mapping file |
190
+ | `SESSION_TTL_HOURS` | No | `72` | Session TTL in hours |
191
+
192
+ ### Run (Local)
193
+
194
+ ```bash
195
+ uv run agent-bridge
196
+ ```
197
+
198
+ ### Run (Docker)
199
+
200
+ ```bash
201
+ cp .env.example .env
202
+ # Edit .env with your tokens (including ANTHROPIC_API_KEY for Docker)
203
+
204
+ docker compose up --build
205
+ ```
206
+
207
+ ### Test
208
+
209
+ ```bash
210
+ uv run pytest tests/ -v
211
+ ```
212
+
213
+ ## Usage
214
+
215
+ - **Channel**: Mention the bot — `@AgentBridge help me refactor this function`
216
+ - **DM**: Send a direct message — the bot responds in the same conversation
217
+ - **Thread continuity**: Reply in the same Slack thread to continue the agent session
218
+
219
+ ## Extending
220
+
221
+ ### Adding a new agent
222
+
223
+ Create `agents/<name>/` with `config.py`, `controller.py`, `events.py`. Implement the `AgentController` protocol — your `run()` method yields `BridgeEvent`s. Wire it up in `__init__.py`.
224
+
225
+ ### Adding a new platform
226
+
227
+ Create `platforms/<name>/` with `config.py`, `adapter.py`. Define your own session key logic (e.g., `discord:{guild}:{channel}`), manage per-session locking, consume `BridgeEvent`s from `bridge.handle_message()`. Wire it up in `__init__.py`.
228
+
229
+ Neither change requires modifying the bridge, the other agent, or the other platform.
230
+
231
+ ## Design Decisions
232
+
233
+ ### One-shot per message (vs. long-running process)
234
+
235
+ Each user message spawns a new `claude -p` process that exits after completion. Session continuity is handled by Claude Code's built-in `--resume` flag.
236
+
237
+ **Why**: Simpler process lifecycle, no idle resource consumption, graceful handling of crashes.
238
+
239
+ ### Per-session locking (platform-owned)
240
+
241
+ An `asyncio.Lock` per session key prevents concurrent agent processes for the same session. This is managed by the platform adapter, not the bridge, because locking strategy may vary by platform.
242
+
243
+ ### Throttled Slack updates
244
+
245
+ Slack message updates are throttled to 1.5-second intervals during streaming.
246
+
247
+ **Why**: Slack's API rate limits are ~1 request/second per method.