* feat(db): main agent, main conversation, user model and delegated task foundation
The product needs one main agent that leads a main conversation, knows the
user across spaces, and hands work to visible specialists. Today none of
that exists in the data model: "Chief" is a client-side naming convention,
the "Hauptthread" is a group the web client creates by name, and nothing
stores what the agents know about the user.
This adds the shared foundation the Phase 1 features build on:
- Bot.kind ("main" | "specialist", one main bot per user) plus capability
fields (tags, builtin tool allowlist, model tier).
- ChatGroup.kind ("group" | "main", one main conversation per user) and
ChatGroup.leadBotId.
- UserProfile and UserFact with category, confidence, source, consent
status and per-space visibility; embeddings are stored as float arrays
because none of the Postgres images ship pgvector.
- DelegatedTask for work the main bot hands to a specialist.
- SpaceModelTier to map a model tier to a connected model per space.
The migration backfills each user's onboarding bot in the home space as the
main bot and turns an existing "Hauptthread" group containing it into the
main conversation. CHECK and foreign key constraints on existing tables are
added NOT VALID and validated in a separate migration, as elsewhere in this
repo.
New db helpers ensure the main bot and the main conversation (idempotent and
race-safe, repairing a lost lead), add same-space specialists to the main
conversation and announce it with a group.updated event, and load the user
context a run may see. The main conversation is exempt from the two-member
floor and cannot have its members replaced through a group update. The
contracts expose the new fields and the user model and delegated task
schemas.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* feat(adapters): every run knows who it is and who the user is, in the user's time zone
Runs dropped the bot's name, title and description whenever custom
instructions existed, always stated the time in UTC, and knew the user only
through free-form durable memory. Groups with a lead, like the main
conversation, still got the peer-group lines that forbid handing work back.
And every group follow-up run failed during setup: the inbound handoff lookup
selected a `bot` relation that Message does not have, which Prisma rejects.
- Instructions now open with the bot's identity, then its own instructions,
a main-agent role paragraph for the main bot, the current time in the
user's time zone with its offset (UTC when unknown), and a `<user>` block
built from loadUserContext: profile fields and the active facts visible in
the run's space, escaped, clipped and framed as data rather than
instructions. Runs that reply in a shared messaging channel get only the
name, zone, locale, language and style, matching how those runs already
leave out durable memory.
- run_subagent helpers receive the identity, time and user block through a
new optional AgentRunRequest.subagentContext instead of nothing.
- Groups with an active lead get lead and specialist lines and matching
inbound handoff instructions. Members may hand a stage back to the lead and
the lead may delegate the next stage; the bounce rules still apply between
members. Ordinary groups keep today's lines.
- The teammate directory is rendered only when the run has message_bot, which
group runs never have, and tool guidance names a tool only when the run has
it.
- The auto-review judge, history compaction and the team chat judge read the
time in the user's zone, and schedule_create defaults to it.
- The inbound handoff sender is read from the handoff block and looked up
among the user's bots; a Postgres test covers the follow-up run, the prompt
it receives, and handing the result back to the lead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* feat(memory): learn the user profile from conversations; add built-in semantic memory
Why: the user profile only knew what onboarding wrote, so people had to repeat themselves,
and semantic recall needed a hosted or separately run memory service. The profile now grows
from everyday conversations, and recall can run on the user's own server with a local
embeddings endpoint (for example llama.cpp). Both stay optional and are tested offline.
What:
- adapter-kit: EmbeddingProvider contract (describe, embed) and the profile.consolidate job
(payload schema and a replace-keyed job factory, one pending job per user).
- adapters: OpenAI-compatible embeddings (POST {baseUrl}/embeddings through the guarded
OpenAI-compatible fetch, batching, bounded responses, clear errors), a deterministic
emulator, an in-process /v1/embeddings server emulator, and a shared conformance suite.
- adapters: "builtin" semantic memory stored in Postgres (semantic_memory_items with Float[]
embeddings, cosine ranking in process, bot/shared/history scopes matching the existing
providers), registered in the memory provider factory and configured through
memory.connectProvider with an embeddings URL, model and optional API key. Private
endpoints require the deployment owner, like the other local memory providers.
- adapters: profile consolidation reads the user's settled app conversations since
UserProfile.consolidatedAt (never messaging channels), asks the main bot's model for strict
JSON facts and an updated summary, drops restatements (embedding cosine when built-in
memory is configured, normalized text otherwise), stores sensitive categories as pending,
and advances the cursor with a compare-and-set. A reconciler hook enqueues it at most daily
per active user; it skips under the scripted runtime and without a usable model.
- db: additive migration 20261002210400_ws4_semantic_memory.
- web: a "Built-in" option in Memory settings; AI data sharing names the embeddings server.
Tests: new embedding conformance, OpenAI-compatible wire, built-in provider and consolidation
unit tests; new Postgres suites for built-in memory (including a real loopback embeddings
server through the memory provider resolver) and for consolidation and its scheduling query,
both added to the integration harness. The settings E2E spec now opens the new form.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* feat(models): map model tiers per space and let bots and helpers ask for one
A space can now map the tiers fast, standard and code to connected models,
including several models served by one OpenAI-compatible router endpoint
(one credential, many model ids). A bot can ask for a tier, and a single
run_subagent call can ask for one. A tier with no model mapped falls back
to the usual model choice, so nothing breaks before tiers are set up.
- db: SpaceModelTier repo (list, find, set or clear); tier mappings are
copied into a new space like model and voice preferences; createBot
carries a tier, so duplicates keep it and spawned bots inherit it.
- contracts/api: models.tiers.list and models.tiers.set;
models.probeOpenAiCompatible can list a connected server's models by
credential id with its stored address and key; bots.update accepts
modelTier and keeps a pinned model and a tier mutually exclusive, so a
model picked on a surface that knows no tiers (mobile) is never silently
overridden. Thinking-level validation follows the tier's model. AI
consent status discloses tier models; desktop local settings allow the
tier procedures.
- adapters: selectConfiguredModel runs a mapped tier ahead of the pinned
model and the space default; the executor resolves bot tiers for runs
and model lookups, and helper tiers through resolveModelTier.
run_subagent takes model_tier; an explicit model wins over it.
- web: a collapsed Tiers section in model settings (shown once a model is
connected) and mapped tiers as choices in the bot Model select.
- docs: tiers in self-host model setup.
- tests: resolution order and fallbacks (selection, executor, Pi helper),
repo, API and contract units, authorization and offline Pi Postgres
suites, and a model-tiers web E2E spec.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* fix(adapters): state the consolidation time in the user's zone; name the memory migration after its content
Profile consolidation now passes the profile's time zone to the shared time
instruction, which takes the zone since every run states the time in the
user's zone. The semantic memory migration is renamed after what it creates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* fix(adapters): keep the user's summary and facts out of replies to other owners' agents
An agent connection wakes a bot with another owner's message, and the reply
goes back to that owner. Such runs now get only the basics of the user block
(name, time zone, locale, language, style), like runs in shared messaging
channels, so confirmed facts such as health never reach another person's
agent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* feat(profile): readable, writable and forgettable user model
The main agent is meant to know the user, but nothing could read or change
the user model the foundation added. Onboarding pasted the user's answers
into the first bot's instructions as a "User Context" block, and the user
had no way to see or remove what their bots knew about them.
This makes the user model a user-controlled resource:
- db: a profile repository scoped by user. One atomic update writes the
account name, profile fields, new facts (deduplicated by normalized
text, capped per user), fact edits including per-space visibility, and
confirmations. Health and values facts wait for confirmation unless the
user wrote them. Forgetting hard-deletes facts by id, category or all,
clears the summary that may restate them and marks earlier chats as
consolidated so they cannot teach them again; forgetting everything also
clears the profile fields. Writes take a per-user advisory lock.
- api/contracts: profile.get, profile.update and profile.forget. Unknown
fact ids are a 404 on update and ignored on forget; spaces outside the
user's memberships are rejected.
- adapters: an update_user_profile tool for the main bot only, hidden from
specialists and shared messaging channels and refused if called anyway.
Facts carry source "conversation" and the bot, thread and message they
came from. The scripted runtime records "learn about me: [category:]
statement" for tests.
- web: onboarding writes the name, time zone (browser default, now
prefilled and editable), locale, chosen language, communication style
and the hobby and work-style answers to the profile instead of
overwriting the bot's instructions, and the harvested memory section no
longer repeats them. A new "About you" settings section lists profile
fields and facts by category with their origin, confirms pending facts,
and forgets one fact or everything.
Tests cover the contracts, the repository (unit and PostgreSQL), tool
selection and refusal, the scripted trigger, the onboarding mapping and
view helpers, an API suite that runs the tool through the executor with a
real main bot, authorization for the new procedures, and an E2E spec.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* feat(delegation): main bot hands work to specialists as tracked tasks
The main agent had no way to give a specialist a piece of work and keep
track of it. message_bot is a fire-and-forget peer message whose work stays
hidden from the user, and handoff_to_bot ends the main bot's turn and only
works between members of one group. Delegation now runs the specialist's
work where the user follows the main agent and brings the result back so
the main agent can close the task.
- delegate_task({bot, title, brief, deadline?}) is offered to the main bot
only and refused unless the caller leads the main conversation. It
resolves the specialist by id or name across the user's spaces, creates a
DelegatedTask and posts a task card in the main conversation.
- A specialist from the main conversation's space joins it and works there
in a run with the new trigger "delegation", which the peer filters do not
hide, so its progress, asks and approvals appear in the main conversation
with the specialist as sender. A specialist from another space works in
its own chat; its written result is posted into the main conversation as
its message.
- The executor mirrors the run on its task (running, waiting, done, failed,
cancelled) after the run starts and when each attempt ends, and updates
the card in place through thread.message.updated. Done and failed tasks
wake the main bot in the main conversation with the result. The job
reconciler settles tasks whose run ended without the executor (stop,
clear, archive, deletion, crash).
- tasks.list({threadId?, status?}) returns the caller's tasks for the
current space.
- The task card shows who, what and status with Lingui labels; the brief
opens on demand. The message_bot card no longer shows the recipient's
unrelated overall status or hard-coded German copy.
- The scripted runtime understands "delegate to the bot named X: ..." and
the task wake prompt; activity rows name delegation runs by task title.
Tests: unit tests for the prompts, status mapping, card, reducer, tool
selection and scripted runtime; a Postgres suite for same-space, group,
other-space, ask and answer, failure, stop and replay; tasks.list in the
authorization suite; and an end-to-end spec for the whole flow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* fix(adapters): show the main agent the specialists it can delegate to
The main conversation is a group run, and group runs no longer list the
teammate directory, so the main agent could not see the names of the
specialists delegate_task expects. Runs that have delegate_task now list
the user's specialists across the spaces they belong to, those next to the
conversation first and others marked with their space, and that list
replaces the teammate directory so no bot appears twice. It also says when
to prefer delegate_task over handoff_to_bot.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* feat(inbox): show what specialists need from the user in the main conversation
Specialists ask questions, request approvals and the computer screen in
their own threads, and the user only finds out by opening each thread. With
the main conversation as the single entry point, those requests have to
reach the user there, with the specialist as the visible sender.
- inbox.list returns, for the current space and the user's own bots, pending
asks and approvals (bot messages with a pending ask block whose run waits
for input), takeover requests, and unread specialist threads. Asks already
in the main conversation are left out; takeovers stay because their
controls live in the bot's own thread. Each item carries the bot, the
thread target, run and message ids, and the ask block for asks and
approvals.
- The main conversation shows the items above the composer. Asks and
approvals are answered in place through threads.answer with the source
thread target; takeovers and unread threads open the bot's thread. The
list refreshes when the bots' status, unread state or newest message
changes, every 15 seconds, and after an answer.
- The inbox shrinks and scrolls so it never pushes the composer out of view.
The transcript now clips its scroller: with no room left in the column,
the scroller's padding used to spill over whatever sat below it.
- The ask block schema is extracted from MessageBlock so the inbox contract
reuses it, and AskCard accepts a className.
Tests: unit tests for the item builder and the component, a Postgres suite
for the query (added to the harness list), inbox cases in the authorization
suite, and an E2E spec in which a specialist asks in its own thread and the
user answers from the main conversation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* fix(adapters): write learned facts under the profile's lock and limit
Profile consolidation wrote facts beside the user profile repository: without
its per-user lock, its duplicate check at write time, or its limit of 500
facts. A long-running profile could grow past the limit and then refuse the
user's own additions. Consolidation now takes the same lock, drops facts the
profile gained since the run read it, and keeps only what still fits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* fix(adapters): keep profile writes out of replies to other owners' agents
A run woken over an agent connection hears another owner's bot, not the
user, so update_user_profile is withheld there: nothing such a conversation
says can become a fact about the user. Detection moves ahead of tool
selection; the reduced user block uses the same flag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* feat(capabilities): least-privilege builtin tools per bot and schedules on behalf
Every bot saw every builtin tool, and nothing outside the runtime's own
declaration kept a run to the tools it was given: the executor ran any tool a
runtime asked for, and the Pi runtime exposed all builtins whenever a caller
passed an empty tool list, which the safety checker, the history summarizer
and the team chat judge do on purpose. A specialist could not be kept to the
tools of its lane, bots had no capability tags, and the main bot could only
schedule routines for itself.
- A bot can carry a builtin tool allowlist. Its runs see only the allowlisted
builtins, and an empty allowlist means none. MAIN_ONLY_BUILTIN_TOOLS (empty
until the user profile and delegation tools land) never reach other bots.
applyTool refuses any tool the run did not expose, and an empty tool list
now means no tools for the Pi runtime and its subagents.
- A bot spawned by a restricted bot inherits the restriction and a duplicate
keeps it, so neither path widens a bot's tools.
- bots.create and bots.update accept tags, restrictBuiltinTools and
allowedBuiltinTools; the router rejects names that are not builtin tools.
bots.builtinTools lists the names a bot's allowlist can use.
- Bot settings get a Tags field and a switch with a tool checklist under
Advanced.
- The main bot's schedule_create takes an optional bot: one of the user's
bots in any space the user still belongs to. The routine belongs to that
bot in its space and wakes in the main conversation when the bot is a
member there, otherwise in its 1:1 thread. Routines record the bot that
scheduled them (additive migration), so schedule_list and schedule_cancel
also cover exactly the routines a bot scheduled for others. Cancelling by
a name that matches several routines is refused.
Tested with unit tests for the per-bot filter, the applyTool guard, the empty
tool list, and on-behalf resolution, creation, listing and cancelling; with
Postgres journeys for on-behalf routines waking across spaces and in the main
conversation and for allowlist enforcement through the scripted runtime; and
with a web E2E for the settings section.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* test(adapters): give the consolidation unit tests the profile lock
The write transaction now takes the per-user profile lock, so the mocked
client answers the lock query, and the first case checks the lock is taken.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* feat(adapters): run profile consolidation on the fast tier when one is mapped
Fact extraction is short structured work, so consolidation asks the model
resolver for the fast tier. The resolver takes a per-call tier and, when the
space has not mapped it, keeps the bot's own tier or model as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
* feat: land in the main conversation, led by the main bot
After onboarding the user ended up in a 1:1 chat with "Chief", and the
"Hauptthread" was a group the web client created and found by name. The
main bot had no conversation it actually led, and addressing another bot
there did nothing unless the user had added it as a member first.
Now the main conversation is a real place:
- bots.ensureMain creates or returns the main bot. Bootstrap and space
navigation return `main` ({ botId, groupId, spaceId }) once the main
bot exists; reading navigation never creates a bot.
- The main bot leads the main conversation and answers when nobody is
addressed: message routing, follow-ups, reactions and clearing the
thread fall back to the group's lead instead of its first member.
- Addressing a same-space bot that is not a member yet, by @Name or a
mention chip, makes it join before the message is routed, so it
answers in the conversation. Specialists from other spaces do not join.
- The main bot cannot be archived or deleted, the main conversation
cannot be archived or deleted or have its members edited, and child
bots never dissolve it. Renaming the main bot renames the conversation.
- Web: onboarding uses bots.ensureMain and finishes in the main
conversation. The main conversation is the first sidebar entry in every
space, shown with the main bot's avatar and name; from another space it
switches back to the home space. The main bot is not listed again among
the bots, its 1:1 thread stays reachable by URL. The client no longer
creates or detects the "Hauptthread" by name, refetches a group when it
changes, and shows the speaker of any of the user's bots in a group.
- Onboarding no longer resets the step when its bootstrap resolves after
the user has already moved on, and new spaces open in the app instead
of walking onboarding again.
Tests: group mention routing with a default responder, lead follow-ups,
child-bot guards, a journey for the main conversation (lead answers,
addressed bot joins, guards, rename), authorization for bots.ensureMain,
and an E2E spec that lands in the main conversation, gets an answer from
the lead, sees an addressed bot join, and reaches the conversation from
another space. Existing E2E specs start in the main bot's thread through
a helper or use the main conversation where that is what they test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
---------
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1016 lines
36 KiB
TypeScript
1016 lines
36 KiB
TypeScript
import type { ConnectorTool } from "@rakazo/adapter-kit";
|
||
import {
|
||
BotSecretDestination,
|
||
BotSecretName,
|
||
MODEL_TIERS,
|
||
SecretAskPurpose,
|
||
SecretHttpRequest,
|
||
USER_FACT_CATEGORIES,
|
||
} from "@rakazo/contracts";
|
||
import { z } from "zod";
|
||
|
||
export const SUBAGENT_MODEL_TIER_DESCRIPTION =
|
||
"Optional model tier: fast for quick simple work, code for programming, standard otherwise. Uses the model this space maps to the tier, or your own model when it maps none. Ignored when model_provider and model_id are set.";
|
||
|
||
export const DELEGATION_TOOL_NAMES = new Set([
|
||
"run_subagent",
|
||
"spawn_bot",
|
||
"create_project",
|
||
"archive_bot",
|
||
"delete_bot",
|
||
"handoff_to_bot",
|
||
"message_bot",
|
||
"delegate_task",
|
||
]);
|
||
|
||
export const builtinAgentTools: ConnectorTool[] = [
|
||
{
|
||
name: "computer_observe",
|
||
description:
|
||
"Capture the current screen of this bot's computer. Returns frame metadata and an image. Observe before coordinate-based actions and whenever another actor may have changed the desktop.",
|
||
inputSchema: { type: "object", properties: {} },
|
||
},
|
||
{
|
||
name: "computer_act",
|
||
description:
|
||
"Perform up to 24 ordered desktop actions on this bot's computer and return the resulting screen. Batch only predictable actions; stop before an outcome you need to inspect. Action kinds: click, move, down, up, type, key, scroll, wait.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
actions: {
|
||
type: "array",
|
||
items: {
|
||
type: "object",
|
||
properties: {
|
||
kind: {
|
||
type: "string",
|
||
enum: ["click", "move", "down", "up", "type", "key", "scroll", "wait"],
|
||
},
|
||
x: { type: "number" },
|
||
y: { type: "number" },
|
||
button: { type: "string", enum: ["left", "right"] },
|
||
double: { type: "boolean" },
|
||
text: { type: "string" },
|
||
key: { type: "string" },
|
||
modifiers: { type: "array", items: { type: "string" } },
|
||
direction: { type: "string", enum: ["up", "down"] },
|
||
amount: { type: "number" },
|
||
ms: { type: "number" },
|
||
},
|
||
required: ["kind"],
|
||
},
|
||
},
|
||
observe: { type: "boolean" },
|
||
settle_ms: { type: "number" },
|
||
},
|
||
required: ["actions"],
|
||
},
|
||
},
|
||
|
||
{
|
||
name: "browser_navigate",
|
||
description:
|
||
'Open a URL in the page browser on this bot\'s computer and return the document title. Prefer this over pixel clicks for web pages. If the result includes fallback:"computer_act", use computer_act on the desktop browser instead.',
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
url: { type: "string", description: "http(s) URL to open." },
|
||
},
|
||
required: ["url"],
|
||
},
|
||
},
|
||
{
|
||
name: "browser_snapshot",
|
||
description:
|
||
"Capture an accessibility-style snapshot of the current page with element refs (e1, e2, …). Use refs with browser_act. Prefer this over computer_observe for web pages. If fallback is computer_act, use the desktop tools instead.",
|
||
inputSchema: { type: "object", properties: {} },
|
||
readOnly: true,
|
||
},
|
||
{
|
||
name: "browser_act",
|
||
description:
|
||
'Click or fill page elements by ref from browser_snapshot (kinds: click, fill, type). Prefer this over computer_act for web pages. If the result includes fallback:"computer_act", use computer_act instead.',
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
actions: {
|
||
type: "array",
|
||
items: {
|
||
type: "object",
|
||
properties: {
|
||
kind: { type: "string", enum: ["click", "fill", "type"] },
|
||
ref: { type: "string", description: "Element ref from browser_snapshot." },
|
||
text: { type: "string", description: "Text for fill or type." },
|
||
},
|
||
required: ["kind", "ref"],
|
||
},
|
||
},
|
||
},
|
||
required: ["actions"],
|
||
},
|
||
},
|
||
{
|
||
name: "list_files",
|
||
description:
|
||
"List files and directories in this bot's home. On a Team Computer, relative paths use the bot folder; use shared/... for shared work or bots/... to inspect the Team root.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: { path: { type: "string" } },
|
||
},
|
||
},
|
||
{
|
||
name: "read_file",
|
||
description:
|
||
"Read a UTF-8 text file from this bot's home. On a Team Computer, relative paths use the bot folder and shared/... accesses shared work. Open visual or binary files with open_path instead.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: { path: { type: "string" } },
|
||
required: ["path"],
|
||
},
|
||
},
|
||
{
|
||
name: "write_file",
|
||
description:
|
||
"Write a UTF-8 file into this bot's home. On a Team Computer, relative paths use the bot folder; use shared/... only for work other bots should share.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
path: { type: "string" },
|
||
content: { type: "string" },
|
||
},
|
||
required: ["path", "content"],
|
||
},
|
||
},
|
||
{
|
||
name: "attach_file",
|
||
description:
|
||
"Attach a workspace file from this bot's home to the chat thread as an image or common file. The file stays in place; users can open it from the message.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: { path: { type: "string" } },
|
||
required: ["path"],
|
||
},
|
||
},
|
||
{
|
||
name: "shell",
|
||
description:
|
||
"Run a command inside this bot's computer. cwd defaults to the bot's folder on a Team Computer and the workspace root on a Private Computer.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
command: { type: "string" },
|
||
cwd: { type: "string" },
|
||
},
|
||
required: ["command"],
|
||
},
|
||
},
|
||
{
|
||
name: "open_path",
|
||
description:
|
||
"Open a workspace file or an http(s) URL in its default graphical application on this bot's computer and return the resulting screen.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: { path: { type: "string" } },
|
||
required: ["path"],
|
||
},
|
||
},
|
||
{
|
||
name: "launch_app",
|
||
description:
|
||
"Launch an installed graphical application on this bot's computer, optionally with a URI, and return the resulting screen.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
application: { type: "string" },
|
||
uri: { type: "string" },
|
||
},
|
||
required: ["application"],
|
||
},
|
||
},
|
||
{
|
||
name: "request_takeover",
|
||
description:
|
||
"Ask the user to take over the computer screen for passwords, 2FA, CAPTCHA, payment, passkeys, or other protected input. Never ask the user to paste protected values in chat.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: { reason: { type: "string" } },
|
||
required: ["reason"],
|
||
},
|
||
},
|
||
{
|
||
name: "ask_user",
|
||
description:
|
||
"Ask the user one short multiple-choice question with tappable options, then wait for their selection. Use this instead of asking them to type when two to four concise choices are enough.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
question: { type: "string", maxLength: 240 },
|
||
options: {
|
||
type: "array",
|
||
items: { type: "string", minLength: 1, maxLength: 80 },
|
||
minItems: 2,
|
||
maxItems: 4,
|
||
uniqueItems: true,
|
||
},
|
||
},
|
||
required: ["question", "options"],
|
||
},
|
||
},
|
||
{
|
||
name: "message_user",
|
||
description:
|
||
"Post a short progress update to the user in this chat immediately. Does not end your turn. Use sparingly during long work for high-signal beats (what you are checking, then a result). Do not dump tool logs, thinking, or a play-by-play of every call. HARD LIMIT: cut off silently at 500 characters, so never put your final answer, a report, or any long-form content here \u2014 it will arrive mangled and the user will never see the rest. Always write your complete final answer in your normal reply, not here.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
message: {
|
||
type: "string",
|
||
maxLength: 500,
|
||
description:
|
||
"Short user-visible update, not the final answer \u2014 longer text is silently truncated.",
|
||
},
|
||
},
|
||
required: ["message"],
|
||
},
|
||
},
|
||
{
|
||
name: "request_secret",
|
||
description:
|
||
"Collect a credential in a masked field. Supply credential to save a named API credential for this bot and user at one HTTPS origin, or connectionId for a one-use connector code. Existing named credentials are reused unless replace is true. For website logins, CAPTCHA, passkeys, or anything that needs the live desktop, call request_takeover instead.",
|
||
// Exactly one destination: credential XOR connectionId. Sibling optionals
|
||
// looked schema-valid to models but the executor rejects both and neither.
|
||
inputSchema: {
|
||
oneOf: [
|
||
{
|
||
type: "object",
|
||
properties: {
|
||
label: { type: "string" },
|
||
purpose: { type: "string", enum: SecretAskPurpose.options },
|
||
credential: z.toJSONSchema(BotSecretDestination),
|
||
replace: {
|
||
type: "boolean",
|
||
description: "Ask the user to replace an existing credential value.",
|
||
},
|
||
},
|
||
required: ["label", "purpose", "credential"],
|
||
additionalProperties: false,
|
||
},
|
||
{
|
||
type: "object",
|
||
properties: {
|
||
label: { type: "string" },
|
||
purpose: { type: "string", enum: SecretAskPurpose.options },
|
||
connectionId: { type: "string" },
|
||
},
|
||
required: ["label", "purpose", "connectionId"],
|
||
additionalProperties: false,
|
||
},
|
||
],
|
||
},
|
||
},
|
||
{
|
||
name: "list_secrets",
|
||
description:
|
||
"List saved credential names and destinations available to this bot and user. Values are never returned.",
|
||
inputSchema: { type: "object", properties: {} },
|
||
},
|
||
{
|
||
name: "secret_request",
|
||
description:
|
||
"Make an authenticated HTTPS request using a saved credential name. The backend injects authentication only at its saved origin. Redirects are rejected; response echoes of the credential are redacted. Use normal request URLs and bodies without secret placeholders.",
|
||
inputSchema: z.toJSONSchema(SecretHttpRequest, { io: "input" }),
|
||
},
|
||
{
|
||
name: "forget_secret",
|
||
description:
|
||
"Remove a saved credential for this bot and user, preventing future requests from using it.",
|
||
inputSchema: z.toJSONSchema(z.object({ name: BotSecretName })),
|
||
},
|
||
{
|
||
name: "render_plot",
|
||
description:
|
||
'Render a chart from tabular data as a PNG and attach it to the chat. Backed by Observable Plot: bar, line, area, scatter, histogram, heatmap, box plot, facets, and more via a declarative JSON spec. Call with {"charts": true} FIRST to list every chart type with a complete runnable example spec ({"charts": "<keyword>"} searches), then copy the closest example and substitute your rows and columns. {"help": true} returns the full guide. Pass rows inline as data, or data_path for a .csv/.tsv/.json file in your home.',
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
charts: {
|
||
description:
|
||
'true lists all chart types with runnable example specs; a keyword string (e.g. "distribution", "share", "trend") searches them.',
|
||
},
|
||
help: {
|
||
type: "boolean",
|
||
description: "Return the full render_plot skill guide instead of rendering.",
|
||
},
|
||
spec: {
|
||
type: "object",
|
||
description:
|
||
"Declarative Observable Plot spec: {title?, width?, height?, x?, y?, color?, fx?, fy?, marks: [{type, options, transform?, data?}]}.",
|
||
},
|
||
data: {
|
||
type: "array",
|
||
description: "Rows as objects, shared by marks without their own data.",
|
||
},
|
||
data_path: {
|
||
type: "string",
|
||
description:
|
||
"Workspace path of a .csv, .tsv, or .json rows file to load instead of inline data.",
|
||
},
|
||
path: {
|
||
type: "string",
|
||
description: "Output PNG path in this bot's home. Default charts/plot-<n>.png.",
|
||
},
|
||
attach: {
|
||
type: "boolean",
|
||
description: "Attach the rendered PNG to the chat (default true).",
|
||
},
|
||
},
|
||
},
|
||
},
|
||
{
|
||
name: "add_mcp_server",
|
||
description:
|
||
"Connect an MCP tool server to this Space when the user asks you to add one and provides the details (URL or command, optional token/headers/env). The server is created immediately and assigned to you. If it needs browser OAuth authorization, an approval card appears in the chat for the user to complete — tell them to click Authorize. Do not invent endpoints; only use details the user provided.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
name: { type: "string", description: 'Display name, e.g. "Brex".' },
|
||
transport: {
|
||
type: "string",
|
||
enum: ["streamable_http", "sse", "stdio"],
|
||
description:
|
||
"streamable_http for modern HTTP servers, sse for legacy HTTP servers, stdio for local commands.",
|
||
},
|
||
endpoint: {
|
||
type: "string",
|
||
description: "HTTPS URL of the remote MCP server (required unless transport is stdio).",
|
||
},
|
||
command: {
|
||
type: "string",
|
||
description:
|
||
"Executable path for stdio transport (required for stdio). Must be allowlisted by the deployment.",
|
||
},
|
||
args: {
|
||
type: "array",
|
||
items: { type: "string" },
|
||
description:
|
||
"Arguments for the stdio command. A single space-separated string also works.",
|
||
},
|
||
env: {
|
||
type: "object",
|
||
description: 'Environment variables for stdio transport, e.g. {"API_KEY": "..."}.',
|
||
},
|
||
headers: {
|
||
type: "object",
|
||
description: 'HTTP headers for remote transports, e.g. {"Authorization": "Bearer ..."}.',
|
||
},
|
||
secret: {
|
||
type: "string",
|
||
description: "Static access token, equivalent to an Authorization: Bearer header.",
|
||
},
|
||
assign_to_self: {
|
||
type: "boolean",
|
||
description:
|
||
"Assign the server to you so its tools are usable in this conversation (default true).",
|
||
},
|
||
},
|
||
required: ["name", "transport"],
|
||
},
|
||
},
|
||
{
|
||
name: "remember",
|
||
description: "Store a durable fact in this bot's explicit memory.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
content: { type: "string" },
|
||
path: { type: "string" },
|
||
},
|
||
required: ["content"],
|
||
},
|
||
},
|
||
{
|
||
name: "update_user_profile",
|
||
description:
|
||
"Save what you learned about the user so all of their bots can use it: short facts and profile settings. Save only what the user said or clearly confirmed, never guesses. Health and values facts stay hidden from bots until the user confirms them.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
facts: {
|
||
type: "array",
|
||
maxItems: 20,
|
||
items: {
|
||
type: "object",
|
||
properties: {
|
||
category: { type: "string", enum: [...USER_FACT_CATEGORIES] },
|
||
value: {
|
||
type: "string",
|
||
description: 'One short statement, e.g. "Works night shifts as a nurse".',
|
||
},
|
||
},
|
||
required: ["category", "value"],
|
||
},
|
||
},
|
||
time_zone: { type: "string", description: "IANA time zone, e.g. Europe/Vienna." },
|
||
language: {
|
||
type: "string",
|
||
description: "Language the user wants replies in, as a BCP-47 tag, e.g. de.",
|
||
},
|
||
communication_style: {
|
||
type: "string",
|
||
description: 'How the user wants to be talked to, e.g. "brief and informal".',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
{
|
||
name: "web_search",
|
||
description:
|
||
"Search the public web. Returns titles, URLs, and snippets. Use when you need current information or links; follow with web_fetch to read a page. Does not need a computer.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
query: { type: "string", description: "Search query." },
|
||
maxResults: {
|
||
type: "number",
|
||
description: "Max results to return (default 5, max 10).",
|
||
},
|
||
},
|
||
required: ["query"],
|
||
},
|
||
readOnly: true,
|
||
},
|
||
{
|
||
name: "web_fetch",
|
||
description:
|
||
"Fetch a public http(s) page and return readable text (title + content). Read-only; no JavaScript. Use after web_search when you need the page itself.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
url: { type: "string", description: "Page URL (http or https)." },
|
||
maxChars: {
|
||
type: "number",
|
||
description: "Max characters of body text (default 8000).",
|
||
},
|
||
},
|
||
required: ["url"],
|
||
},
|
||
readOnly: true,
|
||
},
|
||
{
|
||
name: "cloud_agent_launch",
|
||
description:
|
||
"Launch a remote cloud coding agent on a connected repository. Returns immediately with a tracking id and status; the agent link appears when launched. Opens a PR when openPr is true. Not the bot computer.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
prompt: { type: "string", description: "Task for the remote agent." },
|
||
repository: {
|
||
type: "string",
|
||
description: "Git repository URL (optional for no-repo agents).",
|
||
},
|
||
openPr: {
|
||
type: "boolean",
|
||
description: "Open a pull request when the run finishes.",
|
||
},
|
||
images: {
|
||
type: "array",
|
||
description: "Optional images (data+mimeType or url).",
|
||
items: { type: "object" },
|
||
},
|
||
},
|
||
required: ["prompt"],
|
||
},
|
||
},
|
||
{
|
||
name: "cloud_agent_status",
|
||
description: "Get status, branch, and PR url for a cloud coding agent.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
id: { type: "string", description: "Cloud agent id from launch." },
|
||
},
|
||
required: ["id"],
|
||
},
|
||
readOnly: true,
|
||
},
|
||
{
|
||
name: "cloud_agent_reply",
|
||
description: "Send a follow-up prompt to an existing cloud coding agent.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
id: { type: "string", description: "Cloud agent id." },
|
||
prompt: { type: "string", description: "Follow-up instruction." },
|
||
images: {
|
||
type: "array",
|
||
description: "Optional images (data+mimeType or url).",
|
||
items: { type: "object" },
|
||
},
|
||
},
|
||
required: ["id", "prompt"],
|
||
},
|
||
},
|
||
{
|
||
name: "cloud_agent_cancel",
|
||
description: "Cancel the active run on a cloud coding agent.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
id: { type: "string", description: "Cloud agent id." },
|
||
},
|
||
required: ["id"],
|
||
},
|
||
},
|
||
// Semantic-memory tools: exposed by selectMemoryTools() only when a
|
||
// A Space memory provider is configured (which hides `remember`).
|
||
{
|
||
name: "save_memory",
|
||
description:
|
||
"Store a durable fact in this bot's semantic memory (preferences, decisions, recurring context). Use for anything worth recalling in future conversations.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
content: { type: "string" },
|
||
},
|
||
required: ["content"],
|
||
},
|
||
},
|
||
{
|
||
name: "recall_memory",
|
||
description: "Semantically search this bot's durable memory for facts relevant to a query.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
query: { type: "string" },
|
||
},
|
||
required: ["query"],
|
||
},
|
||
},
|
||
{
|
||
name: "forget_memory",
|
||
description:
|
||
"Forget a durable semantic memory by id (from recall citations). Providers without forget support return an error.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
id: { type: "string", description: "Memory id from a prior recall citation." },
|
||
entity: {
|
||
type: "string",
|
||
description:
|
||
"Optional entity/namespace from the recall citation when the provider scopes deletes.",
|
||
},
|
||
reason: { type: "string", description: "Optional reason recorded with the forget." },
|
||
},
|
||
required: ["id"],
|
||
},
|
||
},
|
||
{
|
||
name: "scratchpad_list",
|
||
description:
|
||
"List this bot's scratchpad / open-work items (todos and parked work). By default omits completed items.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
includeDone: {
|
||
type: "boolean",
|
||
description: "When true, include completed items.",
|
||
},
|
||
},
|
||
},
|
||
},
|
||
{
|
||
name: "scratchpad_add",
|
||
description:
|
||
"Add an open-work item to this bot's scratchpad. Use for todos or parked work that should outlive this turn. Not a reminder or schedule.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
title: { type: "string", description: "Short title for the item." },
|
||
status: {
|
||
type: "string",
|
||
enum: ["open", "parked", "done"],
|
||
description: "Defaults to open.",
|
||
},
|
||
notes: { type: "string", description: "Optional notes." },
|
||
},
|
||
required: ["title"],
|
||
},
|
||
},
|
||
{
|
||
name: "scratchpad_update",
|
||
description: "Update a scratchpad item's title, status, or notes.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
itemId: { type: "string" },
|
||
title: { type: "string" },
|
||
status: { type: "string", enum: ["open", "parked", "done"] },
|
||
notes: { type: "string" },
|
||
},
|
||
required: ["itemId"],
|
||
},
|
||
},
|
||
{
|
||
name: "scratchpad_complete",
|
||
description: "Mark a scratchpad item done.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
itemId: { type: "string" },
|
||
},
|
||
required: ["itemId"],
|
||
},
|
||
},
|
||
{
|
||
name: "scratchpad_remove",
|
||
description: "Permanently remove a scratchpad item.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
itemId: { type: "string" },
|
||
},
|
||
required: ["itemId"],
|
||
},
|
||
},
|
||
{
|
||
name: "schedule_create",
|
||
description:
|
||
'Create a reminder or recurring job for this bot. Use for "remind me in 10 minutes" or "every morning send a joke". Repeats: cron or every/unit (min 1 minute). One-shot: runAt, delayMinutes, or delaySeconds.',
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
name: { type: "string", description: "Short label shown in Routines." },
|
||
prompt: {
|
||
type: "string",
|
||
description:
|
||
"Concrete steps for when the schedule fires: name the connected plugin tools to call (e.g. GITHUB_LIST_RELEASES for owner/repo), what to extract, and how to report. Prefer plugin tools over computer browser or web search for app data.",
|
||
},
|
||
cron: { type: "string", description: "5-field cron for repeating schedules." },
|
||
every: { type: "number", description: "Repeat interval amount for repeating schedules." },
|
||
unit: {
|
||
type: "string",
|
||
enum: ["minutes", "hours", "days"],
|
||
description: "Unit for every (minimum 1 minute).",
|
||
},
|
||
runAt: {
|
||
type: "string",
|
||
description: "ISO datetime for a one-shot schedule.",
|
||
},
|
||
delayMinutes: {
|
||
type: "number",
|
||
description: "Minutes from now for a one-shot schedule.",
|
||
},
|
||
delaySeconds: {
|
||
type: "number",
|
||
description: "Seconds from now for a one-shot schedule (may be under one minute).",
|
||
},
|
||
timezone: {
|
||
type: "string",
|
||
description: "IANA timezone (default: the user's time zone, else UTC).",
|
||
},
|
||
},
|
||
required: ["name", "prompt"],
|
||
},
|
||
},
|
||
{
|
||
name: "schedule_list",
|
||
description: "List this bot's active and inactive schedules (routines).",
|
||
inputSchema: { type: "object", properties: {} },
|
||
},
|
||
{
|
||
name: "schedule_cancel",
|
||
description: "Cancel a schedule by routineId or exact name.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
routineId: { type: "string" },
|
||
name: { type: "string" },
|
||
},
|
||
},
|
||
},
|
||
{
|
||
name: "skill_read",
|
||
description:
|
||
"Load a Claude Agent Skill (SKILL.md recipe) by exact name. Call this when a catalog skill matches the user's request, then follow it immediately.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
name: { type: "string", description: "Exact skill name from the catalog." },
|
||
},
|
||
required: ["name"],
|
||
},
|
||
},
|
||
{
|
||
name: "skill_create",
|
||
description:
|
||
"Create a reusable Claude Agent Skill (generic how-to SKILL.md) shared across assistants. The Pi runtime already understands this format; we persist and inject them. Use when a multi-step task is worth repeating or the user asks to save a skill. Do not include account names, channels, or inboxes — those belong in a routine.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
name: { type: "string", description: "Short skill name." },
|
||
description: {
|
||
type: "string",
|
||
description: "When to use this skill (shown in the / picker and used for auto-use).",
|
||
},
|
||
body: {
|
||
type: "string",
|
||
description: "Markdown steps and guidance after the frontmatter.",
|
||
},
|
||
content: {
|
||
type: "string",
|
||
description:
|
||
"Optional full SKILL.md (frontmatter + body) instead of name/description/body.",
|
||
},
|
||
},
|
||
},
|
||
},
|
||
{
|
||
name: "skill_update",
|
||
description:
|
||
"Update a user-created skill by name or id. Builtin and plugin skills are read-only.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
name: { type: "string", description: "Current exact skill name." },
|
||
skillId: { type: "string" },
|
||
newName: { type: "string" },
|
||
description: { type: "string" },
|
||
body: { type: "string" },
|
||
content: { type: "string", description: "Optional full replacement SKILL.md." },
|
||
},
|
||
},
|
||
},
|
||
{
|
||
name: "skill_delete",
|
||
description:
|
||
"Delete a user-created skill by name or id. Builtin and plugin skills cannot be deleted.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
name: { type: "string" },
|
||
skillId: { type: "string" },
|
||
},
|
||
},
|
||
},
|
||
{
|
||
name: "run_subagent",
|
||
description:
|
||
"Run a short-lived helper inside this turn only. It is not a bot: no list entry, no thread, no computer of its own, and it disappears when this turn ends. Never call this because the user asked to create a bot — that is spawn_bot, and spawn_bot alone.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
name: {
|
||
type: "string",
|
||
description: "Short label shown in the thread, e.g. scout or reviewer.",
|
||
},
|
||
task: { type: "string", description: "The work the helper should complete." },
|
||
instructions: {
|
||
type: "string",
|
||
description: "Optional extra system instructions for the helper.",
|
||
},
|
||
model_provider: {
|
||
type: "string",
|
||
description: "Optional connected model provider. Set together with model_id.",
|
||
},
|
||
model_id: {
|
||
type: "string",
|
||
description: "Optional connected model ID. Set together with model_provider.",
|
||
},
|
||
model_tier: {
|
||
type: "string",
|
||
enum: [...MODEL_TIERS],
|
||
description: SUBAGENT_MODEL_TIER_DESCRIPTION,
|
||
},
|
||
},
|
||
required: ["name", "task"],
|
||
},
|
||
},
|
||
{
|
||
name: "create_project",
|
||
description:
|
||
"Create a project group when the user asks for a new project or team workspace. Projects are shared chats for 2–6 agents. Always include yourself plus the relevant teammates in bot_ids. Creating the project is the whole action unless the user also asked to start work in it.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
name: {
|
||
type: "string",
|
||
minLength: 1,
|
||
maxLength: 80,
|
||
description: 'Short project name, e.g. "Website Redesign".',
|
||
},
|
||
bot_ids: {
|
||
type: "array",
|
||
items: { type: "string" },
|
||
minItems: 1,
|
||
description:
|
||
"Agent ids to invite. You are added automatically; include at least one other teammate.",
|
||
},
|
||
},
|
||
required: ["name", "bot_ids"],
|
||
},
|
||
},
|
||
{
|
||
name: "create_space",
|
||
description:
|
||
"Propose a new space in the current organization when the user asks for a separate data boundary. A space can contain many bots and groups, but its chats, files, memory, tools, and integrations stay isolated from other spaces. This always shows the user a confirmation card before creation. Creating the space is the whole action; do not create bots in it unless the user asks later.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
name: {
|
||
type: "string",
|
||
minLength: 1,
|
||
maxLength: 60,
|
||
description: 'Short display name, e.g. "Customer support".',
|
||
},
|
||
},
|
||
required: ["name"],
|
||
},
|
||
},
|
||
{
|
||
name: "spawn_bot",
|
||
description:
|
||
"Create a full, regular bot — the same kind the user creates from the + button. It gets its own thread, computer, and memory, and appears as a peer in the bot list. Do not also call run_subagent. Creating the bot is the whole action. Only set prompt if the user asked that new bot to start work immediately.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
name: { type: "string" },
|
||
title: { type: "string" },
|
||
instructions: { type: "string" },
|
||
prompt: {
|
||
type: "string",
|
||
description: "Optional first task to run in the new bot's thread.",
|
||
},
|
||
computer_mode: {
|
||
type: "string",
|
||
enum: ["team", "dedicated"],
|
||
description:
|
||
"Optional. team shares one screen with other Team bots; dedicated (Private) gets its own. Defaults to team.",
|
||
},
|
||
},
|
||
required: ["name"],
|
||
},
|
||
},
|
||
{
|
||
name: "update_bot",
|
||
description:
|
||
"Update this bot's own profile fields that the user sees in chat: name (header and list label), title (short role line), description, speaking voice, and whether replies are read aloud. Call this when the user asks you to rename yourself, change your title/description, pick a different voice (e.g. Eva, Bernd), or turn speaking on/off. Do not claim you updated the profile without calling this tool.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
name: {
|
||
type: "string",
|
||
description: "Display name shown in the chat header and bot list.",
|
||
},
|
||
title: {
|
||
type: "string",
|
||
description: "Short role or headline shown in bot settings.",
|
||
},
|
||
description: {
|
||
type: "string",
|
||
description: "Longer blurb describing what this bot does.",
|
||
},
|
||
voice_id: {
|
||
type: "string",
|
||
description:
|
||
"Voice id from the connected provider (e.g. df_eva, dm_bernd). Match the user's requested voice name.",
|
||
},
|
||
auto_speak: {
|
||
type: "boolean",
|
||
description: "When true, read replies aloud. When false, stay silent.",
|
||
},
|
||
},
|
||
},
|
||
},
|
||
{
|
||
name: "archive_bot",
|
||
description:
|
||
"Archive a bot this bot created. Archiving stops its work and routines, hides it from the active list, and preserves its conversation, memory, and files for the user to restore or delete later. confirm_name must exactly match its name. This cannot archive you, bots the user created, or bots another bot created.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
confirm_name: { type: "string", description: "Exact current name of the bot to archive." },
|
||
bot_id: {
|
||
type: "string",
|
||
description:
|
||
"Optional bot id. If omitted, the unique bot this bot created with confirm_name is archived.",
|
||
},
|
||
},
|
||
required: ["confirm_name"],
|
||
},
|
||
},
|
||
{
|
||
name: "message_bot",
|
||
description:
|
||
'Send a useful update, question, or result to another of the user\'s bots. You must call this tool to actually deliver it — writing the message in your own reply text (e.g. "[to Comms] ...") does not send anything and the recipient never sees it. Delivery is async and does not end your turn. Continue independent work; do not poll or send ack-only messages. Later updates only if they add something new.',
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
bot_id: { type: "string", description: "Target bot id from your teammate list." },
|
||
confirm_name: {
|
||
type: "string",
|
||
description: "Exact name of the target bot when bot_id is omitted.",
|
||
},
|
||
message: { type: "string", description: "What to send." },
|
||
intent: {
|
||
type: "string",
|
||
enum: ["request", "result", "question", "status", "fyi"],
|
||
description: "What the recipient should do with this message. Defaults to request.",
|
||
},
|
||
},
|
||
required: ["message"],
|
||
},
|
||
},
|
||
{
|
||
name: "handoff_to_bot",
|
||
description:
|
||
"In a group chat only: transfer a genuinely distinct next stage to another current member. Appends a visible handoff and starts that bot asynchronously. Do not hand a stage back merely to report or repeat the same work; post results in the shared thread. Never paste this tool as JSON in chat — invoke it directly.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
bot_id: { type: "string", description: "Target member bot id." },
|
||
confirm_name: {
|
||
type: "string",
|
||
description: "Exact name of the target member when bot_id is omitted.",
|
||
},
|
||
message: { type: "string", description: "What the receiving bot should do next." },
|
||
},
|
||
required: ["message"],
|
||
},
|
||
},
|
||
{
|
||
name: "delegate_task",
|
||
description:
|
||
"Main agent only: hand a distinct piece of work to one of the user's specialists as a tracked task. A task card appears in the main conversation; a specialist from this space joins it and works there where the user can follow, one from another space works in its own chat. You are woken in the main conversation with the result to close the task with the user. Does not end your turn; do not poll.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
bot: { type: "string", description: "Specialist name or id." },
|
||
title: {
|
||
type: "string",
|
||
maxLength: 120,
|
||
description: "Short task title shown on the card.",
|
||
},
|
||
brief: {
|
||
type: "string",
|
||
description:
|
||
"Everything the specialist needs: goal, context, constraints, and what to return.",
|
||
},
|
||
deadline: {
|
||
type: "string",
|
||
description: "Optional ISO 8601 date-time the result is needed by.",
|
||
},
|
||
},
|
||
required: ["bot", "title", "brief"],
|
||
},
|
||
},
|
||
];
|
||
|
||
/** Agent-connection tools, exposed only when the messaging surface is enabled. */
|
||
export const agentConnectionTools: ConnectorTool[] = [
|
||
{
|
||
name: "connect_agent",
|
||
description:
|
||
"Request a standing connection to another person's agent by their owner's chat address. The other owner must approve before either agent can message the other. Only for agents whose owner messaged the deployment's chat line.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
address: {
|
||
type: "string",
|
||
description:
|
||
"The owner's address on the chat surface: an E.164 phone number (e.g. +15551234567) or platform user id.",
|
||
},
|
||
},
|
||
required: ["address"],
|
||
},
|
||
},
|
||
{
|
||
name: "respond_agent_connection",
|
||
description:
|
||
"Approve or decline the newest pending agent connection request addressed to you. Use only on your owner's explicit instruction.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
accept: { type: "boolean", description: "true to approve, false to decline." },
|
||
},
|
||
required: ["accept"],
|
||
},
|
||
},
|
||
{
|
||
name: "message_agent",
|
||
description:
|
||
"Send a useful update, question, or result to another person's agent over an approved connection. Delivery is async and does not end your turn. Continue independent work; do not poll or send ack-only messages.",
|
||
inputSchema: {
|
||
type: "object",
|
||
properties: {
|
||
address: {
|
||
type: "string",
|
||
description:
|
||
"Chat address (phone number or platform user id) of the connected agent's owner.",
|
||
},
|
||
message: { type: "string", description: "What to send." },
|
||
},
|
||
required: ["address", "message"],
|
||
},
|
||
},
|
||
];
|