Skip to main content
Adding to BOR’s tool surface is two steps: a handler module and a registry entry. The prompt docs, the executor, and both LLM harnesses pick it up automatically.

1. Write the handler

Create (or extend) a module under server/protocol/handlers/. Export an execute(block, ctx) that returns the standard result shape:

The result contract

Keep llmEcho compact — it’s read every following pass. Never throw; return a tool_error result.

2. Register it

Add one entry to TAGS in server/protocol/registry.js:
Import your module at the top of registry.js (import * as myfeature from './handlers/myfeature.js'). That’s it. buildToolDocs() renders the new tool into the system prompt automatically, the executor invokes your handler, and the new harness shims it.

3. (Optional) render a custom card

If you want a richer bubble card than the default activity row, handle your event in host/presence.js:
  • Add a TOOL_META entry (my_tool: ['Label', 'subtitle', '◆']).
  • Add a tag_start branch (open a card) and a result-event branch (finalize it) in handleChatEvent.
  • Add a card type + renderer, and styles in presence.css.
The browser, terminal, editor, and wait cards are all examples of this pattern.

Notes & gotchas

  • Children & attributes. The new-harness shim treats children as flat string properties; tools whose children carry XML attributes need handler-side schema overrides. Prefer attributes for scalars.
  • Opaque content tags. If your tool’s body is file-like content, the parser already handles CDATA robustly — anchor on your own close tag, not CDATA.
  • Audit. Anything that mutates state should call ctx.audit(...).
  • Safety. If your tool runs shell or writes files, route through the validator / path guards.

Checklist

  • Handler returns { ok, event, payload, llmEcho } (+ optional llmMedia).
  • Registry entry with description, params, examples, voiced, feedsBack, execute.
  • Module imported in registry.js.
  • (Optional) presence card + CSS.
  • node --check the changed files.