Skip to main content
Each recipe below starts from a goal, shows the call, and explains what the agent does and does not do. They go from the simplest (one message) to the most complete (a thread policy plus per-message changes). If you want the definitions first, read Capabilities.

Before you start

Every snippet runs inside an AsyncCominty context and uses these placeholders. Replace them with your own values.
Not sure where to find an id? See Find your ids.

The example agent

Every recipe starts from the same agent: agent.chat, the built-in Chat agent. It has no capabilities saved, so every capability is always, with no filter. The recipes work the same with Hive. They do not apply to Planner, which ignores capabilities. See Built-in agents. Each recipe opens with its Starting point: what the agent has before the call, and what the recipe changes. Where a recipe says “the agent can”, it means the agent can use it if the question calls for it. A capability that is on does not force the agent to use it. That is why the messages name what they want (“in my documents”, “without searching the web”): the scope enforces, the words guide the agent. See Help the agent follow a scope.
If you use your own agent, check its capabilities on the Agents page first. A scope on a capability your agent sets to never returns a 422. So does enabled=False on a capability it sets to on_request.

Recipe 1: skip the web for one message

Goal: “Answer this question from what you know. Do not search the web.” Starting point: the example agent. web is always, so the agent may search the web on any message. This recipe turns it off for one message.
▶ examples/11_capabilities.py Expected behavior: The agent answers the first question from what it already knows. Web search is off for this message, and the message says so. The agent still sees the web tool, but a call to it would be refused. It still has every other capability: it could search your documents or call Linear if the question needed it. On the second message you sent no scope, so web search is back, and the message asks for it (“online”).

Recipe 2: answer from one source, then one document

Goal: “Summarize only the 2024 board deck.” Starting point: the example agent. indexed_documents is always, with no filter, so every source is searchable. This recipe narrows one message to a source, then the next one to a document. Start by limiting the message to the source that holds your board decks. Copy the source ID from the Documents page.
The platform does not show document ids yet. The agent can tell you: the question above makes it list the documents it finds, with their ids. Copy the id of the 2024 deck, then pin the next message to that document:
▶ examples/11_capabilities.py Expected behavior: In the first message, the agent searches only the source you named. Documents from your other sources cannot show up in its answer, even if they mention board decks. In the second message, it reads only the 2024 deck and summarizes that one file. A different message in the same thread, without the scope, searches every source again.
A scope that carries only document_ids focuses the message on those documents. It does not keep the thread’s source_ids.

Recipe 3: keep some connections, or none

Goal: “Answer without calling Linear.” Or: “Use Linear, but not Slack.” Starting point: the example agent. mcp is always, with no filter, and Linear and Slack are connected. This recipe turns MCP off for one message, then keeps only Linear for another. Turn MCP off for one message:
To keep only some connections, list them:
▶ examples/08_mcp_linear.py Expected behavior: With enabled=False, Linear and Slack stay connected, but they are off for this message. The agent drafts the template without calling them. On the next message, with no scope, it can call Linear again. With connections=["linear"], the agent can call Linear for this message. Slack stays unavailable, even though it is connected to the agent. The connection names are the ones on the Integrations page of the platform.

Recipe 4: no tools at all

Goal: “Rewrite this text. Do not search or call anything.” Starting point: the example agent. Web, documents, and MCP are all always. This recipe turns the three of them off for one message. For a pure transformation, turn off every tool for the message. It makes the answer faster and more predictable.
▶ examples/07_custom_agent.py Expected behavior: Web, document search, and MCP are off for this message. The agent still sees their tools, but cannot run them. It rewrites the sentence you gave it and nothing else. The next message without a scope gets its capabilities back.

Recipe 5: a thread that never uses the web

Goal: “Support conversations must never browse the web.” Starting point: the example agent. web is always. This recipe removes it from the thread. This time the rule is for the whole thread. Use thread_capabilities with activation="never".
▶ examples/11_capabilities.py Expected behavior: The agent answers from your documents and its own knowledge. It has no web search tool on any message of the thread: with never, the tool is not installed, unlike a scope that only switches it off. Asking for web search on a later message does not work: the request is rejected with a 422 APIError before the agent runs, because the thread does not have the capability to turn on. The other capabilities (documents, MCP, and so on) keep the agent’s values, because you only redefined web.

Recipe 6: generate an image only when asked

Goal: “Let the agent draw, but only when the user asks for it.” Starting point: the example agent. image_generation is always, so the agent may draw on any message. This recipe sets it to on_request for the thread. Use on_request. The capability is installed but off until a message turns it on.
▶ examples/11_capabilities.py Expected behavior: On the first message, image generation is off. The agent describes the flow in words. On the second message, the scope turns image generation on, so the agent can produce the diagram. On the third message you sent no scope, so image generation is off again and the agent answers in words. Sending ImageGenerationScope(enabled=False) on an on_request capability is an error, because the capability is already off.

Recipe 7: restrict a thread to a few sources, connections, and skills

Goal: “This support thread may only read two sources, call Linear, and use one skill.” Starting point: the example agent. Every capability is always, with no filter. This recipe restricts three of them for the whole thread.
▶ examples/11_capabilities.py Expected behavior: The agent searches only the two sources you listed, and no other. It can call Linear but no other connection. It can load only the one skill. Web search, image generation, and the other capabilities keep what the agent has, because you did not pass them. The three lists apply to every message of the thread.

What ALL and invalid values do

A filter you leave out means no restriction. ALL says it explicitly.
Three values look like “no filter” but are errors. The SDK raises InvalidParams before it sends anything:
A filter together with activation="never" or enabled=False is also refused, because those states take no filter.
A capability you pass replaces the agent’s version of it entirely. If the agent limits documents to two sources and you pass IndexedDocumentsCapability(activation="always", document_ids=[...]), the agent’s source_ids are gone. Capabilities you do not pass keep the agent’s value: web stays on until you pass WebCapability(activation="never"). See Override an agent for one thread.

Recipe 8: one memory per customer

Goal: “Remember each customer’s preferences, and never mix customers up.” Starting point: the example agent. It has no memory namespace, so it has no memory. This recipe gives each thread one. Memory is a capability. Give each thread a namespace that includes the customer id.
To run a thread with no memory at all, even if the agent has a namespace:
▶ examples/11_capabilities.py · examples/09_memory.py Expected behavior: The agent has memory tools on this thread. It writes the preference to a file in the customer-acme namespace, and reads it back on the next message. A thread started for another customer uses a different namespace, so it cannot see this file. With activation="never", the agent has no memory tools and writes nothing, even if the agent normally has a namespace. A follow-up cannot change the namespace: start a new thread to use another one. Read what the agent stored with client.memory.

Recipe 9: lock the thread, then focus each message

Goal: “A support thread with a fixed policy, where each question can narrow it further.” Starting point: the example agent. Every capability is always, with no filter. The thread replaces web, documents, and MCP with its own policy. Each message then narrows that policy. Combine both levels. The thread sets the policy once. Each message narrows it without ever going beyond it.
▶ examples/11_capabilities.py Expected behavior: Each scope applies to its message only. After message 2, message 3 searches both sources again. The thread policy is the ceiling. A message can go down from it, never above it.

Next steps

Capabilities

The definitions, activation modes, and filters.

Troubleshooting and migration

Move from disabled_tools, and understand each error.