How to Build Multi-Agent Portfolio Collaboration Workflows Using the OpenAI Agents SDK

The OpenAI Agents SDK enables multi-agent portfolio collaboration workflows through a hub-and-spoke architecture where a Head Portfolio Manager agent orchestrates parallel specialist analyses using the agent-as-tool pattern, dramatically cutting latency while maintaining full observability via OpenAI Traces.

The openai/openai-cookbook repository demonstrates a production-grade implementation of multi-agent portfolio collaboration workflows built entirely on the OpenAI Agents SDK. This architecture decomposes complex investment research into concurrent domain-specific tasks orchestrated by a central agent, yielding structured investment memos with complete auditability and traceability.

Hub-and-Spoke Architecture Overview

The workflow implements a clean separation of concerns through four primary components:

  • Head Portfolio Manager (PM) Agent: The central orchestrator defined in pm.py that receives user queries, breaks them into sub-tasks, and synthesizes final investment memos. It uses the agent-as-tool pattern to expose each specialist as a callable tool according to the source code at lines 40-68【/cache/repos/github.com/openai/openai-cookbook/main/examples/agents_sdk/multi-agent-portfolio-collaboration/investment_agents/pm.py†L40-L68】.

  • Specialist Agents: Domain-specific analysts including Fundamental, Macro, and Quantitative agents. Each performs deep research using appropriate tools like Code Interpreter, WebSearch, and MCP-backed data sources. These are assembled by config.py at lines 17-24【/cache/repos/github.com/openai/openai-cookbook/main/examples/agents_sdk/multi-agent-portfolio-collaboration/investment_agents/config.py†L17-L24】.

  • Tools and MCP Servers: Reusable functions registered with @function_tool include custom data fetchers for FRED and Yahoo Finance via MCP, OpenAI-managed tools, and a memo-editing tool for final report generation.

  • Parallel Execution: The PM agent enables concurrent specialist analysis through parallel_tool_calls=True in ModelSettings, configured at lines 66-68 in pm.py【/cache/repos/github.com/openai/openai-cookbook/main/examples/agents_sdk/multi-agent-portfolio-collaboration/investment_agents/pm.py†L66-L68】.

Configuring the Specialist Agent Bundle

The build_investment_agents() function in config.py constructs the complete agent ecosystem. This factory pattern instantiates each specialist, creates the memo-editing tool, and assembles the Head PM agent with all dependencies injected.


# examples/agents_sdk/multi-agent-portfolio-collaboration/investment_agents/config.py

from investment_agents.fundamental import build_fundamental_agent
from investment_agents.macro import build_macro_agent
from investment_agents.quant import build_quant_agent
from investment_agents.editor import build_editor_agent, build_memo_edit_tool
from investment_agents.pm import build_head_pm_agent

def build_investment_agents():
    fundamental = build_fundamental_agent()
    macro = build_macro_agent()
    quant = build_quant_agent()
    editor = build_editor_agent()
    memo_edit_tool = build_memo_edit_tool(editor)

    head_pm = build_head_pm_agent(
        fundamental, macro, quant, memo_edit_tool
    )
    return InvestmentAgentsBundle(
        head_pm=head_pm,
        fundamental=fundamental,
        macro=macro,
        quant=quant,
    )

Source: config.py

Implementing the Head PM Agent and Agent-as-Tool Pattern

The Head PM agent in pm.py implements sophisticated orchestration logic using the agent-as-tool pattern. Rather than managing specialists through complex delegation protocols, the PM treats each specialist agent as a callable function tool via make_agent_tool().

The implementation defines specialist_analysis_func() to execute individual analysts and run_all_specialists_parallel() to invoke them concurrently using asyncio.gather():


# examples/agents_sdk/multi-agent-portfolio-collaboration/investment_agents/pm.py

from agents import Agent, ModelSettings, function_tool, Runner
from utils import load_prompt, DISCLAIMER
from pydantic import BaseModel
import json, asyncio

class SpecialistRequestInput(BaseModel):
    section: str                # "fundamental", "macro", "quant"

    user_question: str
    guidance: str

async def specialist_analysis_func(agent, input: SpecialistRequestInput):
    result = await Runner.run(
        starting_agent=agent,
        input=json.dumps(input.model_dump()),
        max_turns=75,
    )
    return result.final_output

async def run_all_specialists_parallel(fundamental, macro, quant,
                                      fundamental_input, macro_input, quant_input):
    results = await asyncio.gather(
        specialist_analysis_func(fundamental, fundamental_input),
        specialist_analysis_func(macro, macro_input),
        specialist_analysis_func(quant, quant_input),
    )
    return {"fundamental": results[0], "macro": results[1], "quant": results[2]}

def build_head_pm_agent(fundamental, macro, quant, memo_edit_tool):
    def make_agent_tool(agent, name, description):
        @function_tool(name_override=name, description_override=description)
        async def agent_tool(input: SpecialistRequestInput):
            return await specialist_analysis_func(agent, input)
        return agent_tool

    fundamental_tool = make_agent_tool(fundamental, "fundamental_analysis",
                                       "Generate the Fundamental Analysis section.")
    macro_tool = make_agent_tool(macro, "macro_analysis",
                                 "Generate the Macro Environment section.")
    quant_tool = make_agent_tool(quant, "quantitative_analysis",
                                 "Generate the Quantitative Analysis section.")

    @function_tool(name_override="run_all_specialists_parallel",
                    description_override="Run all three specialist analyses in parallel "
                                         "and return their results as a dict.")
    async def run_all_specialists_tool(fundamental_input, macro_input, quant_input):
        return await run_all_specialists_parallel(
            fundamental, macro, quant,
            fundamental_input, macro_input, quant_input
        )

    return Agent(
        name="Head Portfolio Manager Agent",
        instructions=load_prompt("pm_base.md") + DISCLAIMER,
        model="gpt-4.1",
        tools=[fundamental_tool, macro_tool, quant_tool,
               memo_edit_tool, run_all_specialists_tool],
        model_settings=ModelSettings(parallel_tool_calls=True,
                                     tool_choice="auto", temperature=0),
    )

Source: pm.py

The PM agent loads its orchestration logic from prompts/pm_base.md, which encodes workflow rules, best practices, and firm philosophy, then appends a standard disclaimer.

Enabling Parallel Execution for Low Latency

Performance optimization relies on the parallel_tool_calls=True setting within ModelSettings. When the Head PM invokes run_all_specialists_parallel, the SDK executes the Fundamental, Macro, and Quantitative analyses concurrently rather than sequentially. This configuration is critical for multi-agent portfolio collaboration workflows requiring real-time market analysis.

The max_turns=75 parameter in specialist_analysis_func() allows deep research iterations for each specialist, while the Head PM operates under max_turns=40 during the main execution loop to prevent runaway orchestration cycles.

Observability with OpenAI Tracing

Full auditability is achieved through the add_trace_processor() interface. The workflow wraps all executions with BatchTraceProcessor and FileSpanExporter, generating OpenAI Traces that visualize each agent step, tool call, and data flow.


# examples/agents_sdk/multi-agent-portfolio-collaboration/multi_agent_portfolio_collaboration.ipynb

from agents import add_trace_processor, trace
from agents.tracing.processors import BatchTraceProcessor
from utils import FileSpanExporter

add_trace_processor(BatchTraceProcessor(FileSpanExporter()))

async def run_workflow():
    # ... setup code ...

    with trace("Investment Research Workflow",
               metadata={"question": question[:512]}) as wf_trace:
        response = await Runner.run(bundle.head_pm, question, max_turns=40)

Source: multi_agent_portfolio_collaboration.ipynb lines 18-30【/cache/repos/github.com/openai/openai-cookbook/main/examples/agents_sdk/multi-agent-portfolio-collaboration/multi_agent_portfolio_collaboration.ipynb†L18-L30】

The trace URL is printed upon completion, allowing inspection of the entire execution graph in the OpenAI console, including parallel specialist invocations and data dependencies.

Integrating MCP Servers for Live Market Data

The workflow leverages Model Context Protocol (MCP) servers to access real-time financial data without custom HTTP logic. Located under the mcp/ directory, these servers expose Yahoo Finance and FRED (Federal Reserve Economic Data) as standardized tools that specialists invoke naturally.

The notebook references these implementations in the "Supported Tool Types" section at lines 98-104【/cache/repos/github.com/openai/openai-cookbook/main/examples/agents_sdk/multi-agent-portfolio-collaboration/multi_agent_portfolio_collaboration.ipynb†L98-L104】. Each specialist agent that requires external data connects to its respective MCP server during the async initialization phase.

Executing the Complete Workflow

The notebook implementation demonstrates production execution patterns using asyncio.TaskGroup() for concurrent MCP server connections and the trace() context manager for observability:


# examples/agents_sdk/multi-agent-portfolio-collaboration/multi_agent_portfolio_collaboration.ipynb

import datetime, os, json, asyncio
from agents import Runner, add_trace_processor, trace
from agents.tracing.processors import BatchTraceProcessor
from utils import FileSpanExporter, output_file
from investment_agents.config import build_investment_agents

add_trace_processor(BatchTraceProcessor(FileSpanExporter()))

async def run_workflow():
    today_str = datetime.date.today().strftime("%B %d, %Y")
    question = (
        f"Today is {today_str}. "
        "How would the planned interest rate reduction affect my holdings in GOOGL? "
        "Provide a realistic year‑end price target."
    )
    bundle = build_investment_agents()

    async with asyncio.TaskGroup() as tg:
        # Connect any MCP servers used by specialists

        for agent in [bundle.fundamental, bundle.macro, bundle.quant]:
            for server in getattr(agent, "mcp_servers", []):
                await server.connect()
                tg.create_task(server.run())

        with trace("Investment Research Workflow",
                   metadata={"question": question[:512]}) as wf_trace:
            response = await Runner.run(bundle.head_pm, question, max_turns=40)

    print("Result:", response.final_output)

Source: multi_agent_portfolio_collaboration.ipynb

The workflow requires OPENAI_API_KEY and FRED_API_KEY environment variables. The Head PM receives the natural language question, delegates to parallel specialists via the run_all_specialists_parallel tool, aggregates results, and uses the memo-editing tool from editor.py to compose the final structured report.

Summary

  • Agent-as-tool pattern: The Head PM agent treats specialist agents as callable functions via @function_tool, enabling clean orchestration without complex delegation protocols.
  • Parallel execution: Configuring parallel_tool_calls=True in ModelSettings allows concurrent specialist analysis, significantly reducing end-to-end latency.
  • Modular architecture: Domain expertise is cleanly separated into Fundamental, Macro, and Quantitative agents defined in their respective files (fundamental.py, macro.py, quant.py), promoting reusability and independent testing.
  • Full observability: BatchTraceProcessor and FileSpanExporter capture complete execution traces, providing audit trails for production deployments.
  • Live data integration: MCP servers abstract external data sources (Yahoo Finance, FRED) as standard tools, allowing specialists to access real-time market data without custom integration code.

Frequently Asked Questions

How does the agent-as-tool pattern differ from standard agent delegation?

Agent-as-tool treats specialized agents as function tools callable by the orchestrator, whereas standard delegation typically involves handoff protocols or message passing between agents. In pm.py, the Head PM uses make_agent_tool() to wrap each specialist in a @function_tool decorator, allowing the SDK's standard tool-calling mechanism to handle serialization and parallel execution. This pattern simplifies the orchestration logic and enables the parallel_tool_calls optimization.

What configuration is required to run the portfolio collaboration workflow?

You must set OPENAI_API_KEY and FRED_API_KEY environment variables. The workflow requires Python 3.9+ with the OpenAI Agents SDK installed. MCP servers for Yahoo Finance and FRED must be accessible, and the notebook assumes async execution context with asyncio.TaskGroup() for managing concurrent server connections.

How does parallel execution improve performance in multi-agent workflows?

The Head PM agent configures ModelSettings(parallel_tool_calls=True), which instructs the SDK to execute fundamental_analysis, macro_analysis, and quantitative_analysis simultaneously via asyncio.gather(). This concurrent execution reduces total latency from the sum of individual analysis times to approximately the duration of the slowest specialist, which is critical for time-sensitive financial analysis.

Can this architecture be adapted for non-financial domains?

Yes. The hub-and-spoke pattern demonstrated in config.py and pm.py is domain-agnostic. You can replace the Fundamental, Macro, and Quantitative specialists with domain-specific agents (e.g., Legal, Technical, Marketing) while retaining the same orchestration structure, parallel execution benefits, and observability features. The memo-editing tool and trace processors remain applicable across use cases such as due-diligence pipelines, policy analysis, or research assistants.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →