Your OpenAI Agents SDK agent can reason about a sales pipeline all day, but without concrete data, you have no idea whether the arguments are based in real information or a couple of Reddit posts from four years ago. You need to be able to control your agent's data sources to ensure its proposed solutions are useful.
Airbyte Agents offers managed auth for 50+ connectors. It also builds a queryable Context Store for your agent, which allows the agent to cut down on API calls (and the inevitable issues like rate limiting and 404 errors). In Airbyte's benchmarks , agents querying the Context Store made 40% fewer tool calls and consumed up to 80% fewer tokens than agents going to vendor APIs directly. Airbyte accesses the Context Store under the hood, so you don't need to worry about deciding when to use live API calls versus saved data.
This tutorial shows you how to connect Airbyte to OpenAI Agents, and by the end you'll have an agent that reads recent deals from Salesforce and posts a digest to a Slack channel. The Airbyte half of the code stays identical no matter which connectors you use, so you can swap in your own systems later without relearning anything.
Prerequisites Python 3.11+ and comfort with async/await. Basic familiarity with the OpenAI Agents SDK (you know what an Agent and a tool are). An Airbyte account . Through this account, you'll connect Salesforce and Slack in your Airbyte workspace. The Free tier ($0/mo for 1,000 Agent Operations) is enough to follow along. An OpenAI API key for the model. The Airbyte Slack app installed in your workspace and added to your channel. uv installed to manage dependencies.(Optional) the full code from Airbyte’s quickstarts repo. Step 1: Connect your data to Airbyte Before any code, connect to Salesforce and Slack from your Airbyte workspace . Authenticate both tools through the Airbyte UI; see Add a connector for the full walkthrough. Airbyte will use this authenticated connection to build the Context Store , a queryable database synced from your data sources. Your agent can then pull data from the Context Store or the live API as a fallback.
From the user profile , copy these values:
You'll put them in a .env file next.
Salesforce and Slack are now agent ready from the Airbyte workspace.
Step 2: Set up the project and your .env Create the project and add dependencies with uv:
uv init deal_digest_agent
cd deal_digest_agent
uv add openai-agents airbyte-agent-sdk python-dotenvCreate a .env file next to your script.
AIRBYTE_CLIENT_ID =your_client_id
AIRBYTE_CLIENT_SECRET =your_client_secret
OPENAI_API_KEY =sk-...
TARGET_CHANNEL_ID =your_slack_channel_idCreate a new file called deal_digest_agent.py and load the env at the top of your script. Then you can build the Airbyte auth config. You'll share this single config across both connectors:
import os
from dotenv import load_dotenv
from airbyte_agent_sdk import AirbyteAuthConfig
load_dotenv ()
auth = AirbyteAuthConfig (
airbyte_client_id=os.getenv ("AIRBYTE_CLIENT_ID" ),
airbyte_client_secret=os.getenv ("AIRBYTE_CLIENT_SECRET" ),
)The auth declaration isn't strictly necessary for a proof of concept like this one; the Airbyte Agents SDK can resolve authorization from the loaded environment. However, if you're programmatically determining permissions (in production, for instance), you will need logic like this command to evaluate access.
Step 3: Construct the connectors connect resolves each connector by slug against your workspace, using either the auth config or the environment variables directly. Then build_connector_tools turns each connector into a set of ready-to-bind agent tools:
from airbyte_agent_sdk import build_connector_tools, connect
salesforce = connect ("salesforce", auth_config= auth)
slack = connect ("slack", auth_config= auth)You'll use salesforce and slack when calling build_connector_tools(connector, framework="openai_agents") as the connector argument. To see which entities and actions each connector exposes before you prompt against it, browse Agent connectors — for example, the Salesforce connector page .
Before implementing these tools in the agent, you can use check() in the main() function to catch a bad credential now rather than halfway through an agent run:
check = await salesforce.check()
print (check.status) This method returns a status of "healthy" or "unhealthy" plus an error string, so failures are observable before you debug agent logic. See the full sample code for an example of this call.
Step 4: Connect Airbyte to the agent as tools The agent can't access salesforce or slack yet. Each is a connector that you'll need to convert into ConnectorTools objects.
ConnectorTools includes an as_list() method that returns three plain async callables to answer the following questions for the agent at runtime:
inspect_connector: What is this connector and is it ready?read_skill_docs: How do you call the connector? If the agent has a specific operation, it can specify a section of the docs to narrow results to that one task. Otherwise this call will list all of the connector's entities and actions.execute: Run the necessary command.This progressive flow keeps the agent's context window small and its calls accurate. To hand these callables to the OpenAI Agents SDK, wrap each one with function_tool.
from agents import function_tool
def register (connector, prefix ):
tools = build_connector_tools(connector, framework="openai_agents" )
return [
function_tool(tool, name_override=f"{prefix} _{tool.__name__} " , strict_mode=False )
for tool in tools.as_list()
]First, pass strict_mode=False. The OpenAI Agents SDK enforces a strict JSON schema by default, which rejects execute's open-ended params object, so the tools won't register without it. Second, both connectors produce tools with the same three names, and the model will fail silently if it can’t tell the tools apart. Prefix the tool with name_override (salesforce_execute, slack_execute, and so on) to keep the model from confusing the two connectors.
Now construct the agent and hand it all six tools with *register:
from agents import Agent
agent = Agent(
name="Deal Digest Agent" ,
model="gpt-5.6-terra" ,
instructions=(
"You summarize sales pipeline activity. Before your first execute call, "
"inspect the connector and read its skill docs to learn the available "
"entities, actions, and parameters. Read recent opportunities from "
"Salesforce, then post a short digest to the Slack channel the user names. "
"Before executing an Airbyte operation, call the connector's "
"read_skill_docs tool without a section to obtain its outline. Copy the "
"exact section ID from that outline, including the 'actions.' prefix, "
"and read that section. For execute calls, pass the entity and action "
"separately; never pass 'entity.action' as either argument. Salesforce "
"list actions expect a complete SOQL query in params['q']."
),
tools=[*register(salesforce, "salesforce" ), *register(slack, "slack" )],
)The system prompt tells the agent to inspect and read skill docs before executing, which is the flow build_connector_tools is built around. Because the docs are read at runtime, the prompt only carries domain constraints, not a hand-written schema. You can port this to one of the following frameworks:
You'll only need to change the function_tool wrapper to match that framework's tool constructor.
Step 5: Give the agent a task and run it Call the agent in the main() function of your application.
Runner.run executes the agent. The runner handles the tool-call loop with the following agent actions:
Inspect Salesforce and read its skill docs. List the opportunities. Inspect Slack and post the digest. import asyncio
from agents import Runner
import os
from dotenv import load_dotenv
import logging
load_dotenv()
async def main ():
try :
tool_error_logger = logging.getLogger("airbyte_agent_sdk.translation._decorator" )
tool_error_logging_was_disabled = tool_error_logger.disabled
try :
tool_error_logger.disabled = True
result = await Runner.run(
agent,
"Summarize our 5 most recent Salesforce opportunities and post the "
f"digest to the {os.getenv('TARGET_CHANNEL_ID' )} Slack channel."
"Use mrkdwn formatting in the message." ,
)
finally :
tool_error_logger.disabled = tool_error_logging_was_disabled
print (result.final_output)
finally :
try :
await salesforce.close()
finally :
await slack.close()
if __name__ == "__main__" :
asyncio.run(main())In the main() function, your agent reads live deals and posts a digest to Slack on a single instruction before closing the connectors to release HTTP resources.
A note on tool_error_logger. This logic suppresses exception tracebacks that the agent may generate while calling Salesforce or Slack. This is mainly to keep the console from raining false errors as the agent prods the tool with API calls to access data.
Once the agent completes the prompt, the code reverts tool_error_logger to its previous state again.
Run the complete example The full, runnable program is in Airbyte’s quickstarts Github repo . It loads config from .env, checks both connections, runs the agent, and closes the connectors. Run it with:
uv run deal_digest_agent.pyWhere to go next You can extend this script in many ways. Add a nightly run that posts the digest to Slack every day. Add in a third connector from the connector library , say HubSpot or Jira, and let the agent cross-reference a stalled deal with its open tickets before it writes the summary. Just use the following pattern to connect each new tool:
<connector > = connect("<airbyte-connector-name > ", auth_config=auth)
register(<connector > , "prefix")Then register the callables as a new tool for the agent.
For everything the SDK can do beyond this pattern, see the SDK interface tutorial .