OpenAI Agents quickstart — governed Runner tools
The adapter converts MCP definitions into strict OpenAI Agents SDK
FunctionTool instances. This example uses the public scripted model helper to
make one deterministic call to a tool that the running bridge actually
advertises.
Before you start
- Python 3.10+; Python 3.11 and 3.12 are CI-covered.
- Cordum bootstrapped with
./tools/scripts/quickstart.sh. cordum-mcp-bridgeonPATH.- The secure environment from Framework integrations.
:::note Strict JSON Schema
The adapter normalizes nested object schemas for the SDK's strict mode. Use
strict=False only when an upstream tool intentionally advertises a loose
schema.
:::
1. Install
python -m venv .venv
source .venv/bin/activate
pip install "cordum-adapters[openai-agents]"
2. Build a governed Runner
Save as openai_agents_quickstart.py:
import asyncio
import os
from agents import Agent
from cordum_agent_adapters.audit import CordumConversationLogger
from cordum_agent_adapters.mcp_client import McpStdioClient
from cordum_agent_adapters.openai_agents import (
build_openai_agent_tools,
run_governed,
tee_events,
)
from cordum_agent_adapters.testing import FakeModel, ScriptedTurn
TOOL_NAME = "cordum.workflow.run"
def require_tool(tool_defs: list[dict], name: str) -> list[dict]:
selected = [tool for tool in tool_defs if tool.get("name") == name]
if not selected:
advertised = sorted(str(tool.get("name")) for tool in tool_defs)
raise RuntimeError(f"{name} is not advertised; got {advertised}")
return selected
async def main() -> None:
client = McpStdioClient(
command=["cordum-mcp-bridge"],
env=os.environ.copy(),
)
try:
selected = require_tool(client.list_tools(), TOOL_NAME)
logger = CordumConversationLogger(client)
tools = build_openai_agent_tools(client, tools=selected, logger=logger)
model = FakeModel(
turns=[
ScriptedTurn(
tool_calls=[
{
"name": TOOL_NAME,
"arguments": {
"workflow_id": "demo-workflow",
"input": {},
"dry_run": True,
},
}
]
),
ScriptedTurn(content="Review the actual tool output above."),
]
)
agent = Agent(
name="governed_runner",
instructions="Use only the Cordum tool assigned to you.",
tools=tools,
model=model,
)
result = await run_governed(
agent,
"Start demo-workflow as a dry run.",
client=client,
logger=logger,
)
async for event in tee_events(result, logger):
item = getattr(event, "item", None)
if getattr(item, "type", "") == "tool_call_output_item":
print("tool output:", item.output)
finally:
client.close()
if __name__ == "__main__":
asyncio.run(main())
Run it after loading the common TLS environment:
python openai_agents_quickstart.py
Replace demo-workflow with a workflow in your tenant. The output comes from
the bridge; this page does not prescribe a successful response or dashboard
row. run_governed adds a correlation identifier to OpenAI trace metadata, and
conversation logging remains optional and best-effort.
3. Exercise a hard deny
Upload a v1 bundle through the HTTPS policy API:
version: v1
rules:
- id: block-tutorial-workflow-run
match:
capabilities:
- cordum.workflow.run
decision: deny
reason: "Tutorial hard-deny check"
A hard deny returns isError: true; McpStdioClient raises McpToolError.
The OpenAI Agents adapter converts that error into a tool-result message for the
Runner loop.
4. Recognize approval holds
The pack bridge keeps an approval-gated job nonterminal and blocks the original
Runner tool call. Resolve the job in the dashboard before the bounded
CORDUM_MCP_CALL_TIMEOUT; approval lets the same call continue. A timeout is
returned through the tool-error path, and this bridge does not use an
approval-reference retry.