Overview
An agent gets far more useful once it can reach the tools your team already uses. For instance, your support agent can check GitHub to answer "Is the login bug fixed yet?"
Linear, Slack, GitHub, Sentry and more. One shared account, or each user's own.
Any internal API or database, run in your process.
Files in the repo and pages the agent keeps itself filled from.
Exa, Tavily and Perplexity behind one search route.
Agents join calls and answer in channels natively.
Twilio and Telnyx numbers, or your own SIP trunk.
Connectors
Add a connector by its name under connectors in agent.yaml:
name: assistant # an internal assistant for your team
mode: text
llm: llm-fast
connectors:
- linear # each user signs in to their own Linear
- name: github
connection: company # one account the app owns, by its labelgetstream agents test assistant --syncAnd ask: "What are the open issues in GetStream/awesome-ai-news?"
The model sees each tool as <name>__<tool>, for example linear__list_issues.
Whose account
Each connector uses one shared account or each user's own. A public MCP server needs no account:
| Whose account | In agent.yaml |
Who connects it | Example | Learn more |
|---|---|---|---|---|
| Each user's own | a name, such as - linear |
each user, in the dashboard test or on a page of your app | their Linear issues | Each user's account |
| One shared account | connection, a label or an id |
you, once, in the dashboard or from your backend | the company's GitHub | Shared account |
| A public MCP server | your connector, with connection |
you, once, from your backend | DeepWiki | Custom connectors |
Generally we recommend a shared account for company tools like GitHub, so your users never have to sign in. Use each user's own account when the agent should act as that person, for instance to answer "Which Linear issues are assigned to me?"
Catalog
| Connector | What the agent can do | Sign-in |
|---|---|---|
slack |
Search the workspace, read messages, and look people up | OAuth, Stream's client only |
github |
Search repositories, and read issues and pull requests | OAuth, your client or Stream's; or a token |
sentry |
Search issues and errors, and read their events | OAuth, registers its own client |
linear |
Search issues and projects, and read workspace context | OAuth, registers its own client |
notion |
Search and read the pages and databases the user can access | OAuth, registers its own client |
hubspot |
Search contacts, companies and deals | OAuth, your client or Stream's |
salesforce |
Read CRM records | OAuth, your client or Stream's |
calendly |
Check availability | OAuth, registers its own client |
calcom |
Read bookings and event types | OAuth, registers its own client |
google_calendar |
Read calendars, events and free time | OAuth, your client or Stream's |
google_drive |
Search files and folders, and read what is in them | OAuth, your client or Stream's |
google_docs |
Read documents and their contents | OAuth, your client or Stream's |
gmail |
Search email threads, and read drafts | OAuth, your client or Stream's |
bigquery |
List datasets, and run SQL queries | OAuth, your client or Stream's |
metabase |
Search, and browse collections and data | OAuth, registers its own client |
gong |
Look up CRM accounts | OAuth, your client, or registers its own |
shopify |
Search a shop's policies and FAQs | none |
"Registers its own client" means you set up nothing. To use your own client, see OAuth clients.
githubalso takes a personal access token. See Shared account.shopifyneeds the shop'smyshopify.comaddress and no sign-in. See A shop with no sign-in.metabasetakes your Metabase's host name as the inputhost, such asmetabase.example.com. It asks for read and query scopes only, so the tools that write are not offered.gongneeds an integration registered by a Gong technical administrator on a paid seat.
Channels
Slack, WhatsApp, iMessage and SMS let people talk to the agent, instead of giving it tools. Each one uses a shared account, set with connection:
- Slack:
slack_botanswers in Slack threads as your workspace's own bot - WhatsApp:
whatsappanswers on WhatsApp with your own Meta app - iMessage:
linqanswers over iMessage on your own Linq line - SMS:
telnyxanswers over SMS on your own Telnyx number
Your own functions
For anything that is not a connector, register a function. The model calls it over the session socket and your process runs it, so it can reach private databases and internal APIs without exposing them:
agent.tools.register<{
summary: string;
priority: "low" | "normal" | "urgent";
}>({
name: "create_ticket",
description: "Open a support ticket for the caller",
parameters: {
type: "object",
properties: {
summary: {
type: "string",
description: "one sentence describing the problem",
},
priority: { type: "string", enum: ["low", "normal", "urgent"] },
},
required: ["summary", "priority"],
},
run: ({ summary, priority }, signal) =>
tickets.create(summary, priority, signal),
});import kotlinx.serialization.json.jsonPrimitive
val createTicket = AgentTool(
name = "create_ticket",
description = "Open a support ticket for the caller",
parameters = AgentTool.strings(
mapOf(
"summary" to "one sentence describing the problem",
"priority" to "low, normal or urgent",
),
required = listOf("summary", "priority"),
),
) { args ->
tickets.create(
args["summary"]?.jsonPrimitive?.content.orEmpty(),
args["priority"]?.jsonPrimitive?.content.orEmpty(),
)
}
val session = agents.agent("support").chat(createTicket)let createTicket = AgentTool(
name: "create_ticket",
description: "Open a support ticket for the caller",
parameters: .strings([
"summary": "one sentence describing the problem",
"priority": "low, normal or urgent",
], required: ["summary", "priority"])
) { arguments in
try await tickets.create(arguments["summary"]?.stringValue ?? "", arguments["priority"]?.stringValue ?? "")
}
let session = try await agents.chat(agent: "support", tools: [createTicket])
await session.start()final createTicket = AgentTool(
name: 'create_ticket',
description: 'Open a support ticket for the caller',
parameters: AgentTool.strings({
'summary': 'one sentence describing the problem',
'priority': 'low, normal or urgent',
}, required: ['summary', 'priority']),
run: (arguments) =>
tickets.create(arguments['summary'] as String, arguments['priority'] as String),
);
final session = await agents.agent('support').chat(SessionOptions(tools: [createTicket]));@agent.llm.register_function(description="Open a support ticket for the caller")
async def create_ticket(summary: str, priority: str) -> dict[str, str]:
return await tickets.create(summary, priority)agent.tools.register("create_ticket",
description: "Open a support ticket for the caller",
params: {
summary: "one sentence describing the problem",
priority: "low, normal or urgent"
}) do |summary:, priority:|
Tickets.create(summary, priority)
enduse GetStream\VisionAgents\Describe;
$agent->tools->register(
'create_ticket',
'Open a support ticket for the caller',
fn (
#[Describe('one sentence describing the problem')] string $summary,
#[Describe('low, normal or urgent')] string $priority,
) => $tickets->create($summary, $priority),
);type CreateTicket struct {
Summary string `json:"summary" schema:"one sentence describing the problem"`
Priority string `json:"priority" schema:"low, normal or urgent"`
}
err := tools.Register(agent.Tools(), "create_ticket", "Open a support ticket for the caller",
func(ctx context.Context, args CreateTicket) (any, error) {
return tickets.Create(ctx, args.Summary, args.Priority)
})agent.Tools.Register<CreateTicket, Ticket>("create_ticket",
"Open a support ticket for the caller",
(args, cancellationToken) => tickets.CreateAsync(args.Summary, args.Priority, cancellationToken));
record CreateTicket(
[property: Description("one sentence describing the problem")] string Summary,
[property: Description("low, normal or urgent")] string Priority);use schemars::JsonSchema;
use serde::Deserialize;
use vision_agents::Tools;
#[derive(Deserialize, JsonSchema)]
struct CreateTicket {
/// one sentence describing the problem
summary: String,
/// low, normal or urgent
priority: String,
}
let tools = Tools::new();
tools.register("create_ticket", "Open a support ticket for the caller", |args: CreateTicket| async move {
tickets::create(&args.summary, &args.priority).await
});See Tool calling & Dispatch for the details.
Knowledge and web search
knowledge/in the agent folder, and pages inknowledge/urls.yamlthat we crawl and keep fresh. See Knowledge & skills.search: search-fastinagent.yamllets the agent search the web with Exa, Tavily or Perplexity.- Agents remember people across conversations. See Memory & Persistence.
Stream Chat and Video
Agents are native to Stream:
- An agent joins a Stream Video call, or holds a written conversation in a Stream Chat channel that outlives the session.
- Dispatch answers messages written to an agent's channel and calls that reach Stream over SIP.
- Your apps join the same call or channel with the client SDKs.
Webhooks
- A guardrail webhook lets your server decide which turns the agent answers. See Guardrails.
- A transcription job with a callback URL calls you when the transcript is ready.
Connectors deep dive
Each entry under connectors takes these keys:
| Key | What it sets | Default |
|---|---|---|
name |
The name the model sees each tool under, as <name>__<tool> |
Required |
connector |
The connector id, such as linear or custom_wiki |
name |
connection |
One account the app owns, by its id or label. See Whose account | Each user's own account |
tools |
Tool names or patterns to offer. See Choosing tools | Every tool |
required |
true refuses a session whose connection is missing or not connected |
Optional: the session goes on |
sandbox |
true lets code in the agent's sandbox call the connector's API. See Sandbox access |
No access |
A name alone, such as - linear, is the same as name: linear.
When the agent syncs, a connection the app doesn't have is left out with a warning, and the rest of the agent syncs. With required: true, the sync fails instead. Leaving connectors out of agent.yaml keeps what is already stored, and an empty list removes it.
Going further
Let each user sign in, from the dashboard or your own app.
One account the app owns, for every conversation.
Any MCP server: public, behind a key, or with a sign-in.
Offer only the tools the agent needs.
Answer in Slack threads as your workspace's bot.
Answer on WhatsApp with your own Meta app.
Answer over iMessage on your own Linq line.
Answer over SMS on your own Telnyx number.
Let code in the agent's sandbox call a connector's API.
Errors you may see, why, and what to do.