Workflow I could not complete
A path-installed plugin proxies a local MCP daemon (Aurora Operator) into BB threads with bb.agents.registerTool. omp under ACP loads MCP servers only from its ACP client, so BB's dynamic-tool bridge is the only way those threads reach the daemon. When the daemon gains an option (a new enum value in aurora_run), the plugin reloads and registers the new schema, but every omp thread that was already running keeps the old schema until its provider session is rebuilt. Agents in long-running threads report the tool as "outdated" and cannot pass the new option.
Restarting threads is not a workable answer for agents that run for hours or days, and omp already applies MCP tool-list changes mid-session: the same omp session mounted and unmounted tools from another MCP server when that server's list changed (xd:// device inventory changed notices at 10:52:50, 11:07:24 and 11:10:07 UTC in the session below).
Observed (bb 0.45.0, omp 18.8.7, macOS 26)
- Thread
thr_6xuag86y3q (acp-omp) started 2026-10-11 10:14 UTC. The plugin had last been loaded before the daemon gained aurora_run.gemma_phone values extraction-capacity, synthesis-capacity and embedding-public. The plugin was reloaded at ~11:00 UTC. The agent then reported: "the bridge's aurora_run options leave out extraction-capacity, synthesis-capacity and embedding-public".
- New threads started after the reload see the new schema.
Steps to reproduce
-
Create a path plugin whose server registers one tool and selects it for every provider:
export default function plugin(bb) {
bb.agents.registerTool({
name: "live_probe",
description: "Return its arguments.",
parameters: { type: "object", properties: { a: { type: "string" } } },
execute: async (input) => JSON.stringify(input),
});
bb.agents.configure(() => ({ tools: ["live_probe"], skills: [] }));
}
-
bb plugin install /path/to/plugin --yes
-
Start an omp (ACP) thread and ask: "List the parameters of the live_probe tool."
-
Add b: { type: "string" } to properties, then bb plugin reload <plugin-id>.
-
In the same thread ask again.
Reproduced from source on main at 3912f4aaefb7dff55c7e0cb5003b26b5cf1457dc with pnpm dev (omp 18.8.7, thread thr_wz4xr6e763 on the dev server, one provider session 01a12b09-594e-7622-834f-4a732b824ea3 and one omp acp process throughout):
- Step 3 answer
a; the agent's read of the tool schema at 12:56:39Z returned type Args = { a?: string; };
- Step 4: the server logged only
{"msg":"plugin live-probe@0.1.0 loaded"}; the omp debug log (~/.omp/logs/omp.<date>.<pid>.log) received no notifications/tools/list_changed.
- Step 5 answer still
a; the read at 12:57:08Z returned type Args = { a?: string; };
Expected vs actual
- Expected: after step 4 the running session's tool list contains
b (the bridge MCP server emits notifications/tools/list_changed and serves the new schema).
- Actual: the running session keeps
{ a } until the provider session is rebuilt.
Root cause (base commit 3912f4aaefb7dff55c7e0cb5003b26b5cf1457dc)
- The server resolves the tool set only while building
thread.start/turn.submit:
|
const dynamicToolContributions = resolveDynamicTools( |
|
conditionalConfiguration.tools, |
|
); |
|
const dynamicTools = dynamicToolContributions.map( |
|
(contribution) => contribution.tool, |
|
); |
. A plugin reload only notifies the UI:
|
function notifyPluginsChanged(): void { |
|
const changes: SystemChangeKind[] = ["plugins-changed"]; |
|
const providerRegistrationRevision = |
|
deps.providerRegistry?.getRegistrationRevision(); |
|
if ( |
|
providerRegistrationRevision !== undefined && |
|
providerRegistrationRevision !== lastNotifiedProviderRegistrationRevision |
|
) { |
|
lastNotifiedProviderRegistrationRevision = providerRegistrationRevision; |
|
changes.push("provider-registrations-changed"); |
|
} |
|
deps.hub.notifySystem(changes); |
|
} |
- On
turn.submit to an already hosted thread the daemon returns before using resumeContext.dynamicTools:
|
async function resumeThreadRuntimeIfMissing( |
|
args: ResumeThreadRuntimeIfMissingArgs, |
|
): Promise<void> { |
|
const { command, entry, options } = args; |
|
const { resumeContext } = command; |
|
if (entry.runtime.hasThread(command.threadId)) { |
|
return; |
|
} |
|
if (!resumeContext.providerThreadId) { |
|
throw new CommandDispatchError( |
|
"unknown_thread_runtime", |
|
`No provider thread id available for thread ${command.threadId}`, |
|
); |
|
} |
|
const bridgeLaunch = await resolveRuntimeBridgeLaunch( |
|
command.resumeContext.bridgeLaunch ?? command.bridgeLaunch, |
|
options, |
|
); |
|
await entry.runtime.resumeThread({ |
|
bridgeLaunch, |
|
skillRoots: entry.skillRoots, |
|
environmentId: command.environmentId, |
|
threadId: command.threadId, |
|
projectId: resumeContext.projectId, |
|
providerThreadId: resumeContext.providerThreadId, |
|
providerId: resumeContext.providerId, |
|
contributedEnv: resumeContext.contributedEnv, |
|
options: command.options, |
|
instructions: resumeContext.instructions, |
|
dynamicTools: resumeContext.dynamicTools, |
|
instructionMode: resumeContext.instructionMode, |
|
}); |
|
} |
, and turn/start carries no tools:
|
const turnInputFields = { |
|
threadId: z.string().min(1), |
|
providerThreadId: z.string().min(1), |
|
input: z.array(promptInputSchema), |
|
clientRequestId: clientTurnRequestIdSchema, |
|
options: bridgeExecutionOptionsSchema, |
|
}; |
|
|
|
export const turnStartParamsSchema = z.object(turnInputFields).passthrough(); |
- The ACP bridge freezes the list into the bb-bridge MCP child's env (
BB_ACP_DYNAMIC_TOOLS) at session creation and advertises tools: {} without listChanged:
|
async function buildSessionMcpServers( |
|
params: AcpSessionParams, |
|
): Promise<AcpMcpServerConfig[]> { |
|
const dynamicTools = params.dynamicTools ?? []; |
|
if (dynamicTools.length === 0) { |
|
return []; |
|
} |
|
const bridge = await ensureDynamicToolBridge(); |
|
const config = buildAcpMcpServerConfig({ |
|
bridgeArgs: resolveBridgeProcessArgsForMcpServer(), |
|
command: process.execPath, |
|
dynamicTools, |
|
host: bridge.host, |
|
port: bridge.port, |
|
runtimeEnv: resolveBridgeProcessEnvForMcpServer(), |
|
threadId: params.threadId, |
|
token: bridge.token, |
|
}); |
|
process.stderr.write( |
|
`acp bridge: built "${config.name}" session MCP config for thread "${params.threadId}" (${dynamicTools.length} tools)\n`, |
|
); |
|
return [config]; |
|
} |
,
|
export function buildAcpMcpServerConfig( |
|
args: BuildAcpMcpServerConfigArgs, |
|
): AcpMcpServerConfig { |
|
return { |
|
name: ACP_BRIDGE_MCP_SERVER_NAME, |
|
command: args.command, |
|
args: args.bridgeArgs, |
|
env: [ |
|
...args.runtimeEnv, |
|
{ name: ENV_HOST, value: args.host }, |
|
{ name: ENV_PORT, value: String(args.port) }, |
|
{ name: ENV_TOKEN, value: args.token }, |
|
{ name: ENV_THREAD_ID, value: args.threadId }, |
|
{ name: ENV_TOOLS, value: JSON.stringify(args.dynamicTools) }, |
|
], |
|
}; |
|
} |
,
|
case "initialize": |
|
writeResult(message.id, { |
|
protocolVersion: |
|
typeof objectParams(message.params).protocolVersion === "string" |
|
? objectParams(message.params).protocolVersion |
|
: "2024-11-05", |
|
capabilities: { tools: {} }, |
|
serverInfo: { name: ACP_BRIDGE_MCP_SERVER_NAME, version: "1.0.0" }, |
|
}); |
|
void callBridge(env, { |
|
kind: "initialized", |
|
toolCount: env.tools.length, |
|
}).catch((error) => { |
|
process.stderr.write( |
|
`bb-bridge MCP: failed to report initialize: ${ |
|
error instanceof Error ? error.message : String(error) |
|
}\n`, |
|
); |
|
}); |
|
return; |
|
|
|
case "tools/list": |
|
writeResult(message.id, { |
|
tools: env.tools.map((tool) => ({ |
|
name: tool.name, |
|
description: tool.description, |
|
inputSchema: tool.inputSchema, |
|
})), |
|
}); |
|
return; |
- The plugin docs describe this as intended:
|
Resolution happens for `thread.start` and `turn.submit`. A selected tool set |
|
takes effect only when the provider session is next started/resumed; BB never |
|
hot-mutates a running provider session. Instructions follow the same rule: a |
|
live provider session keeps the instructions it was constructed with, and |
|
changed instructions apply when the session is next constructed. |
Proposed behavior
When a plugin change (install, reload, enable/disable, settings change) alters a live thread's resolved dynamic tools, BB pushes the new set to providers that can apply it without rebuilding the session. Policy stays on the server; the daemon applies it to live sessions:
- The server recomputes each live thread's tools through the same
configure resolution used at start/submit and sends them to the daemon.
- The daemon stores the set (so a rebuilt session uses it) and forwards it to bridges that declare a new optional handshake capability,
threadToolsUpdate, through a new bridge request thread/tools/update.
- The ACP bb-bridge MCP server advertises
tools.listChanged and emits notifications/tools/list_changed exactly once when a name, description or schema changed; unchanged sets and presentation-only changes emit nothing. In-flight calls finish; calls to a removed tool fail with the existing Unknown tool error.
turn.submit to a live session also applies resumeContext.dynamicTools, covering pushes missed while a host was offline.
- Providers that cannot apply tools live keep today's behavior (Codex app-server
dynamicTools; Claude Code's in-process MCP server with fixed allowedTools). Tool instructions remain session-constructed.
No public Plugin SDK API changes; the plugin-authoring docs change from "never hot-mutates" to which providers update live.
Related
Duplicate search (gh search issues --repo get-bb/bb): "list_changed", "dynamic tools", "plugin tools", "tool schema", "bridge tools", "provider session restart", "tools/list_changed", "plugin tool reload running session", "hot-mutate provider session". None reports live sessions keeping a stale plugin tool schema.
Prototype
Fork branch feat/live-plugin-tool-updates: https://github.com/ComicBit/bb/tree/feat/live-plugin-tool-updates (commit 76d093dc62e6ff726e8a7f8430b3084258d43ab6, based on 3912f4aaefb7dff55c7e0cb5003b26b5cf1457dc)
HOST_DAEMON_PROTOCOL_VERSION 234 → 235 (new online commands provider.sessions.list and thread.tools.update; resumeContext.dynamicTools now applies to live sessions). Neither command wakes a host; an older daemon fails the RPC and the server logs it at debug level. The bridge protocol stays at version 2: the capability is optional and old bridges are never sent the request.
- Tests: the real bb-bridge MCP child under an MCP SDK client (advertises
listChanged; a changed schema emits list_changed once and tools/list returns it; same set or presentation-only change emits nothing; in-flight call completes; removed tool fails); ACP and ACP-next bridge thread/tools/update; agent-runtime no_session/unchanged/deferred; daemon dispatch; server test that installs a real path plugin, rewrites and reloads it, and checks the push. Typecheck, lint and tests pass for every touched package, including @bb/server (3439 tests).
Live check: pnpm dev from the branch, the same probe plugin and omp 18.8.7. Each thread kept one provider session and one omp acp process across the reloads.
thr_w7axgj2eyg: before the edit the agent answered a; after adding b and bb plugin reload live-probe it answered a,b in the same session (schema read at 12:54:11Z: type Args = { a?: string; b?: string; };).
- Server log for that reload:
{"status":"applied","threadId":"thr_w7axgj2eyg","toolNames":["update_environment_directory","live_probe"],"msg":"Updated the dynamic tools of a live provider session"}; omp debug log: {"message":"MCP notification received","path":"mcp:bb-bridge","method":"notifications/tools/list_changed"}.
- A tool added by a reload also appeared live in a second thread (
thr_c9iacs8jwz), which had started before the tool was selected.
- Negative control: reloading without changes logged no update and no further
list_changed.
Per CONTRIBUTING.md, I will not open a pull request until this is aligned. Once approved, I will open it from the branch above.
AGENT GENERATED
Workflow I could not complete
A path-installed plugin proxies a local MCP daemon (Aurora Operator) into BB threads with
bb.agents.registerTool. omp under ACP loads MCP servers only from its ACP client, so BB's dynamic-tool bridge is the only way those threads reach the daemon. When the daemon gains an option (a new enum value inaurora_run), the plugin reloads and registers the new schema, but every omp thread that was already running keeps the old schema until its provider session is rebuilt. Agents in long-running threads report the tool as "outdated" and cannot pass the new option.Restarting threads is not a workable answer for agents that run for hours or days, and omp already applies MCP tool-list changes mid-session: the same omp session mounted and unmounted tools from another MCP server when that server's list changed (
xd:// device inventory changednotices at 10:52:50, 11:07:24 and 11:10:07 UTC in the session below).Observed (bb 0.45.0, omp 18.8.7, macOS 26)
thr_6xuag86y3q(acp-omp) started 2026-10-11 10:14 UTC. The plugin had last been loaded before the daemon gainedaurora_run.gemma_phonevaluesextraction-capacity,synthesis-capacityandembedding-public. The plugin was reloaded at ~11:00 UTC. The agent then reported: "the bridge'saurora_runoptions leave outextraction-capacity,synthesis-capacityandembedding-public".Steps to reproduce
Create a path plugin whose server registers one tool and selects it for every provider:
bb plugin install /path/to/plugin --yesStart an omp (ACP) thread and ask: "List the parameters of the live_probe tool."
Add
b: { type: "string" }toproperties, thenbb plugin reload <plugin-id>.In the same thread ask again.
Reproduced from source on
mainat3912f4aaefb7dff55c7e0cb5003b26b5cf1457dcwithpnpm dev(omp 18.8.7, threadthr_wz4xr6e763on the dev server, one provider session01a12b09-594e-7622-834f-4a732b824ea3and oneomp acpprocess throughout):a; the agent's read of the tool schema at 12:56:39Z returnedtype Args = { a?: string; };{"msg":"plugin live-probe@0.1.0 loaded"}; the omp debug log (~/.omp/logs/omp.<date>.<pid>.log) received nonotifications/tools/list_changed.a; the read at 12:57:08Z returnedtype Args = { a?: string; };Expected vs actual
b(the bridge MCP server emitsnotifications/tools/list_changedand serves the new schema).{ a }until the provider session is rebuilt.Root cause (base commit
3912f4aaefb7dff55c7e0cb5003b26b5cf1457dc)thread.start/turn.submit:bb/apps/server/src/services/threads/thread-runtime-config.ts
Lines 213 to 218 in 3912f4a
bb/apps/server/src/services/plugins/plugin-service.ts
Lines 898 to 910 in 3912f4a
turn.submitto an already hosted thread the daemon returns before usingresumeContext.dynamicTools:bb/apps/host-daemon/src/command-handlers/thread.ts
Lines 186 to 218 in 3912f4a
turn/startcarries no tools:bb/packages/provider-bridge-protocol/src/requests.ts
Lines 95 to 103 in 3912f4a
BB_ACP_DYNAMIC_TOOLS) at session creation and advertisestools: {}withoutlistChanged:bb/packages/provider-bridge-acp/src/bridge/bridge.ts
Lines 512 to 534 in 3912f4a
bb/packages/provider-bridge-acp/src/bridge/tool-proxy-mcp.ts
Lines 109 to 125 in 3912f4a
bb/packages/provider-bridge-acp/src/bridge/tool-proxy-mcp.ts
Lines 264 to 293 in 3912f4a
bb/plugins/bb-guide/skills/bb-plugin-authoring/references/backend-cli-agents.md
Lines 254 to 258 in 3912f4a
Proposed behavior
When a plugin change (install, reload, enable/disable, settings change) alters a live thread's resolved dynamic tools, BB pushes the new set to providers that can apply it without rebuilding the session. Policy stays on the server; the daemon applies it to live sessions:
configureresolution used at start/submit and sends them to the daemon.threadToolsUpdate, through a new bridge requestthread/tools/update.tools.listChangedand emitsnotifications/tools/list_changedexactly once when a name, description or schema changed; unchanged sets and presentation-only changes emit nothing. In-flight calls finish; calls to a removed tool fail with the existingUnknown toolerror.turn.submitto a live session also appliesresumeContext.dynamicTools, covering pushes missed while a host was offline.dynamicTools; Claude Code's in-process MCP server with fixedallowedTools). Toolinstructionsremain session-constructed.No public Plugin SDK API changes; the plugin-authoring docs change from "never hot-mutates" to which providers update live.
Related
Duplicate search (
gh search issues --repo get-bb/bb): "list_changed", "dynamic tools", "plugin tools", "tool schema", "bridge tools", "provider session restart", "tools/list_changed", "plugin tool reload running session", "hot-mutate provider session". None reports live sessions keeping a stale plugin tool schema.Prototype
Fork branch
feat/live-plugin-tool-updates: https://github.com/ComicBit/bb/tree/feat/live-plugin-tool-updates (commit76d093dc62e6ff726e8a7f8430b3084258d43ab6, based on3912f4aaefb7dff55c7e0cb5003b26b5cf1457dc)HOST_DAEMON_PROTOCOL_VERSION234 → 235 (new online commandsprovider.sessions.listandthread.tools.update;resumeContext.dynamicToolsnow applies to live sessions). Neither command wakes a host; an older daemon fails the RPC and the server logs it at debug level. The bridge protocol stays at version 2: the capability is optional and old bridges are never sent the request.listChanged; a changed schema emitslist_changedonce andtools/listreturns it; same set or presentation-only change emits nothing; in-flight call completes; removed tool fails); ACP and ACP-next bridgethread/tools/update; agent-runtimeno_session/unchanged/deferred; daemon dispatch; server test that installs a real path plugin, rewrites and reloads it, and checks the push. Typecheck, lint and tests pass for every touched package, including@bb/server(3439 tests).Live check:
pnpm devfrom the branch, the same probe plugin and omp 18.8.7. Each thread kept one provider session and oneomp acpprocess across the reloads.thr_w7axgj2eyg: before the edit the agent answereda; after addingbandbb plugin reload live-probeit answereda,bin the same session (schema read at 12:54:11Z:type Args = { a?: string; b?: string; };).{"status":"applied","threadId":"thr_w7axgj2eyg","toolNames":["update_environment_directory","live_probe"],"msg":"Updated the dynamic tools of a live provider session"}; omp debug log:{"message":"MCP notification received","path":"mcp:bb-bridge","method":"notifications/tools/list_changed"}.thr_c9iacs8jwz), which had started before the tool was selected.list_changed.Per CONTRIBUTING.md, I will not open a pull request until this is aligned. Once approved, I will open it from the branch above.