@agentdeploymentco/bishop 0.5.0 → 0.5.2

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/README.md CHANGED
@@ -5,1017 +5,19 @@ Bishop puts an agent where your team already works.
5
5
  Point it at a directory an agent already works in, and the agent becomes something your team can talk to: a Slack thread, an email thread, a Teams conversation, or all of them at once. Bishop handles the connection and the mapping between threads and agent sessions.
6
6
 
7
7
  ```
8
+ $ npm install -g @agentdeploymentco/bishop
8
9
  $ cd path/to/agent
9
10
  $ bishop slack setup
10
11
  $ bishop
11
12
  ```
12
13
 
13
- Bishop is an agent gateway: it sits between the places people work and the agents they work with. Today that is Slack, Gmail and Microsoft Teams, one agent, in a single process.
14
-
15
- [design/glossary.md](design/glossary.md) is what Bishop's own words mean, if a term below is unfamiliar.
16
-
17
- ## Requirements
18
-
19
- - Node 24 or newer
20
- - A directory the agent works in, meaning `claude` or `codex` runs there and does something useful
21
- - At least one interface. For Slack: the [`slack` CLI](https://docs.slack.dev/tools/slack-cli/), logged in with `slack auth login`, unless you set the app up by hand (see [Without the Slack CLI](#without-the-slack-cli)), plus permission to install an app in your workspace. For email: a Google Workspace account, a Google Cloud project, and a Workspace user for the agent. For Teams: the [Teams Developer CLI](https://microsoft.github.io/teams-sdk/), logged in with `teams login`, unless an administrator creates the app for you, plus a way for Teams to reach this host over HTTPS (see [Microsoft Teams](#microsoft-teams)). Reading or sending a file in a Teams channel also wants the [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli), which setup uses for the one grant that is not in the Teams manifest.
22
-
23
- ## Install
24
-
25
- ```
26
- $ npm install -g @agentdeploymentco/bishop
27
- ```
28
-
29
- That puts `bishop` on your PATH. Bishop always treats the current directory as the agent, so run it from the agent's directory. [In a container](#in-a-container) is the other way to run it.
30
-
31
- ### From source
32
-
33
- The published package is minified and carries no source maps. If you work on Bishop, and so can read its repository, build it instead:
34
-
35
- ```
36
- $ git clone https://github.com/Agent-Deployment-Co/bishop.git
37
- $ cd bishop
38
- $ npm install
39
- $ npm run build
40
- $ npm link # optional, puts `bishop` on your PATH
41
- ```
42
-
43
- Without `npm link`, run it as `node /path/to/bishop/dist/cli.js`.
44
-
45
- ## Set up an agent
46
-
47
- From the agent's directory:
48
-
49
- ```
50
- $ bishop slack setup
51
- ```
52
-
53
- It asks for an app name, a display name, and a description, defaulting the name to the directory name. Pass them as flags to skip the questions:
54
-
55
- ```
56
- $ bishop slack setup --name salesforce-guy \
57
- --display-name "Salesforce Guy" \
58
- --description "Answers Salesforce admin questions"
59
- ```
60
-
61
- `--no-prompt` takes the defaults instead of asking. Bishop assumes it when there's no terminal, so this works in a script.
62
-
63
- Setup creates the Slack app, installs it, and writes two files:
64
-
65
- ```
66
- .bishop/config.json configuration, commit this
67
- .env credentials, gitignored for you
68
- ```
69
-
70
- It also adds `bishop.db*` to `.gitignore`. `bishop` creates the database on its first run to hold the thread-to-session mapping, and that pattern covers its write-ahead log and the copies described under [Upgrading](#upgrading).
71
-
72
- Then start it:
73
-
74
- ```
75
- $ bishop
76
- ```
77
-
78
- Invite the bot to a channel, or DM it directly.
79
-
80
- ### Without the Slack CLI
81
-
82
- If you can't install the CLI, or somebody else owns app creation in your workspace, Bishop can adopt an app you set up by hand.
83
-
84
- First get the manifest, so the scopes and events are right by construction:
85
-
86
- ```
87
- $ bishop slack manifest > manifest.json
88
- ```
89
-
90
- Create the app in Slack from that file (**Create New App**, then **From a manifest**), install it to the workspace, then collect two tokens from App Settings: the bot token (`xoxb-`) under OAuth & Permissions, and an app-level token (`xapp-`) with `connections:write` under Basic Information.
91
-
92
- ```
93
- $ bishop slack setup --manual
94
- ```
95
-
96
- It asks for both tokens, checks them against Slack, and builds the config from what Slack reports. You don't have to type the app ID or name: `auth.test` returns them.
97
-
98
- One thing it can't check is which events the app subscribes to. If the agent never answers anything, that's the first place to look.
99
-
100
- ### Email
101
-
102
- Gmail is the second interface. It needs a Google Cloud project, and the agent needs its own Workspace user, say `salesforce-guy@example.com`. That address is what people write to, and its mailbox is what Bishop reads. Don't point this at a person's account.
103
-
104
- Then create an OAuth client, once:
105
-
106
- 1. In the [Google Cloud console](https://console.cloud.google.com/), pick or create a project and **enable the Gmail API**.
107
- 2. Configure the OAuth consent screen with user type **Internal**. This is what lets you skip Google's app verification.
108
- 3. Create an OAuth client ID of type **Desktop app**.
109
- 4. Put both values in `.env`:
110
-
111
- ```
112
- BISHOP_GMAIL_CLIENT_ID=...
113
- BISHOP_GMAIL_CLIENT_SECRET=...
114
- ```
115
-
116
- Then authorize the mailbox:
117
-
118
- ```
119
- $ bishop gmail setup
120
- ```
121
-
122
- A browser opens. Sign in **as the agent's Workspace user**, not as yourself, and approve. Bishop catches the redirect, writes `BISHOP_GMAIL_REFRESH_TOKEN` to `.env`, and tells you which mailbox it reached.
123
-
124
- Re-running it later checks the credentials instead of authorizing again, so it doubles as a health check in a deploy script. It exits non-zero when the mailbox can't be reached.
125
-
126
- **Set `gmail.allow` before pointing anyone at it.** Without it, every address that can reach the mailbox can use the agent, and a Workspace mailbox can be reached from anywhere.
127
-
128
- ```json
129
- { "gmail": { "allow": ["you@example.com", "@example.com"] } }
130
- ```
131
-
132
- #### Moving it to a server
133
-
134
- The refresh token isn't tied to the machine you authorized on. Copy three lines to the server's `.env` and start Bishop there:
135
-
136
- ```
137
- BISHOP_GMAIL_CLIENT_ID=...
138
- BISHOP_GMAIL_CLIENT_SECRET=...
139
- BISHOP_GMAIL_REFRESH_TOKEN=...
140
- ```
141
-
142
- To set it up on the server directly instead, run `bishop gmail setup` there. It notices it's over SSH, prints a URL instead of opening one, and asks you to paste back where the browser ended up. The browser lands on a `http://127.0.0.1` address that fails to load, which is expected: the code is in the address bar, and pasting the whole address is enough. `--no-browser` forces that mode anywhere.
143
-
144
- #### When it stops working
145
-
146
- Google revokes Gmail refresh tokens when the mailbox's password is reset. The agent goes quiet, the log says the token is no longer valid, and `bishop gmail setup` fixes it by authorizing again.
147
-
148
- If that isn't acceptable, use domain-wide delegation instead. Nothing about it expires and no person is ever needed again, but it costs more to set up: a service account key, which most organizations now block by default, plus a Workspace super admin to grant the delegation. Set `BISHOP_GMAIL_USER` and `BISHOP_GMAIL_SERVICE_ACCOUNT` instead of the three OAuth variables, and run `bishop gmail setup` to check it. It prints the exact client ID and scope to paste into the admin console when the grant is missing.
149
-
150
- Bishop runs whichever interfaces have credentials. Set up more than one and a single process answers all of them at once, with separate threads and separate sessions.
151
-
152
- ### Microsoft Teams
153
-
154
- Teams needs a Teams app, and the app needs a URL to deliver to. Slack holds a socket open; Teams POSTs every message to an HTTPS endpoint Bishop serves. Nothing binds a port unless the Teams credentials are set.
155
-
156
- ```
157
- $ bishop teams setup --endpoint https://your-host.example/api/messages
158
- ```
159
-
160
- It creates the app through the [Teams Developer CLI](https://microsoft.github.io/teams-sdk/), writes the three `BISHOP_TEAMS_*` credentials to `.env`, and records the app in `.bishop/config.json`. It prints a link to install the app in Teams. You can leave `--endpoint` off and set it later.
161
-
162
- It also asks the app for `ChannelMessage.Read.Group` and `ChatMessage.Read.Chat`, which are what let the agent hear a reply that does not @-mention it, `ChannelSettings.Read.Group` and `TeamSettings.Read.Group`, which are what tell the agent who can read a channel, and turns on `supportsFiles`, which is what lets anyone attach a file in a chat with it. `teams app create` takes no flags for any of them, so each is a further write, and a Developer Portal that refuses one costs that setting rather than the setup: it says what is missing and what to run. Run setup again on a configured agent and it reports whatever is still missing.
163
-
164
- One grant is not in the Teams manifest, so setup makes it with the [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) instead. `Files.ReadWrite.All` on the app registration is what lets the agent read a file attached in a channel and send one there, and consenting it takes an administrator, so setup tries and prints the commands when it cannot: `az` missing, `az` logged out, signed in to another tenant, and a refusal all say which one happened. Install `az` and run `az login --allow-no-subscriptions` before setup if you can make the grant yourself. Nothing else needs `az`, and an agent that only works in direct chats never needs the grant.
165
-
166
- **Adding or changing those permissions on an app that is already installed means removing it and installing it again.** Teams asks for them at install time and at no other time, so an app updated in place keeps the consent it was installed with, whatever its manifest now says. Remove it from every team and chat it is in, then add it back. A new app is unaffected, since installing it is the first thing that happens to it.
167
-
168
- Install the CLI with the flags, then log in:
169
-
170
- ```
171
- $ npm install -g --allow-scripts=@azure/msal-node-extensions,keytar @microsoft/teams.cli
172
- $ teams login
173
- ```
174
-
175
- The CLI keeps its login in a native module that npm refuses to build by default, and falls back to holding the token in memory rather than reporting it. Installed without the flags it logs in, says it worked, and starts every later command logged out. Bishop says so when it sees that happen.
176
-
177
- **Run this on a workstation.** Over SSH the CLI has no browser to open and falls back to the device code flow, which Microsoft blocks by default in every tenant, so no login can succeed there. Set up on a laptop and copy the `BISHOP_TEAMS_*` lines to the server, or use the administrator path below, which needs no CLI at all. `bishop teams setup` and `bishop teams endpoint` both say this rather than let a login fail with an Entra error code.
178
-
179
- Run it again and it reports what's configured, including where the app currently points, and changes nothing. If only half of setup is on disk, an app with no credentials or credentials with no app, it says which half and stops rather than create a second app: a client secret is shown once, and an orphaned app is left behind in your tenant.
180
-
181
- #### The endpoint
182
-
183
- Bishop serves `/api/messages` on port 3978 by default, on loopback, so something has to carry the public HTTPS to it. A tunnel does that without opening a port on the host:
184
-
185
- ```
186
- $ cloudflared tunnel --url http://localhost:3978
187
- ```
188
-
189
- That gives you the URL to hand `bishop teams setup --endpoint`. An ephemeral tunnel hostname changes on every restart, and a Teams app pointed at the old one goes quiet with nothing in the log to say why, so use a stable hostname for anything but a trial.
190
-
191
- To terminate TLS in Bishop itself instead, give it a certificate:
192
-
193
- ```json
194
- {
195
- "teams": {
196
- "port": 443,
197
- "host": "0.0.0.0",
198
- "tls": { "cert": "/etc/bishop/fullchain.pem", "key": "/etc/bishop/privkey.pem" }
199
- }
200
- }
201
- ```
202
-
203
- **When the URL changes**, which is every time an ephemeral tunnel restarts:
204
-
205
- ```
206
- $ bishop teams endpoint https://new-host.example/api/messages
207
- ```
208
-
209
- That touches the app and nothing else. When the change rewrites the app's manifest, which moving to a different domain does, it says so: the app has to be reinstalled in Teams before it receives anything again.
210
-
211
- #### When somebody else owns the app
212
-
213
- In many tenants only an administrator can create it, and installing custom apps can be turned off tenant-wide. `bishop teams instructions` prints what to send them. When they send back a client ID, a client secret, and a tenant ID:
214
-
215
- ```
216
- $ bishop teams setup --manual
217
- ```
218
-
219
- It checks the three against Microsoft before saving them, using the same request Bishop makes on every turn, so a bad secret fails here instead of silently on the first message.
220
-
221
- #### Installing the app
222
-
223
- Last, install the app in Teams by opening the link setup printed. When this is blocked the link does nothing at all: no activity is delivered, so Bishop's log stays silent and a working endpoint looks broken.
224
-
225
- Sideloading has to be allowed twice over, and either setting blocks it alone. `bishop teams setup` names whichever one is in the way. Both live in the Teams admin center at `admin.teams.microsoft.com`, which is its own portal rather than a page of the Microsoft 365 admin center or Entra: "Let users interact with custom apps" under Teams apps, Manage apps, Org-wide app settings, and "Upload custom apps" in the app setup policy assigned to the account, under Teams apps, Setup policies. An administrator can skip the per-user policy by uploading the app package under Teams apps, Manage apps, which publishes it to the whole org and takes a few hours to appear.
226
-
227
- ### GitHub
228
-
229
- An agent that opens pull requests, reads issues, or pushes branches needs a GitHub credential of its own. Without one it borrows whatever login happens to be on the machine, and in a container there is nothing to borrow.
230
-
231
- ```
232
- $ bishop github setup
233
- ```
234
-
235
- It takes the token from the [`gh` CLI](https://github.com/cli/cli), offering to run `gh auth login` if you aren't logged in, verifies it against GitHub, and writes `GH_TOKEN` to `.env`. From there it reaches the agent's environment, and `gh` picks it up on its own.
236
-
237
- The report names the account the token acts as and the scopes it carries, and prints the `gh auth refresh` for any scope the agent needs that's missing. Re-running it checks the token instead of issuing another, so it doubles as a health check in a deploy script. It exits non-zero when GitHub won't accept the token.
238
-
239
- **A token from `gh auth login` acts as you, in every repository you can reach.** To limit the agent instead, create a [fine-grained personal access token](https://github.com/settings/personal-access-tokens) scoped to the repositories it should touch, put it in `.env` as `GH_TOKEN`, and run setup to check it. GitHub reports no scopes for a fine-grained token, so setup says what it can't tell you rather than guessing.
240
-
241
- git never reads `GH_TOKEN`, so `git push` still asks for a password until the credential helper is installed. Setup says so when it's missing:
242
-
243
- ```
244
- $ gh auth setup-git
245
- ```
246
-
247
- **`gh auth login` replaces whichever account the `gh` CLI is signed in as.** On a machine where you are signed in as yourself, `--temporary` runs the login in a `gh` configuration directory it creates and deletes, so your own login survives:
248
-
249
- ```
250
- $ bishop github setup --temporary
251
- ```
252
-
253
- The token still lands in `.env`. Because the temporary directory has no account in it, this always logs in rather than reusing one, and it needs a terminal. A `GH_TOKEN` already in `.env` is checked as usual and no login happens at all.
254
-
255
- For a deployment whose secrets live somewhere other than a `.env` file, `--print` writes the credential to stdout as JSON and nothing to disk, with the report and any prompting on stderr:
256
-
257
- ```
258
- $ bishop github setup --print
259
- {"GH_TOKEN":"gho_..."}
260
- ```
261
-
262
- `--no-prompt` never offers the login, and fails with the command to run instead. `--temporary` and `--print` combine: log in as the agent, pipe the token into a secret store, leave nothing behind.
263
-
264
- ## Pointing at a different agent
265
-
266
- By default Bishop treats the current directory as both its own home and the agent, so the files above land beside the agent. `--agent` separates the two:
267
-
268
- ```
269
- $ bishop --agent /srv/agents/salesforce-guy
270
- ```
271
-
272
- Bishop's config, credentials, and database stay in the directory you run from. The agent directory is only read, by the agent itself, so its `CLAUDE.md` and `.claude/settings.json` govern as always and nothing of Bishop's is written into it. **Bishop never updates a directory you point it at**, whether or not it's a git checkout: pull, branch, and edit it whenever you like. The exception to reading only is [worktree mode](#worktree-mode), where the agent creates worktrees under `.claude/worktrees/` in its own directory.
273
-
274
- This is how you swap the agent underneath a running deployment. Point Bishop at a checkout, a worktree, or a release directory, and change which one it points at without touching any Slack configuration or losing thread history.
275
-
276
- ### Pointing at a repository
277
-
278
- `--agent` also takes a git URL, which Bishop clones on startup:
279
-
280
- ```
281
- $ bishop --agent https://github.com/acme/salesforce-guy
282
- $ bishop --agent https://github.com/acme/salesforce-guy/tree/staging
283
- $ bishop --agent git@github.com:acme/salesforce-guy.git
284
- ```
285
-
286
- GitLab's `/-/tree/<branch>` form works too, as does any URL git can clone. A bare `owner/repo` is treated as a local directory, since nothing distinguishes it from a relative path. Bishop uses whatever git credentials the machine already has, so a private repo works if `git clone` works there.
287
-
288
- This is the other half of the split. Point at a directory and its state is yours: Bishop reads it, writes nothing into it, and you pull when you want to. The one thing it removes there is an old thread's worktree, see [Cleaning up after itself](#cleaning-up-after-itself). Point at a repository and Bishop owns the checkout instead, under `.bishop/agents/<owner>-<repo>`:
289
-
290
- ```
291
- .bishop/agents/acme-salesforce-guy/
292
- ├── mirror.git/ bare clone, the only thing a fetch touches
293
- └── snapshots/
294
- ├── current -> b4c5d6e what the next new thread starts on
295
- ├── a1b2c3d/ an older commit, still in use by a thread
296
- └── b4c5d6e/
297
- ```
298
-
299
- A snapshot is one commit, checked out and never rewritten. **A thread picks its snapshot on its first message and stays there for good**, so a conversation always sees one consistent set of files. Threads on the same commit share a snapshot, so a quiet week costs one directory.
300
-
301
- `current` is for you, not for Bishop. Nothing reads it while a thread runs; it records what the next new thread would start on.
302
-
303
- ### Picking up a push without restarting
304
-
305
- `--auto-update` checks the remote at the start of every new thread:
306
-
307
- ```
308
- $ bishop --agent https://github.com/acme/salesforce-guy --auto-update
309
- ```
310
-
311
- A new thread gets whatever is on the branch now. Threads already going keep their own snapshot, so nobody's conversation changes underneath them. Without the flag, Bishop checks once at startup and every thread that process handles starts on that commit.
312
-
313
- Threads waking together share one fetch. If the fetch fails, new threads start on the snapshot Bishop already has and the reason is logged, so an expired credential doesn't take the agent offline.
314
-
315
- It doesn't install anything. A commit that changes `package.json` gets a snapshot with no `node_modules`, and what to do about that is the agent's business. It also doesn't touch Bishop's own config, credentials, or Slack app, which are read from the directory you ran it from and still need a restart to change.
316
-
317
- ## Cleaning up after itself
318
-
319
- A finished conversation leaves things behind: the snapshot it was pinned to, the worktree and branch it edited in, the files people shared in it, and its rows in Bishop's database. A schedule leaves a set every time it fires, which for an hourly one is 24 a day.
320
-
321
- Bishop collects them itself, at startup and every six hours after, so a gateway left running for months doesn't fill a disk. Everything a thread left is kept for 30 days past its last turn:
322
-
323
- ```json
324
- {
325
- "gc": {
326
- "maxAgeDays": 30
327
- }
328
- }
329
- ```
330
-
331
- A snapshot a thread is still using is kept however old the commit is, and so is `current`. Nothing a live conversation is using is ever touched.
332
-
333
- **It deletes what it collects.** A worktree goes with its branch, including work the agent committed there and nobody merged, and a thread that comes back after that starts fresh on the current files. If a thread's work matters, merge it before it ages out, or raise `maxAgeDays`.
334
-
335
- **A collected thread stops being one Bishop is listening to.** In a Slack channel, mention the agent to pick it up again; a reply without a mention in a thread that old is ignored. DMs and email are unaffected.
336
-
337
- To collect on your own terms, with Bishop stopped or from cron:
338
-
339
- ```
340
- $ bishop gc
341
- $ bishop gc --max-age-days 7
342
- ```
343
-
344
- It reads the database and the disk and nothing else, so it needs no credentials.
345
-
346
- `bishop slack setup` and `bishop slack manifest` don't take `--agent`. They only deal with Bishop's own directory and the Slack app, and know nothing about any agent.
347
-
348
- ## Using it over email
349
-
350
- Set up a Gmail mailbox for the agent (see [Email](#email)) and the agent has an address. Anyone on its allow list emails it, and the reply arrives in the same thread.
351
-
352
- An email thread is an agent session, exactly as a Slack thread is. Reply to the agent's answer and it picks up where it left off.
353
-
354
- Email works differently enough to be worth stating:
355
-
356
- - **Nothing happens until the turn is over.** No streaming, no progress, no sign the agent is working. Then one plain-text reply arrives with everything it had to say. This makes a long turn fine, which it isn't in Slack.
357
- - **The reply goes to everyone on the thread.** So Bishop refuses a thread with anyone on it who isn't on the allow list. If you're on the list and copied someone who isn't, it tells you why. If you're not on the list, it sends nothing at all.
358
- - **Labels are the only progress you get.** Bishop marks each thread `Bishop/Working` while it runs, then `Bishop/Done`, `Bishop/Failed`, or `Bishop/Refused`. Open the mailbox to see what the agent is doing.
359
- - **Attachments come through.** Bishop downloads what was attached and hands the agent the path, the same as in Slack.
360
- - **No stop button.** Nothing in email can cancel a turn.
361
-
362
- Bishop never answers automated mail: autoresponders, mailing lists, bounces, its own address, or anything Gmail marked spam. It also refuses mail whose sender Gmail's own verdict doesn't clear, without writing back, since the from address is the one thing known to be untrustworthy there. That check reads DMARC rather than a bare SPF or DKIM pass, and only from the topmost `Authentication-Results` header, because a sender can write that header themselves and only Gmail's own copy means anything.
363
-
364
- Two rules are worth stating plainly, because both are about mail the agent must never send:
365
-
366
- **Bishop only ever writes to an address on the allow list.** Someone not on it gets no reply, not even one explaining why. The thread is labelled `Bishop/Refused` and the reason goes in the log. Without this, pointing the agent at a mailbox that receives ordinary mail means it answers every stranger who writes in.
367
-
368
- **Bishop never answers mail that arrived before you pointed it at the mailbox.** It records that moment the first time it connects and refuses anything older, whatever else happens to its polling state. Startup logs the cutoff as `answeringMailAfter`. Restarts still catch up on whatever arrived while it was down, because the cutoff is when it first attached, not when the process started. There is no setting that moves it earlier.
369
-
370
- ## Using it in Teams
371
-
372
- Install the app in Teams and message it directly, or @-mention it in a channel. A channel reply starts a thread and the agent follows that thread; a chat has no threads, so the whole chat is one session.
373
-
374
- Teams behaves much the way Slack does:
375
-
376
- - **A turn shows itself while it runs.** What the agent writes lands as it writes it, and a run of tool calls shares one message that is rewritten as the work moves and replaced by a count of them when the agent speaks again. A turn ends with a line saying what it cost, edited onto the last message. Send `` `! verbosity medium` `` to drop the tool calls in one conversation, or `` `! verbosity low` `` to leave the answer alone with no closing line, which is how Teams behaved before. `teams.verbosity` in `.bishop/config.json` sets where every conversation starts. There is still no typing indicator: Teams hides it while a stream is open and outside one it lapses in seconds.
377
- - **Commands work as they do in Slack.** Type the command in backticks, like `` `! model opus` ``, and Teams formats it as code. The whole message has to be the code: a command inside a sentence goes to the agent.
378
- - **Tables do not render.** Teams supports a narrower markdown than Slack. Bold, italics, links, code and lists work, and the agent is told to write a list where it would otherwise reach for a table.
379
- - **@-mention it once per thread, not once per message.** Teams delivers a channel or group-chat message to an app only when the app is @-mentioned. `bishop teams setup` asks for two resource-specific consent permissions that lift that, so once the agent has answered in a thread it hears the replies without being tagged again. Without them the agent still works and still has to be tagged every time.
380
- - **Changing those permissions means removing the app and installing it again.** Teams asks for them when an app is installed and at no other time. Updating an app that is already in a team or a chat does not ask, however the manifest changed, so it goes on hearing nothing but mentions until somebody removes it from every team and chat it is in and adds it back. This catches out anyone upgrading an agent that was already running in Teams.
381
- - **It can stay out of a conversation.** In a thread with several people talking, a message that did not name the agent is one it may leave alone, and it says nothing at all rather than answering. A message that names it is always answered, as is anything in a one-to-one chat.
382
- - **It catches up when it is pulled in partway.** @-mention it halfway down a channel thread or in a group chat that has been going a while, and its first answer there has read what was said before, the way it does in Slack. Only messages from people on the allow list are quoted to it; anyone else is counted and not quoted. Reading them back goes through Microsoft Graph on the same two permissions, so an app installed without them answers from the mention alone.
383
- - **Files come through in a direct chat, and images anywhere.** Attach a file in a chat with the agent and it gets the path, the same as in Slack; an image pasted into a message reaches it in any scope. Teams shows no attach button at all until the app's manifest asks for files, so `bishop teams setup` turns that on when it creates the app, and reports it when re-run against an app that predates this.
384
- - **A file in a channel needs one more grant.** Teams keeps an attached file off the message it delivers, so Bishop reads it back through Microsoft Graph, and a file the agent sends goes into the channel's SharePoint folder the same way. Both take `Files.ReadWrite.All` on the app registration; an app that already holds `Files.Read.All` can still read channel files but can't send them. `bishop teams setup` makes that grant with `az` when it can, and prints the commands for an administrator when it cannot. Re-run it and it says whether the grant is there, which it reads from the app's own token rather than from `az`. No Azure subscription is involved and the bot stays Teams-managed. Without the grant the agent is still told a file was attached and says it could not read it, rather than answering as though the message were empty, and a file it sends to a channel is left off the message.
385
- - **A file attached in a group chat may still not come through.** The channel path covers it if Teams describes it the same way, which is untested. A direct chat and a channel are the two that are known to work.
386
- - **Stop a turn with a command.** Teams has no stop button for Bishop to draw, so send `` `! stop` `` in the conversation instead. It stops the turn running there, which closes with a line saying how long it ran, and anything you sent while it was running is answered next.
387
-
388
- The allow list takes user principal names (`hayes@example.com`), `@example.com` for everyone at a domain, or an Entra object ID:
389
-
390
- ```json
391
- { "teams": { "allow": ["hayes@example.com", "@example.com"] } }
392
- ```
393
-
394
- Leave it out and anyone in the tenant who can reach the app can use the agent. Somebody not on the list is told so, rather than left with silence that looks like the agent being broken.
395
-
396
- ## Using it in Slack
397
-
398
- **DM the bot** and every message reaches the agent.
399
-
400
- **@-mention it in a channel** to start a thread. Bishop replies in that thread
401
- and follows the conversation there. It ignores everything else in the channel, including messages in threads it isn't part of.
402
-
403
- Each thread is its own agent session with its own history. Two threads are two independent conversations even with the same person, and a thread picks up where it left off, including after Bishop restarts.
404
-
405
- **A turn shows its work.** Slack marks the thread as working for as long as the agent is, however long that is, which is also the stop button. Everything the agent writes lands as its own message as it writes it, and the tools it calls in between share a card that says what it is doing now. When the run of tool calls ends, the card becomes a line saying how many it used. The turn closes with the answer and a line saying how long it took, or how long it ran before you stopped it.
406
-
407
- **Turn it down.** A thread showing every tool call is a thread that's hard to read when you already trust the agent. Send `` `! verbosity medium` `` to drop the tool card, or `` `! verbosity low` `` to leave only the answer. Send `` `! verbosity` `` to see where a thread stands, and `` `! verbosity default` `` to put it back. It applies to the turn that's running, so you can quiet one mid-flight. `slack.verbosity` in the config sets where every thread starts, and Slack always marks the thread as working whatever the level.
408
-
409
- **Run one thread on another model.** Send `` `! model opus` `` to move a thread onto a different model, `` `! model terra high` `` to set the model and the effort together, or `` `! model --effort high` `` to change how hard it thinks and leave the model alone. Send `` `! model` `` to see where a thread stands, and `default` in either position to put it back: `` `! model default default` ``. Names go to the harness as you type them, in its own naming, so `opus` or `gpt-5.1-codex-max` means whatever it means there; Bishop only checks that it looks like a model name. A name the harness doesn't know does what a bad `--model` does, which differs by harness and is described under `agent.model` below: Codex fails the turn, Claude says so in prose about a model and silently ignores an effort it doesn't recognize. `` `! model` `` is how you see what a thread is being run with, which is the only sign of a typo in the silent case. Unlike verbosity this takes effect on the thread's next turn, not the one running, and it lasts for the life of the thread.
410
-
411
- **Commands go to Bishop, not the agent.** Anything whose whole message is code opening with `! ` is a command: Bishop runs it, answers in the thread, and the agent is never told about either. `` `! help` `` lists them, and any of them takes `--help`. What follows the `!` has to look like a command name, so code and pasted output reach the agent as they always did.
412
-
413
- **It doesn't answer every message in a busy thread.** In a thread where more than one other person is talking, the agent can leave a message alone when it wasn't for the agent, the way anyone else in the thread would. Slack still shows it thinking for a moment, the way somebody starts typing and then doesn't send. It always answers a message that @-mentions it, and always answers in a thread where you're the only other person talking, so a DM and a thread between you and the agent are unchanged.
414
-
415
- **Write while it's working.** A message sent mid-turn gets an :hourglass_flowing_sand: and waits, rather than starting a second turn in the same conversation. When the turn ends, everything waiting goes to the agent as one prompt, with who wrote each one attached. Edit or delete a waiting message and the agent sees the change: what it gets is what the thread says at the moment it is sent.
416
-
417
- **Share files with it.** Attach a file to a message, with or without a caption, and the agent gets it: Bishop downloads it and hands over the path. Screenshots included, so "look at this" and an image works. There is no restriction on type; what the agent can do with a `.parquet` is up to the agent. Files over 100MB reach it as a name and a note saying they were too large, which `files.maxMb` changes.
418
-
419
- **It can open a file shared before it arrived.** Tag the agent into a thread that already has files in it and it is told what was shared, then downloads the ones it needs. Nothing is fetched on the way in, so tagging it into a thread with forty screenshots in it costs nothing.
420
-
421
- **Mentions work both ways.** The agent reads mentions of other people as names rather than as Slack's user ids, and when it writes `@name` Bishop turns it into a real mention that notifies them. Other apps count: an agent can mention the bot beside it in a channel. A name that matches nobody, or more than one account, stays as plain text. `@here`, `@channel`, and `@everyone` are never converted, so an agent can't wake a workspace.
422
-
423
- **It can talk to another agent, in a thread you started.** Pull two agents into the same thread and they can address each other, and the one you are talking to is told which messages came from another agent rather than a person. Neither can start the exchange: a bot's message is answered only in a thread that already has a session, so a bot can't DM the agent or open a thread with it. Apps that post under a name rather than an account, like an incoming webhook, are never answered at all. And an exchange between them is capped at three turns in a row by default, after which Bishop says it's holding off and waits for someone to say something. A turn another agent drew can't use Bishop's own tools either, so it can't leave a schedule behind. Set `slack.maxBotTurns` to change the cap, or to `0` to have the agent never answer another bot.
424
-
425
- In a DM, Slack shows a stop button for as long as the turn runs. Pressing it cancels the agent mid-turn. `` `! stop` `` does the same in any thread, and anything sent while the turn was running is answered next.
426
-
427
- Slack caps messages near 4000 characters, so Bishop splits longer answers. Slack also doesn't render tables and handles nested lists badly, so agent output written for a terminal will sometimes look wrong in a thread.
428
-
429
- ## Scheduled prompts
430
-
431
- Bishop can run a prompt on a clock and post the answer where you say, only when the agent has something to say.
432
-
433
- ```
434
- bishop schedules add \
435
- --name "CI check" \
436
- --cron "0 9 * * 1-5" \
437
- --to "#ops" \
438
- --prompt "Check CI on main. If nothing is broken, answer <no-output>."
439
- ```
440
-
441
- `--to` takes `#channel` or `@person` for Slack, or an email address for Gmail, and never an id: Bishop resolves it once, when you add the schedule, and shows it back the same way. Adding a Slack destination needs three scopes (`channels:read`, `groups:read`, `im:write`) that an app installed before this existed won't have, so run `bishop slack setup` again if it says so.
442
-
443
- ```
444
- bishop schedules list # everything scheduled here, in your own time zone
445
- bishop schedules disable <id> # stop it firing, keep it
446
- bishop schedules enable <id> # start it again, from now
447
- bishop schedules rm <id> # remove it
448
- ```
449
-
450
- What to expect:
451
-
452
- - **Silence is the normal outcome.** Bishop posts nothing when the agent answers `<no-output>` or says nothing at all. `NOTHING TO REPORT` still works, so a prompt written before this doesn't have to change. Tell the agent in the prompt what is worth reporting; that criterion is yours, not Bishop's.
453
- - **Nothing streams.** A scheduled turn is invisible while it runs, on Slack as much as on email, because Bishop can't unsay a report it turns out not to want. There is no way to stop one mid-turn.
454
- - **Every firing is a fresh session.** The agent doesn't remember the last one, so tell it to write down anything it needs to carry over.
455
- - **`--cron` is five fields**, read in `--tz` (defaults to this machine's zone), and no more often than every five minutes. `@daily` and friends work.
456
- - **Nothing fires while Bishop is stopped, and nothing catches up.** A firing missed while it was down is skipped, and the log says so. A firing whose previous run is still going is skipped too.
457
- - **A failure is never silent.** Bishop posts to the destination on the first failed run, records the reason in `schedules list`, and after five failures in a row turns the schedule off and says so. Re-enable it with `bishop schedules enable`.
458
- - **A schedule stops when its author loses access.** Bishop re-checks the allow list at every firing, so removing someone stops what they scheduled.
459
-
460
- Schedules live in `bishop.db`, not in a config file, so `bishop schedules list` is the only way to see what the agent will do tomorrow.
461
-
462
- ### Asking the agent for one
463
-
464
- You can also just ask, in a thread or an email:
465
-
466
- > @agent send me a report every morning at 9am in #ops, and stay quiet if there's nothing to say
467
-
468
- The agent writes the schedule itself, and Bishop wakes up and delivers it. Leave the destination out and it posts back into the conversation you asked in.
469
-
470
- Slack refuses a channel the app isn't in, Gmail refuses an address that isn't on `gmail.allow`, and you'll be told so in the thread rather than finding out at nine tomorrow. You can ask by email for something to land in Slack; it's the `#ops` that decides where it goes, not where you asked from.
471
-
472
- Two limits the command line doesn't have, both because the agent is picking rather than you:
473
-
474
- - **It won't start DMing someone who isn't on `slack.allow`**, and won't send mail at all unless `gmail.allow` is set. Posting in a channel is fine, since the app had to be invited there. Use `bishop schedules add` to aim somewhere the recipient hasn't opted into.
475
- - **Twenty schedules per person.** Ask for a twenty-first and it'll tell you to remove one first.
476
-
477
- What the agent can *see* is narrower. It lists and removes schedules you created, plus any that post into the conversation you're in, and nothing else, so asking it won't name private channels you aren't in. `bishop schedules list` still shows everything, with `by slack:U123…` naming who asked for each.
478
-
479
- ## Configuration
480
-
481
- `.bishop/config.json` holds everything except credentials. Setup writes it, and you can edit it by hand.
482
-
483
- ```json
484
- {
485
- "slack": {
486
- "workspace": "acme",
487
- "app": {
488
- "id": "A09XXXXXXXX",
489
- "name": "salesforce-guy",
490
- "displayName": "Salesforce Guy",
491
- "description": "Answers Salesforce admin questions"
492
- },
493
- "allow": [
494
- "hayes.davis@agentdeployment.co",
495
- "revops",
496
- "@sales-managers"
497
- ]
498
- },
499
- "gmail": {
500
- "allow": ["hayes.davis@agentdeployment.co", "@agentdeployment.co"],
501
- "pollSeconds": 20
502
- },
503
- "agent": {
504
- "harness": "claude",
505
- "model": "claude-sonnet-5",
506
- "effort": "high"
507
- }
508
- }
509
- ```
510
-
511
- Every block is optional, and an interface runs when its credentials are set rather than when its block is here. `gmail` takes `allow`, `pollSeconds`, `labelPrefix`, and `markRead`; its `allow` is addresses and `@domain` entries, and an entry that is neither stops startup.
512
-
513
- `slack` takes `allow`, `maxBotTurns`, and `verbosity`, plus the `workspace` and `app` fields setup writes.
514
-
515
- `teams` takes `allow`, `verbosity`, `port`, `host`, and `tls`, plus the `app` fields setup writes. Its `verbosity` is Slack's three levels and works the same way, including `! verbosity` for one conversation, except that `low` also drops the line saying what a turn cost.
516
-
517
- `agent` takes `harness`, `model`, `effort`, `worktrees`, `options`, and `harnesses`. The last two are alternatives: `options` for a config that runs one harness, `harnesses` for one that switches.
518
-
519
- Every key is checked at startup, so a typo fails with the path to it rather than being ignored. The one exception is `agent.options`, which is passed through to the harness unvalidated.
520
-
521
- ### Which agent runs
522
-
523
- `agent.harness` picks the SDK. `claude` is the default and runs the Claude Agent SDK; `codex` runs OpenAI's Codex SDK.
524
-
525
- ```json
526
- {
527
- "agent": {
528
- "harness": "codex"
529
- }
530
- }
531
- ```
532
-
533
- `--harness` overrides it for one run, and an unsupported name fails at startup naming the ones that exist:
534
-
535
- ```
536
- $ bishop --harness codex
537
- $ bishop --harness gemini
538
- unknown harness "gemini"; supported: claude, codex
539
- ```
540
-
541
- Bishop manages no model credentials either way. Each harness authenticates however its own CLI does in that directory, so make `claude` or `codex` work there first. For Codex that means `codex login`, or `CODEX_API_KEY` in the environment or `.env`.
542
-
543
- `OPENAI_API_KEY` is deliberately not read, because `codex` doesn't read it either. With only `OPENAI_API_KEY` set and no login, `codex` sends no credential at all and the request comes back `401 Missing bearer`. Picking it up here would invent a credential path Codex doesn't have, and on a machine with a working `codex login` it would override that login and move billing to the API key without saying so.
544
-
545
- Bishop warns at startup when it can find neither a `CODEX_API_KEY` nor a login, since the first turn would otherwise fail on authentication with nothing to explain it. The startup check can't catch that itself: it stops before any model call, which is what makes it free. Otherwise the log names the credential it found.
546
-
547
- A session id belongs to the harness that issued it, so changing this leaves running threads unable to resume. Bishop notices, logs it, and starts each thread a fresh session on its next message rather than failing the turn. Nothing else is lost.
548
-
549
- Codex threads read differently in one way that shows: what its tool cards say is vaguer, because a command comes through as a raw shell line and nothing else, which Bishop won't put in a thread.
550
-
551
- ### Model and effort
552
-
553
- Every harness has a model and a reasoning level, so both sit at the top of `agent` rather than inside `agent.options`:
554
-
555
- ```json
556
- {
557
- "agent": {
558
- "harness": "codex",
559
- "model": "gpt-5.6-codex",
560
- "effort": "high"
561
- }
562
- }
563
- ```
564
-
565
- Both are free strings, passed to the harness as written. The names belong to the harness and change with it, so Bishop doesn't check them: `claude` takes `claude-sonnet-5` and `low` through `max`, `codex` takes `gpt-5.6-codex` and `modelReasoningEffort`'s levels.
566
-
567
- Nothing checks the value before the first turn, including the startup check, which stops before any model call. A name the harness doesn't know surfaces in a thread instead, differently by harness. Codex fails the turn with the API's own complaint about the field. Claude answers a bad model with the agent saying in prose that the model is wrong, and ignores an effort level it doesn't recognize. So an effort typo under Claude is silent: check the log line at startup, which names the model and effort Bishop passed.
568
-
569
- `--model` and `--effort` override the config for one run:
570
-
571
- ```
572
- $ bishop --harness claude --model claude-opus-5 --effort high
573
- ```
574
-
575
- Each harness maps them onto its own option, which is `model` and `effort` under Claude, `model` and `modelReasoningEffort` under Codex. Setting the same thing again in `agent.options` loses to these, and startup logs a warning naming the key it overrode. That way a flag never does nothing silently.
576
-
577
- ### Switching between harnesses
578
-
579
- `agent.options` is written in one harness's vocabulary, so a config meant to run either one keeps a block per harness:
580
-
581
- ```json
582
- {
583
- "agent": {
584
- "harness": "claude",
585
- "worktrees": true,
586
- "effort": "high",
587
-
588
- "harnesses": {
589
- "claude": {
590
- "options": { "maxTurns": 30 }
591
- },
592
- "codex": {
593
- "model": "gpt-5.6-codex",
594
- "options": { "sandboxMode": "danger-full-access" }
595
- }
596
- }
597
- }
598
- }
599
- ```
600
-
601
- `bishop` runs Claude with its block, and `bishop --harness codex` runs Codex with its own model and sandbox setting. Nothing written for one follows the other.
602
-
603
- A block takes `model`, `effort`, and `options`, and nothing else. `model` and `effort` layer over the ones beside `harness`, so `effort` above applies to both harnesses and Codex adds a model. `options` doesn't layer: it replaces `agent.options` outright, since merging two objects written in different vocabularies would splice one harness's keys into the other's call.
604
-
605
- That leaves `agent.options` as the options for whichever harness has no block of its own, which is what keeps a config that never switches harness exactly as it was. Set both for the same harness and startup warns that the block wins. Switch to a harness with no block and startup warns that it is being handed options meant for the other one, since the harness that doesn't own them ignores what it doesn't recognize and says nothing.
606
-
607
- A harness name Bishop doesn't run fails at startup rather than being ignored, so a misspelled block can't quietly do nothing.
608
-
609
- ### Who can use the agent
610
-
611
- `allow` takes usernames, emails, and group names in one list, and Bishop works out which is which. A leading `@` is optional. Groups expand to their members, and Bishop refreshes the list every few minutes so a membership change lands without a restart.
612
-
613
- Every message is checked, not just the one that starts a thread, so someone who isn't allowed can't join a conversation another person started. They get a reply only they can see.
614
-
615
- Another app is an entry like any other: list it by name and the agent will answer it, subject to the same limits on a bot-only exchange described in [Using it in Slack](#using-it-in-slack). This is the setting that decides which apps can reach the agent, and it matters more than it did: without a list, any app posting in a thread someone started can draw a turn, and what it posts is text nobody in the thread wrote. Set `slack.maxBotTurns` to `0` to keep every one of them out.
616
-
617
- Leave `allow` out and anyone who can reach the bot can use the agent. Bishop warns at startup when that's the case.
618
-
619
- An entry matching two different things, like a name that's both a person and a group, stops startup and says which candidates it found. An entry matching nothing logs a warning and Bishop keeps running, so a departed colleague's email doesn't take the agent down.
620
-
621
- ### Agent options
622
-
623
- `agent.options` is passed to the agent harness. Under Claude that is the Agent SDK's own options object, not a settings file: anything from [Claude Code's settings reference](https://code.claude.com/docs/en/settings-reference) goes in the nested `settings` object, which is shaped like a `.claude/settings.json`. A settings key written at the top level is dropped by the SDK without a word, so put it a level down:
624
-
625
- ```json
626
- {
627
- "agent": {
628
- "options": {
629
- "maxTurns": 30,
630
- "settings": {
631
- "permissions": { "allow": ["Bash(python3:*)"] }
632
- }
633
- }
634
- }
635
- }
636
- ```
637
-
638
- These layer over the agent's own settings files rather than replacing them, so a project that sets `model` in `.claude/settings.json` keeps it unless `agent.options` names `model` too. [`agent.model` and `agent.effort`](#model-and-effort) sit above `agent.options` in turn, and a config that switches harness puts its options in [a block per harness](#switching-between-harnesses) instead.
639
-
640
- Bishop sets a few options itself and ignores them here: `cwd`, `resume`, `systemPrompt`, `abortController`, `sessionId`, `forkSession`, and `canUseTool`. Setting one logs a warning naming it. They decide which agent runs, which conversation it continues, and how Bishop tells the agent who it's talking to.
641
-
642
- Bishop sets two defaults, both overridable. `permissionMode: "auto"`, because nothing in Slack can answer a permission prompt and an agent needing approval for a tool reports itself blocked instead of doing the work; override it in `agent.options` if you want something stricter. And `settings.autoMemoryEnabled: false`, because auto memory writes to `~/.claude/projects/<agent-directory>/memory`, outside the agent's own repository: it doesn't travel when you deploy the agent somewhere else, and a remote agent's directory changes with every commit, so the memory of the last one is orphaned. Turn it back on with:
643
-
644
- ```json
645
- {
646
- "agent": {
647
- "options": {
648
- "settings": { "autoMemoryEnabled": true }
649
- }
650
- }
651
- }
652
- ```
653
-
654
- That key belongs under `settings` and nowhere else. The harness ignores it at the top of `agent.options`, so Bishop warns at startup if you put it there.
655
-
656
- `hooks` and `settings` merge instead of being replaced or ignored. Your hooks run alongside the one [worktree mode](#worktree-mode) installs, so setting `hooks` can't switch that off, and your `settings` object keeps Bishop's defaults for the keys it doesn't name. Pointing `settings` at a settings *file* instead opts out of both defaults, which Bishop warns about.
657
-
658
- #### Under the Codex harness
659
-
660
- `agent.options` takes Codex's own fields instead:
661
-
662
- ```json
663
- {
664
- "agent": {
665
- "harness": "codex",
666
- "options": {
667
- "sandboxMode": "danger-full-access",
668
- "additionalDirectories": ["/srv/data"]
669
- }
670
- }
671
- }
672
- ```
673
-
674
- Bishop's defaults are `approvalPolicy: "never"`, `sandboxMode: "workspace-write"`, `networkAccessEnabled: true`, and `skipGitRepoCheck: true`. Nothing in Slack can answer an approval prompt, so an agent that asks for one stalls the turn. The sandbox is the compensation: writes stay inside the working directory, and network access stays on because an agent that can't push, install, or fetch fails at something in most threads. Override any of them here.
675
-
676
- Bishop sets `workingDirectory` and `developer_instructions` itself and ignores them here, for the same reasons as above.
677
-
678
- `config` and `configOverrides` reach Codex's own `--config` mechanism, which is how anything in `config.toml` can be set per-agent. Bishop uses `config.developer_instructions` for the principal, so a `config` object you set is merged rather than replaced.
679
-
680
- ### Worktree mode
681
-
682
- Threads share one directory, so two people directing an agent in two threads can have it write the same files. Turn that off:
683
-
684
- ```json
685
- {
686
- "agent": {
687
- "worktrees": true
688
- }
689
- }
690
- ```
691
-
692
- Each thread now works in its own git worktree, named `bishop-<team>-<channel>-<thread>`, and stays in it for every later message. It lives in `.claude/worktrees/` at the root of the repository the agent is in, which is the agent directory itself unless you pointed `--agent` at a git URL; there Bishop's own checkout layout puts it in `.bishop/agents/<slug>/mirror.git/.claude/worktrees/`. `git worktree list` always says.
693
-
694
- How it gets there depends on the harness, because only one of them can be told to move itself:
695
-
696
- - **Claude** creates the worktree the first time the agent decides to edit a file, so a thread that only asks questions never gets one. Bishop names it, tells the agent to enter it, and refuses edits in the shared checkout until it has. The branch is the worktree name prefixed with `worktree-`.
697
- - **Codex** has no equivalent tool, so Bishop creates the worktree itself and starts the session inside it. Every thread gets one whether or not it edits anything, and the branch is the worktree name exactly. Nothing depends on the agent cooperating.
698
-
699
- Either way the agent commits on that branch and says which one it is.
700
-
701
- A worktree outlives the thread that made it, and Bishop collects it a month after the thread's last message, along with its branch. See [Cleaning up after itself](#cleaning-up-after-itself). To take one sooner: under Claude the harness locks each one while its session holds it, and the lock outlives the process, so removing one takes two commands:
702
-
703
- ```
704
- $ git worktree list
705
- $ git worktree unlock .claude/worktrees/bishop-T1-C1-1712345678-123456
706
- $ git worktree remove .claude/worktrees/bishop-T1-C1-1712345678-123456
707
- ```
708
-
709
- Add `--force` to `remove` to discard uncommitted changes in it. Check `git -C <worktree> status` first: that's where a thread's unfinished work is.
710
-
711
- A new worktree holds only tracked files, so `node_modules` and anything else your repository ignores is missing. Symlink what the build needs:
712
-
713
- ```json
714
- {
715
- "agent": {
716
- "worktrees": true,
717
- "options": {
718
- "settings": {
719
- "worktree": {
720
- "symlinkDirectories": ["node_modules"],
721
- "baseRef": "head"
722
- }
723
- }
724
- }
725
- }
726
- }
727
- ```
728
-
729
- Bishop sets `baseRef` to `head` so a worktree branches from wherever the agent directory is now. Set it to `fresh` to branch from `origin/<default-branch>` instead. Everything else the harness offers here, including `sparsePaths` for a large monorepo, works the same way. That block is Claude's; under Codex, Bishop always branches from `HEAD` and there is nothing to configure.
730
-
731
- Under Claude, Bishop refuses `Edit`, `Write`, and `NotebookEdit` in the shared directory until the agent has entered its worktree, and tells the agent why. Bash is not checked, since a command's target can't be read off its text. So this keeps threads out of each other's way; it isn't a sandbox, and an agent determined to write outside its worktree can. Under Codex the session starts in the worktree, so nothing has to be refused, and `sandboxMode` is what limits where it can write, or nono when the sandbox is on.
732
-
733
- **When the agent directory isn't a git repository**, there's nothing to isolate. Bishop warns at startup and says so once in each thread, then edits the directory directly.
734
-
735
- ### Sandbox
736
-
737
- The agent has a shell, and without a sandbox it can read anything Bishop can: `bishop.db`, `.bishop/config.json`, `.env`, and files people shared in other threads. Turning the sandbox on runs the agent, and everything it starts, under [nono](https://nono.sh/os-sandbox), which has the kernel refuse all of that. It works the same under either harness. Under Codex, Bishop also switches Codex's own sandbox off (`sandboxMode` becomes `danger-full-access`) and logs that it did, because nono replaces it and Codex's own failed to start inside nono when tested on Linux.
738
-
739
- Install nono first. On Debian or Ubuntu:
740
-
741
- ```
742
- VERSION=$(curl -sIL https://github.com/nolabs-ai/nono/releases/latest | grep -i location | grep -oP 'v\K[0-9a-zA-Z.-]+')
743
- ARCH=$(dpkg --print-architecture)
744
- wget https://github.com/nolabs-ai/nono/releases/download/v${VERSION}/nono-cli_${VERSION}_${ARCH}.deb
745
- sudo dpkg -i nono-cli_${VERSION}_${ARCH}.deb
746
- ```
747
-
748
- On macOS, or Linux with Homebrew, `brew install nono`. Other distributions are covered in [nono's install guide](https://nono.sh/docs/cli/getting_started/installation). Then check that this machine can enforce it:
749
-
750
- ```
751
- nono setup --check-only
752
- ```
753
-
754
- Linux needs kernel 5.13 or later, and macOS 10.15 or later. nono has to be on the `PATH` Bishop runs with, which for a systemd unit is the unit's own `Environment=PATH=…`.
755
-
756
- Bishop's own directory can't be the agent directory with the sandbox on, because the kernel can't grant a directory while hiding part of it. Run Bishop from a directory of its own and point `--agent` at the agent, either a path or a git URL:
757
-
758
- ```
759
- mkdir ~/bishop-home && cd ~/bishop-home
760
- bishop --agent ~/agents/salesforce-guy
761
- ```
762
-
763
- Move `.env` and `.bishop/` from the old directory into the new one first, or run setup there again. Then turn it on in `.bishop/config.json`:
764
-
765
- ```json
766
- {
767
- "agent": {
768
- "sandbox": true
769
- }
770
- }
771
- ```
772
-
773
- Bishop checks nono works before it starts, and refuses to start rather than run the agent unconfined. The log says `sandbox is on` when it is.
774
-
775
- Each turn can read and write its working directory (the thread's worktree in worktree mode), the repository's git directory so it can commit, the temp directory, and Claude Code's own `~/.claude`. It can read the files shared in its own thread, and your git config. Network access is unchanged. Everything else under your home directory is out of reach, and a command that tries gets "Permission denied", which the agent can report like any other error.
776
-
777
- That includes tools installed under your home directory, like `nvm`, `pyenv` or `~/.cargo`. Grant them with a nono profile, which adds to Bishop's grants rather than replacing them. Save one as `~/.config/nono/profiles/toolchain.json`:
778
-
779
- ```json
780
- {
781
- "meta": { "name": "toolchain" },
782
- "filesystem": {
783
- "read": ["$HOME/.nvm", "$HOME/.cargo"]
784
- }
785
- }
786
- ```
787
-
788
- and name it:
789
-
790
- ```json
791
- {
792
- "agent": {
793
- "sandbox": { "profile": "toolchain" }
794
- }
795
- }
796
- ```
797
-
798
- `nono profile guide` explains the format. Don't add a `filesystem.deny` for anything Bishop grants: on Linux nono refuses to start with one, and Bishop will refuse to start with it.
799
-
800
- The sandbox also keeps the agent out of `~/.ssh` and the `gh` CLI's login, so it can't push. `bishop nono setup` writes a nono profile named `bishop` (or whatever `--name` says) to `~/.config/nono/profiles/` that grants both, plus nono's `node-dev` toolchain and `~/.npm`, and names it in `agent.sandbox`, turning the sandbox on if it was off, so run it from Bishop's own directory, apart from the agent. If a profile already sits at that path it isn't replaced, and if `agent.sandbox` already names another profile the command leaves it and explains how to combine the two. The profile lets the agent read your SSH private keys, so give Bishop's user keys meant for the agent.
801
-
802
- `file_share` sends only files the agent could read itself: a file somewhere else has to be copied into its working directory first.
803
-
804
- Leave Claude Code's own `sandbox` setting off under nono. One sandbox is easier to reason about, and Claude's has an escape hatch that retries a blocked command outside it.
805
-
806
- The container image doesn't include nono, and the sandbox isn't supported there yet. macOS is supported by nono but hasn't been tested with Bishop.
807
-
808
- ### Files people share
809
-
810
- Files go both ways: people attach them for the agent, and the agent sends them back. This half is the first.
811
-
812
- Bishop downloads what people attach and saves it under `.bishop/files`, one directory per thread, collected with everything else that thread left behind. The agent reads it from disk, so any file type works.
813
-
814
- A file shared earlier in the conversation is named to the agent rather than downloaded, and the agent fetches the ones it wants with a tool. That keeps being tagged into a busy thread cheap, and it means the agent can go back for a file it was told about turns ago. A reference only works in the conversation the file was shared in.
815
-
816
- ```json
817
- {
818
- "files": {
819
- "maxMb": 100
820
- }
821
- }
822
- ```
823
-
824
- The default is 100. A file over the limit isn't refused: the agent is told its name and that it was too large, so it can say so. There is no restriction on type and won't be.
825
-
826
- **Slack needs the `files:read` and `files:write` scopes**, which are new. An app set up before these has to be reinstalled to grant them, and until then Bishop warns at startup about each one it is missing, the agent sees a note in place of each file, and a file it sends fails to upload. Run `bishop slack manifest` for the current manifest, or reinstall from App Settings. Gmail needs nothing new. **Teams needs `supportsFiles`** on the app's bot, without which it offers no attach button in a chat with the agent and only pasted images arrive. `bishop teams setup` sets it on a new app and tells you when an existing one is missing it, which needs the Teams CLI logged in.
827
-
828
- ### Files the agent sends
829
-
830
- The other half. The agent can send a file back: an upload in the Slack thread, an attachment on the mail in email, a file card in Teams. It registers the file with a tool, which checks it and hands back a reference, and writes that reference into its reply where the file should appear. In Slack the file arrives on the message carrying the words around it, the way a person sends one. A file it registers and never places is still sent, after the answer, so nothing is silently dropped.
831
-
832
- In a Teams personal chat the recipient gets a card asking them to accept the file, and it arrives once they do. In a channel the file is uploaded to the channel's SharePoint files folder, where it stays after the conversation and anyone with access to that folder can open it, including people who were never in the thread. Sending to a channel needs the `Files.ReadWrite.All` grant described under [Microsoft Teams](#microsoft-teams); without it the message goes out without the file. Group chats can't carry a file, and an agent that tries in one is told so.
833
-
834
- The limit is 25MB per answer in Slack and Teams and 4MB by email, across everything one answer sends. Neither is configurable, and the email one is lower because of how Bishop talks to Gmail rather than what Gmail accepts. Anything past it is dropped with a line in the log and the message still goes. A scheduled report can carry files too, and an agent that reports nothing sends nothing, attachment included.
835
-
836
- ### Credentials
837
-
838
- `.env` holds each interface's credentials, written by its setup command:
839
-
840
- ```
841
- BISHOP_SLACK_BOT_TOKEN=xoxb-...
842
- BISHOP_SLACK_APP_TOKEN=xapp-...
843
- BISHOP_TEAMS_CLIENT_ID=...
844
- BISHOP_TEAMS_CLIENT_SECRET=...
845
- BISHOP_TEAMS_TENANT_ID=...
846
- ```
847
-
848
- Add `ANTHROPIC_API_KEY` if the agent should use a specific key. Without one, the agent authenticates however `claude` does on that machine, usually an existing login. Bishop logs which of the two it used at startup.
849
-
850
- Anything else in `.env` reaches the agent's environment too, which is how `GH_TOKEN` from [`bishop github setup`](#github) gets there.
851
-
852
- ## Running it for real
853
-
854
- Bishop is one process that logs JSON to stdout and errors to stderr, so any process supervisor works. Slack and Gmail need outbound access only, since one holds a socket open and the other polls. Teams is the exception: it POSTs activities to an endpoint rather than holding a socket open, so Bishop serves one when Teams credentials are present. That endpoint binds loopback like every other port Bishop opens, and a tunnel gives it a public hostname and a certificate without an inbound firewall rule.
855
-
856
- ```
857
- $ BISHOP_LOG_LEVEL=info bishop
858
- ```
859
-
860
- `BISHOP_LOG_LEVEL=debug` adds tool activity and every routing decision. Pipe through `npx pino-pretty` to read it by eye. It works in `.env` as well as exported, and an exported one wins. It is the only setting of Bishop's own that `.env` carries; everything else lives in `config.json`. A value that isn't a level (`trace`, `debug`, `info`, `warn`, `error`, `fatal`, `silent`) logs a warning and leaves the level at `info`.
861
-
862
- At startup Bishop checks that the agent can launch, resolves the allow list, and connects to Slack, failing with a specific message if any of those doesn't work.
863
-
864
- ### As a systemd service
865
-
866
- This section assumes Bishop and its dependencies are already installed. [`LINUX_INSTALL.md`](LINUX_INSTALL.md) is the punchlist for running natively on a Linux host instead of the container: Node, the OS packages an agent needs as its shell, `uv`, the npm install, credentials, and a hardened unit.
867
-
868
- A unit file keeps Bishop running across crashes and reboots on a Linux host:
869
-
870
- ```
871
- [Unit]
872
- Description=Bishop gateway for <agent>
873
- After=network-online.target
874
- Wants=network-online.target
875
-
876
- [Service]
877
- Type=simple
878
- User=<user>
879
- WorkingDirectory=/path/to/agent
880
- Environment=BISHOP_LOG_LEVEL=info
881
- ExecStart=/path/to/node /path/to/bishop/dist/cli.js --agent /path/to/agent
882
- Restart=on-failure
883
- RestartSec=5
884
-
885
- [Install]
886
- WantedBy=multi-user.target
887
- ```
888
-
889
- Point `ExecStart` at an absolute path to `node`, not the bare command. systemd doesn't source `.bashrc` or `.profile`, so a version installed through nvm isn't on `PATH` for the service even though it is for you in a login shell. `which node` finds the real path; symlink it somewhere stable like `~/.local/bin` so the unit survives a node upgrade without an edit. Installed from npm, the script it runs is `<npm root -g>/@agentdeploymentco/bishop/dist/cli.js`.
890
-
891
- ```
892
- $ sudo systemctl enable --now bishop.service
893
- $ journalctl -u bishop -f
894
- ```
895
-
896
- **Editing the unit file needs no sudo, applying it always does.** `chown` the unit to the service's own user (`sudo chown <user> /etc/systemd/system/bishop.service`) so an edit is a normal file write, then scope `sudoers` to exactly the two commands a change needs: `daemon-reload` and `restart bishop.service`. That is the smallest grant that lets a deployment change its own `ExecStart` without a root shell.
897
-
898
- ```
899
- <user> ALL=(root) NOPASSWD: /usr/bin/systemctl daemon-reload, /usr/bin/systemctl restart bishop.service
900
- ```
901
-
902
- **Switching `--agent` from a local path to a git URL with `--auto-update`, replace the flag, don't append it.** `ExecStart` is a backslash-continued multi-line value, and the old `--agent /path` line loses its trailing `\` when a new one gets pasted below it. systemd then ends the directive at that line, and the new `--agent` line below becomes a bare, invalid line in `[Service]`. `systemctl status` catches half of this ("unit file changed on disk, run daemon-reload") but not the syntax error, so read the file back after editing rather than trusting the diff you meant to make.
903
-
904
- **A restart kills whichever turn asked for it, when Bishop is asked to reconfigure itself.** An agent that edits its own unit file and runs `systemctl restart` takes down the process running that same turn. It comes back clean, since session and thread state live in Bishop's own working directory rather than the agent's, so the conversation resumes on the next message. But the response that triggered the restart cuts off mid-stream, which reads as a crash to whoever is waiting on it unless they're told to expect it. Some harnesses also refuse this outright: Claude Code's own permission model blocks a session from editing a file outside its project directory or restarting the service that's running it, so a human ends up applying the unit change and the restart by hand either way.
905
-
906
- **A fresh `--auto-update` snapshot has no git identity.** Each commit gets its own directory under `.bishop/agents/<slug>/snapshots/`, cloned straight from the bare mirror, and none of them inherit a `user.name`/`user.email` set locally in an old checkout. Set it globally for the service's user once (`git config --global user.name ...`) rather than per snapshot, or every commit the agent makes fails with "Author identity unknown."
907
-
908
- ### In a container
909
-
910
- `ghcr.io/agent-deployment-co/bishop` runs Bishop on Debian with Node 24, for `linux/amd64` and `linux/arm64`. The image is the agent's shell as well as Bishop's, so it carries git, `gh`, ripgrep, jq, curl, ssh, and python3 alongside both harness SDKs. `uv` and `uvx` are there too, with CPython 3.14 already installed and on `PATH` as `python3.14`, since Debian's `python3` is externally managed and won't let anything install into it.
911
-
912
- Two directories to persist. `/data` is the working directory, holding everything Bishop writes:
913
-
914
- ```
915
- /data/.bishop/config.json configuration
916
- /data/.bishop/agents/ cloned agents, when --agent is a git URL
917
- /data/bishop.db the thread-to-session mapping
918
- ```
919
-
920
- `/home/node` is the second, holding what the harnesses write: their logins, and the session transcripts they resume from. Bishop resumes a thread by handing back the session id it recorded in `bishop.db`, so a home directory that dies with the container leaves every existing thread asking for a transcript the harness no longer has. Mount it or lose every conversation on the next deploy.
921
-
922
- Credentials go in the environment rather than a `.env` file:
923
-
924
- ```
925
- $ docker run -d --name bishop \
926
- -v bishop-data:/data -v bishop-home:/home/node \
927
- -e BISHOP_SLACK_BOT_TOKEN -e BISHOP_SLACK_APP_TOKEN \
928
- -e ANTHROPIC_API_KEY \
929
- -e GIT_CONFIG_COUNT=2 \
930
- -e GIT_CONFIG_KEY_0=user.name -e GIT_CONFIG_VALUE_0="Agent" \
931
- -e GIT_CONFIG_KEY_1=user.email -e GIT_CONFIG_VALUE_1="agent@example.com" \
932
- ghcr.io/agent-deployment-co/bishop \
933
- --agent https://github.com/you/your-agent.git --auto-update
934
- ```
935
-
936
- The entrypoint is `bishop`, so arguments after the image are its flags, and any other command works the same way: `docker run --rm -v bishop-data:/data ghcr.io/agent-deployment-co/bishop gc`.
937
-
938
- Set a git identity, as above, or the agent can't commit what it does in a worktree. `GIT_CONFIG_COUNT` reaches every snapshot and worktree Bishop creates, which a `git config` run in one directory doesn't.
939
-
940
- Bishop runs as uid 1000 and needs to write both volumes. On Kubernetes that means a `fsGroup` in the pod's security context, or volumes already owned by 1000.
941
-
942
- Tags: `latest` and `sha-<commit>` from `main`, plus `<version>` and `<major>.<minor>` from a `v*` git tag. `bishop --version` reads `package.json`, so a release tag has to bump it in the same commit or the image reports a version its tag disagrees with.
943
-
944
- The image doesn't include nono, so `agent.sandbox` isn't supported in it yet.
945
-
946
- Setup commands that need a browser or a terminal (`bishop slack setup`, `bishop gmail setup`) are meant to be run on a laptop. Carry the tokens they produce to the container as environment variables, and the `.bishop/config.json` they write into the volume.
947
-
948
- ## Troubleshooting
949
-
950
- **"--agent directory doesn't exist"** or **"is not a directory"** means the path is wrong. Bishop checks it before anything else, because the harness reports a bad directory as a native binary failing to launch.
951
-
952
- **"The Claude agent couldn't start"** means the harness didn't launch. Check that
953
- `claude` works in that directory first. Note that a wrong `ANTHROPIC_API_KEY` passes this check and fails on the first real message, since only a live call tests a key.
954
-
955
- **The agent says it's blocked on permissions.** Something needs approval that
956
- `auto` mode declined. Add an allow rule under `agent.options.settings.permissions.allow`.
957
-
958
- **The bot doesn't answer in a channel.** It only replies to mentions and to
959
- threads it's already in. Confirm it's in the channel and that you mentioned it.
960
-
961
- **The bot doesn't answer at all** and the log shows a refusal. The sender isn't
962
- on the `allow` list.
963
-
964
- **The agent never answers, and nothing appears in the log.** If you set the app up by hand, check its Event Subscriptions. Bishop can't see them, and an app subscribed to nothing looks identical to an idle one.
965
-
966
- **Setup says the app kept a `(local)` suffix.** The `slack` CLI's login expires
967
- after 12 hours, which stops Bishop from correcting the app name. Run `slack auth login` again, or rename the app in Slack.
968
-
969
- **You can't find what a thread changed.** In worktree mode the work is on that thread's branch, not in the agent directory. `git worktree list` shows every one, and `git branch --list 'worktree-bishop-*'` shows the branches.
970
-
971
- **A build or test fails inside a worktree.** A fresh worktree has only tracked files, so `node_modules` and anything else the repository ignores isn't there. Add it to `agent.options.settings.worktree.symlinkDirectories`.
972
-
973
- **The agent says it can't edit a file.** In worktree mode Bishop refuses writes in the shared directory. The agent is told to call `EnterWorktree` first, and normally does it unprompted. If the agent directory isn't a git repository, that can't work, and Bishop says so in the thread.
974
-
975
- **"agent.sandbox is on, but nono is not installed or not on PATH"** means what it says. Install it as in [Sandbox](#sandbox), and check `which nono` as the user and `PATH` Bishop runs with.
976
-
977
- **"The sandbox would have to grant …, which contains …/.bishop"** means Bishop's own directory is inside the agent directory. Run Bishop from a directory of its own and point `--agent` at the agent.
978
-
979
- **The agent says "Permission denied" with the sandbox on.** It reached for something outside its grants. If it needs it, add the path to a nono profile; `nono why --path <path> --op read` says which rule decided.
980
-
981
- **Sessions get confused.** Delete `bishop.db` to make every thread start fresh.
982
- It holds only the thread-to-session mapping.
983
-
984
- ## Upgrading
985
-
986
- Install the new version and restart. A version that changes the database migrates it on the first start, and says so in the log.
987
-
988
- **Bishop copies the database before it migrates.** The copy lands in `.bishop/backups/`, named for the schema it holds. To go back, stop Bishop, install the version you were on, and restore it:
989
-
990
- ```
991
- rm bishop.db bishop.db-wal bishop.db-shm
992
- cp .bishop/backups/bishop.db.v8.backup bishop.db
993
- ```
994
-
995
- **Delete `bishop.db-wal` first, as above.** Bishop runs the database in write-ahead logging mode, so recent changes live in that file rather than in `bishop.db`. A Bishop that stopped cleanly leaves nothing there, but one that was killed does, and SQLite replays it into whatever database file it finds beside it without checking that the two belong together. Restore without removing it and you get the migrated database back, looking exactly like a successful rollback. You can't tell which case you're in by looking, so remove them every time.
996
-
997
- Nothing deletes the copies, and `bishop gc` leaves them alone. One per schema version this database has been migrated from, so a handful at most. Upgrading again from the same version replaces that version's copy: what is worth keeping is the database as it stood just before the migration you are undoing, so if you restore, run the old version for a while and upgrade again, the copy covers that stretch too.
998
-
999
- If Bishop can't take the copy it refuses to start and says nothing was migrated, which usually means the disk is full or something else is under that name. Nothing has changed at that point, so fix the reason and start again.
1000
-
1001
- ## What this version doesn't do
1002
-
1003
- - One agent per directory, one process. No agent registry.
1004
- - Slack and Gmail only. No Teams.
1005
- - No sandboxing between threads on one agent. The [sandbox](#sandbox) keeps the agent out of Bishop's own state and other threads' shared files, but every thread can write the same working directory. [Worktree mode](#worktree-mode) keeps concurrent threads out of each other's files by convention, and it needs a git repository. Without it, two threads can write the same file at once.
1006
- - Files come in but don't go out. The agent can read what you attach; it can't attach anything to its answer.
1007
- - No approval flow. Permission decisions are the harness's, and Slack has no way to ask a person.
1008
- - No way to turn Bishop's own tools off. Bishop listens on `127.0.0.1` on an ephemeral port to serve them, authenticated with a credential minted per turn and destroyed when the turn ends. Nothing is reachable from off the machine, but anything running on it can reach the port and will be refused.
1009
-
1010
- Bishop notices neither a revoked Slack app nor a dead socket, so a supervised process can look healthy while receiving nothing. Restart it if the agent goes quiet.
1011
-
1012
- ## How setup gets the tokens
14
+ Bishop is an agent gateway: it sits between the places people work and the agents they work with. Today that is Slack, Gmail and Microsoft Teams, one agent, in a single process. It needs Node 24 or newer, and an agent directory where `claude` or `codex` already runs.
1013
15
 
1014
- Worth knowing if you're wondering why the `slack` CLI is a requirement.
16
+ ## Documentation
1015
17
 
1016
- Socket Mode needs an app-level (`xapp-`) token, and no Slack API will mint one. The `slack` CLI does, and hands it to a project it runs. So setup writes a throwaway Slack project, runs it once to collect both tokens, and stops it. The CLI creates that app as a dev app and appends ` (local)` to its name, so setup pushes the intended manifest over it afterward. That changes no permissions and needs no reinstall.
18
+ **[bishop.agentdeployment.co](https://bishop.agentdeployment.co)** covers installing, running, and deploying Bishop: setting up each interface, configuration, the sandbox, running under systemd or in a container, upgrading, and troubleshooting. Its source is [`docs/`](docs/) in this repository.
1017
19
 
1018
- Teams needs none of that. `teams app create` talks to the Developer Portal directly and reports the app and its credentials as JSON, which setup reads and writes to `.env` under its own variable names.
20
+ [`design/`](design/) is how Bishop works inside, for anyone changing it, and [`design/glossary.md`](design/glossary.md) is what its words mean.
1019
21
 
1020
22
  ## License
1021
23