# Authentication & Security Source: https://docs.stackone.com/a2a/auth-security Learn about A2A authentication methods, required headers, and security best practices for StackOne A2A agents. ## Authentication StackOne A2A agents use the same authentication as the regular StackOne API, ensuring consistent security across all integration methods. ### Required Headers All A2A requests require these headers: ```bash theme={null} Authorization: Basic x-account-id: Content-Type: application/json ``` **Get API Key:** 1. Go to **Configuration → API Keys** in the dashboard 2. Create or copy existing API key **Get Account ID:** 1. Go to **Accounts** in the dashboard 2. Select your linked account 3. Copy the account ID (numeric format like `47187425466113776871` or short alphanumeric ID) You can also retrieve account IDs programmatically via the [List Accounts API endpoint](/platform/api-reference/accounts/list-accounts). See [API Keys Guide](/guides/api-keys) for detailed instructions. Unlike some other StackOne endpoints, A2A does **not** support query parameters for authentication. You must use headers for both the API key and account ID. ### API Key **How to create the Basic Auth token:** 1. Take your StackOne API key (e.g., `v1.us1.AAblXDxi8h_OO1AZG_Hyg4V3w65x9...`) 2. Append a colon: `v1.us1.YYplXCxi8h_OO9HZG_Kyg4V3w65x9...:` 3. Base64 encode the result ```bash Terminal theme={null} echo -n ":" | base64 ``` ```javascript JavaScript theme={null} btoa(":"); ``` ```python Python theme={null} import base64 token = base64.b64encode(b"").decode('ascii') ``` ### Account ID The account ID must be passed via the `x-account-id` header. **Account ID Format:** * Numeric string (e.g., `47187425466113776871`) * Short alphanumeric ID (e.g., `abc123xyz`) ## Security Best Practices ### Store API Keys Securely Use environment variables and never commit API keys to version control. ## Troubleshooting Authentication Common authentication issues include: * 401 Unauthorized errors - Check your API key is valid * 403 Forbidden errors - Verify account permissions * Missing header issues - Ensure all required headers are present * Base64 encoding problems - Verify the encoding includes the colon * Account ID validation - Confirm the account ID exists and is accessible ## Testing Authentication Verify your authentication setup by fetching the Agent Card: ```bash cURL theme={null} curl -X GET https://a2a.stackone.com/.well-known/agent-card.json \ -H 'Authorization: Basic ' \ -H 'x-account-id: ' ``` A response with agent details based on your account's configured integrations and enabled actions confirms your authentication is configured correctly. ## Next Steps Once authentication is configured: Get started with the A2A UI and cURL Use the official A2A SDKs to build your own tools Build agents in frameworks with A2A integrations # Agent Development Kit (ADK) Source: https://docs.stackone.com/a2a/framework-guides/adk Build agents in Google's ADK that communicate with StackOne's A2A agents using RemoteA2aAgent. ## Overview The [Agent Development Kit (ADK)](https://google.github.io/adk-docs/) is Google's open-source framework for building AI agents. ADK has built-in support for the A2A protocol, allowing your agents to communicate with remote A2A agents like StackOne's. Use ADK to build agents that: * Consume StackOne's A2A agents as remote sub-agents * Orchestrate multi-agent systems with local and remote agents * Access StackOne platform actions without managing tool definitions ## Installation Install ADK with A2A support using `pip`: ```bash theme={null} pip install google-adk[a2a] ``` ## Quick Start This example creates an orchestrator agent that delegates HR tasks to a StackOne A2A agent. ```python theme={null} import base64 import httpx from google.adk.agents.llm_agent import Agent from google.adk.agents.remote_a2a_agent import RemoteA2aAgent # StackOne A2A configuration STACKONE_API_KEY = "" STACKONE_ACCOUNT_ID = "" BASE64_API_KEY = base64.b64encode(f"{STACKONE_API_KEY}:".encode()).decode() # Create an HTTP client with StackOne authentication headers http_client = httpx.AsyncClient( headers={ "Authorization": f"Basic {BASE64_API_KEY}", "x-account-id": STACKONE_ACCOUNT_ID } ) # Create a remote A2A agent pointing to StackOne stackone_agent = RemoteA2aAgent( name="stackone_hr_agent", description="Agent that handles HR operations via StackOne integrations.", agent_card="https://a2a.stackone.com/.well-known/agent-card.json", httpx_client=http_client ) # Create the orchestrator agent with StackOne as a sub-agent root_agent = Agent( model="gemini-2.0-flash", name="hr_assistant", instruction=""" You are an HR assistant that helps with employee management tasks. Delegate HR operations to the stackone_hr_agent. When the user asks about employees, time off, or other HR data, use the stackone_hr_agent to fetch or update the information. """, sub_agents=[stackone_agent], ) ``` See [Authentication Guide](/a2a/auth-security) for details on obtaining your API key and account ID. ## Architecture ADK's `RemoteA2aAgent` allows your local agent to delegate tasks to remote A2A agents: ```mermaid theme={null} flowchart LR User[User] --> RootAgent[Root Agent
Local ADK] RootAgent -->|A2A Protocol| StackOneA2A[StackOne A2A Agent] StackOneA2A --> Providers[(Connected SaaS
HiBob, Workday, etc.)] RootAgent --> LocalTools[Local Tools
Optional] style StackOneA2A fill:#10b981,stroke:#059669,color:#fff ``` ## Complete Example Here's a complete example with a local tool and a remote StackOne agent: ```python theme={null} import base64 import httpx from google.adk.agents.llm_agent import Agent from google.adk.agents.remote_a2a_agent import RemoteA2aAgent from google.genai import types # Configuration STACKONE_API_KEY = "" STACKONE_ACCOUNT_ID = "" BASE64_API_KEY = base64.b64encode(f"{STACKONE_API_KEY}:".encode()).decode() # Local tool example def get_current_date() -> str: """Get the current date.""" from datetime import datetime return datetime.now().strftime("%Y-%m-%d") # Create an HTTP client with StackOne authentication headers http_client = httpx.AsyncClient( headers={ "Authorization": f"Basic {BASE64_API_KEY}", "x-account-id": STACKONE_ACCOUNT_ID } ) # Remote StackOne A2A agent stackone_agent = RemoteA2aAgent( name="stackone_hr_agent", description=""" Agent that handles HR operations via StackOne integrations. Can list employees, get employee details, manage time off requests, and perform other HR operations depending on the connected integration. """, agent_card="https://a2a.stackone.com/.well-known/agent-card.json", httpx_client=http_client ) # Root orchestrator agent root_agent = Agent( model="gemini-2.0-flash", name="hr_assistant", instruction=""" You are an HR assistant that helps with employee management tasks. You have access to: 1. get_current_date - Get today's date 2. stackone_hr_agent - Delegate HR operations (employees, time off, etc.) When users ask about HR data, delegate to stackone_hr_agent. Always confirm actions before making changes. """, global_instruction="You are HRBot, ready to help with HR tasks.", sub_agents=[stackone_agent], tools=[get_current_date], generate_content_config=types.GenerateContentConfig( temperature=0.7, ), ) ``` ## Running the Agent Use ADK's built-in web server to interact with your agent: ```bash theme={null} adk web path/to/your/agent/folder ``` Then open `http://localhost:8000` in your browser to chat with your agent. ### Example Interactions **Listing Employees:** ```text theme={null} User: List all employees Bot: I'll get that information for you from the HR system. [Delegates to stackone_hr_agent] Here are the employees: 1. John Doe - Software Engineer 2. Jane Smith - Product Manager ... ``` **Getting Employee Details:** ```text theme={null} User: Get details for employee ID emp_123 Bot: [Delegates to stackone_hr_agent] Here are the details for emp_123: - Name: John Doe - Department: Engineering - Start Date: 2023-01-15 ... ``` ## Multiple StackOne Accounts Connect to multiple StackOne accounts by creating separate HTTP clients and `RemoteA2aAgent` instances for each: ```python theme={null} import base64 import httpx from google.adk.agents.llm_agent import Agent from google.adk.agents.remote_a2a_agent import RemoteA2aAgent STACKONE_API_KEY = "" BASE64_API_KEY = base64.b64encode(f"{STACKONE_API_KEY}:".encode()).decode() # HTTP client for HiBob account hibob_http_client = httpx.AsyncClient( headers={ "Authorization": f"Basic {BASE64_API_KEY}", "x-account-id": "" } ) # HTTP client for BambooHR account bamboo_http_client = httpx.AsyncClient( headers={ "Authorization": f"Basic {BASE64_API_KEY}", "x-account-id": "" } ) # Agent for HiBob integration hibob_agent = RemoteA2aAgent( name="hibob_agent", description="Agent for HiBob HR operations", agent_card="https://a2a.stackone.com/.well-known/agent-card.json", httpx_client=hibob_http_client ) # Agent for BambooHR integration bamboo_agent = RemoteA2aAgent( name="bamboo_agent", description="Agent for BambooHR operations", agent_card="https://a2a.stackone.com/.well-known/agent-card.json", httpx_client=bamboo_http_client ) # Orchestrator with both agents root_agent = Agent( model="gemini-2.0-flash", name="multi_hr_assistant", instruction=""" You have access to multiple HR systems: - hibob_agent: For HiBob HR operations - bamboo_agent: For BambooHR operations Route requests to the appropriate system based on user context. """, sub_agents=[hibob_agent, bamboo_agent], ) ``` ## Best Practices Provide detailed descriptions for your `RemoteA2aAgent` instances. The orchestrator uses these descriptions to decide when to delegate tasks. ```python theme={null} # Good: Specific description stackone_agent = RemoteA2aAgent( name="stackone_hr_agent", description="Handles employee management: listing employees, getting employee details, updating employee information, and managing time off requests.", ... ) # Bad: Vague description stackone_agent = RemoteA2aAgent( name="stackone_agent", description="HR agent", ... ) ``` Include clear delegation instructions in your orchestrator's system prompt: ```python theme={null} root_agent = Agent( instruction=""" When the user asks about: - Employee data -> delegate to stackone_hr_agent - Time off requests -> delegate to stackone_hr_agent - Local operations -> use local tools Always confirm before making changes to HR systems. """, ... ) ``` ADK handles A2A communication errors, but you should instruct your agent on how to respond: ```python theme={null} root_agent = Agent( instruction=""" If the HR agent returns an error or is unavailable: 1. Apologize to the user 2. Explain the issue briefly 3. Suggest trying again later or contacting support """, ... ) ``` When your application shuts down, clean up the HTTP client and agent resources: ```python theme={null} # Clean up when done await stackone_agent.cleanup() await http_client.aclose() ``` ## Next Steps Build custom A2A tools with the Python SDK Use the A2A JavaScript SDK Learn about authentication and security Learn A2A basics with cURL # AG2 Source: https://docs.stackone.com/a2a/framework-guides/ag2 Build agents in AG2 that communicate with StackOne's A2A agents using A2aRemoteAgent. ## Overview [AG2](https://docs.ag2.ai/) (formerly AutoGen) is an open-source framework for building AI agents. AG2 has built-in support for the A2A protocol through `A2aRemoteAgent`, allowing your agents to communicate with remote A2A agents like StackOne's. Use AG2 to build agents that: * Consume StackOne's A2A agents as remote agents * Orchestrate multi-agent conversations with local and remote agents * Access StackOne platform actions without managing tool definitions `A2aRemoteAgent` supports only asynchronous methods - this is a limitation of the A2A client. ## Installation Install AG2 with A2A support using `pip`: ```bash theme={null} pip install ag2[a2a] ``` ## Quick Start This example creates a local agent that delegates HR tasks to a StackOne A2A agent. ```python theme={null} import asyncio import base64 from autogen import ConversableAgent, LLMConfig from autogen.a2a import A2aRemoteAgent, HttpxClientFactory # StackOne A2A configuration STACKONE_API_KEY = "" STACKONE_ACCOUNT_ID = "" BASE64_API_KEY = base64.b64encode(f"{STACKONE_API_KEY}:".encode()).decode() # Create HTTP client with StackOne authentication headers http_client = HttpxClientFactory( headers={ "Authorization": f"Basic {BASE64_API_KEY}", "x-account-id": STACKONE_ACCOUNT_ID } ) # Create a remote A2A agent pointing to StackOne stackone_agent = A2aRemoteAgent( url="https://a2a.stackone.com", name="stackone_hr_agent", client=http_client ) # Create a local agent to orchestrate conversations llm_config = LLMConfig({"model": "gpt-4o-mini"}) orchestrator = ConversableAgent( name="hr_assistant", system_message=""" You are an HR assistant that helps with employee management tasks. Delegate HR operations to the stackone_hr_agent. When the user asks about employees, time off, or other HR data, use the stackone_hr_agent to fetch or update the information. """, llm_config=llm_config, ) # Start a conversation async def main(): await orchestrator.a_initiate_chat( recipient=stackone_agent, message={"role": "user", "content": "List the first 5 employees"} ) asyncio.run(main()) ``` See [Authentication Guide](/a2a/auth-security) for details on obtaining your API key and account ID. ## Architecture AG2's `A2aRemoteAgent` allows your local agent to communicate with remote A2A agents: ```mermaid theme={null} flowchart LR User[User] --> LocalAgent[Local Agent
AG2] LocalAgent -->|A2A Protocol| StackOneA2A[StackOne A2A Agent] StackOneA2A --> Providers[(Connected SaaS
HiBob, Workday, etc.)] LocalAgent --> LocalTools[Local Tools
Optional] style StackOneA2A fill:#10b981,stroke:#059669,color:#fff ``` ## Complete Example Here's a complete example with a local tool and a remote StackOne agent: ```python theme={null} import asyncio import base64 from datetime import datetime from autogen import ConversableAgent, LLMConfig from autogen.a2a import A2aRemoteAgent, HttpxClientFactory # Configuration STACKONE_API_KEY = "" STACKONE_ACCOUNT_ID = "" BASE64_API_KEY = base64.b64encode(f"{STACKONE_API_KEY}:".encode()).decode() # Local tool example def get_current_date() -> str: """Get the current date.""" return datetime.now().strftime("%Y-%m-%d") # Create HTTP client with StackOne authentication headers http_client = HttpxClientFactory( headers={ "Authorization": f"Basic {BASE64_API_KEY}", "x-account-id": STACKONE_ACCOUNT_ID } ) # Remote StackOne A2A agent stackone_agent = A2aRemoteAgent( url="https://a2a.stackone.com", name="stackone_hr_agent", client=http_client ) # Configure local agent with LLM llm_config = LLMConfig({"model": "gpt-4o-mini"}) # Root orchestrator agent orchestrator = ConversableAgent( name="hr_assistant", system_message=""" You are an HR assistant that helps with employee management tasks. You have access to: 1. get_current_date - Get today's date 2. stackone_hr_agent - Delegate HR operations (employees, time off, etc.) When users ask about HR data, delegate to stackone_hr_agent. Always confirm actions before making changes. """, llm_config=llm_config, ) # Register local tool orchestrator.register_for_llm(name="get_current_date", description="Get the current date")(get_current_date) async def main(): # Start conversation with StackOne agent await orchestrator.a_initiate_chat( recipient=stackone_agent, message={"role": "user", "content": "List all employees"} ) # Print the conversation history messages = orchestrator.chat_messages[stackone_agent.name] for message in messages: print(f"{message['name']}: {message['content']}") asyncio.run(main()) ``` ## Creating from AgentCard If you already have an `AgentCard` (e.g., fetched from a discovery service), create an `A2aRemoteAgent` directly from it: ```python theme={null} from a2a.types import AgentCard, AgentCapabilities from autogen.a2a import A2aRemoteAgent # Create or fetch an AgentCard card = AgentCard( name="stackone_hr_agent", url="https://a2a.stackone.com", description="Agent that handles HR operations via StackOne integrations", version="0.1.0", default_input_modes=["text"], default_output_modes=["text"], capabilities=AgentCapabilities(streaming=True), skills=[], supports_authenticated_extended_card=False, ) # Create the remote agent from the card stackone_agent = A2aRemoteAgent.from_card(card) ``` ## Human in the Loop Support `A2aRemoteAgent` automatically handles Human in the Loop (HITL) interactions when the remote agent requests human input. This happens transparently - the client loops until the agent completes its task or the conversation terminates. ```python theme={null} from autogen.a2a import A2aRemoteAgent # Connect to a remote agent that may request human input stackone_agent = A2aRemoteAgent( url="https://a2a.stackone.com", name="approval_agent", client=http_client ) # The client automatically handles any human input requests await orchestrator.a_initiate_chat( recipient=stackone_agent, message={"role": "user", "content": "Should I approve this budget proposal?"} ) # If the remote agent requests input, the user will be prompted automatically ``` ## Multiple StackOne Accounts Connect to multiple StackOne accounts by creating separate HTTP clients and `A2aRemoteAgent` instances: ```python theme={null} import base64 from autogen import ConversableAgent, LLMConfig from autogen.a2a import A2aRemoteAgent, HttpxClientFactory STACKONE_API_KEY = "" BASE64_API_KEY = base64.b64encode(f"{STACKONE_API_KEY}:".encode()).decode() # HTTP client for HiBob account hibob_http_client = HttpxClientFactory( headers={ "Authorization": f"Basic {BASE64_API_KEY}", "x-account-id": "" } ) # HTTP client for BambooHR account bamboo_http_client = HttpxClientFactory( headers={ "Authorization": f"Basic {BASE64_API_KEY}", "x-account-id": "" } ) # Agent for HiBob integration hibob_agent = A2aRemoteAgent( url="https://a2a.stackone.com", name="hibob_agent", client=hibob_http_client ) # Agent for BambooHR integration bamboo_agent = A2aRemoteAgent( url="https://a2a.stackone.com", name="bamboo_agent", client=bamboo_http_client ) # Orchestrator with access to both agents llm_config = LLMConfig({"model": "gpt-4o-mini"}) orchestrator = ConversableAgent( name="multi_hr_assistant", system_message=""" You have access to multiple HR systems: - hibob_agent: For HiBob HR operations - bamboo_agent: For BambooHR operations Route requests to the appropriate system based on user context. """, llm_config=llm_config, ) ``` ## Advanced Configuration ### Custom HTTP Client Set custom options on the HTTP client such as headers and timeout: ```python theme={null} from autogen.a2a import A2aRemoteAgent, HttpxClientFactory # Create custom HTTP client factory http_client = HttpxClientFactory( timeout=30.0, headers={ "User-Agent": "MyApp/1.0", "Authorization": f"Basic {BASE64_API_KEY}", "x-account-id": STACKONE_ACCOUNT_ID } ) stackone_agent = A2aRemoteAgent( url="https://a2a.stackone.com", name="stackone_hr_agent", client=http_client ) ``` ### Client Configuration Pass additional client configuration using `ClientConfig`: ```python theme={null} from a2a.client import ClientConfig from autogen.a2a import A2aRemoteAgent stackone_agent = A2aRemoteAgent( url="https://a2a.stackone.com", name="stackone_hr_agent", client=http_client, client_config=ClientConfig(streaming=True), ) ``` ### Testing with MockClient Mock remote agent replies for testing: ```python theme={null} from autogen.a2a import A2aRemoteAgent, MockClient stackone_agent = A2aRemoteAgent( url="https://a2a.stackone.com", name="stackone_hr_agent", client=MockClient(response_message="Mock response for testing"), ) ``` ## Best Practices `A2aRemoteAgent` only supports asynchronous methods. Always use `a_initiate_chat` and other async methods: ```python theme={null} # Correct: Use async methods await orchestrator.a_initiate_chat( recipient=stackone_agent, message={"role": "user", "content": "List employees"} ) # Incorrect: Sync methods won't work # orchestrator.initiate_chat(...) # This will fail ``` Provide detailed system messages for your orchestrator agent. Include clear instructions about when to delegate to remote agents: ```python theme={null} orchestrator = ConversableAgent( name="hr_assistant", system_message=""" You are an HR assistant. When users ask about: - Employee data -> delegate to stackone_hr_agent - Time off requests -> delegate to stackone_hr_agent - Local operations -> use local tools Always confirm before making changes to HR systems. """, llm_config=llm_config, ) ``` Wrap A2A calls in try-except blocks to handle network or protocol errors: ```python theme={null} async def safe_hr_query(message: str): try: await orchestrator.a_initiate_chat( recipient=stackone_agent, message={"role": "user", "content": message} ) except Exception as e: print(f"Error communicating with HR agent: {e}") # Handle fallback logic ``` Always provide messages in the correct format with `role` and `content`: ```python theme={null} # Correct format await orchestrator.a_initiate_chat( recipient=stackone_agent, message={"role": "user", "content": "List employees"} ) # Also valid: just content string await orchestrator.a_initiate_chat( recipient=stackone_agent, message="List employees" ) ``` ## Next Steps Build custom A2A tools with the Python SDK Use the A2A JavaScript SDK Learn about authentication and security Learn A2A basics with cURL # BeeAI Framework Source: https://docs.stackone.com/a2a/framework-guides/beeai Build agents in BeeAI Framework that communicate with StackOne's A2A agents using A2AAgent. ## Overview The [BeeAI Framework](https://framework.beeai.dev/) is an open-source framework for building AI agents, developed under the Linux Foundation. BeeAI has built-in support for the A2A protocol through `A2AAgent`, allowing your agents to communicate with remote A2A agents like StackOne's. Use BeeAI Framework to build agents that: * Consume StackOne's A2A agents as remote agents * Orchestrate multi-agent systems with local and remote agents * Access StackOne platform actions without managing tool definitions **Python only**: BeeAI Framework's `A2AAgent` supports custom authentication headers required for StackOne in Python. For TypeScript, use the [A2A JavaScript SDK](/a2a/sdk-guides/javascript) directly. ## Installation Install BeeAI Framework with A2A support: ```bash theme={null} pip install beeai-framework[a2a] ``` ## Quick Start This example creates an `A2AAgent` that connects to a StackOne A2A agent. ```python theme={null} import asyncio import base64 from beeai_framework.adapters.a2a.agents import A2AAgent, A2AAgentParameters, HttpxAsyncClientParameters from beeai_framework.memory.unconstrained_memory import UnconstrainedMemory # StackOne A2A configuration STACKONE_API_KEY = "" STACKONE_ACCOUNT_ID = "" BASE64_API_KEY = base64.b64encode(f"{STACKONE_API_KEY}:".encode()).decode() async def main(): # Create A2A agent with StackOne authentication agent = A2AAgent( url="https://a2a.stackone.com", memory=UnconstrainedMemory(), parameters=A2AAgentParameters( httpx_async_client=HttpxAsyncClientParameters( headers={ "Authorization": f"Basic {BASE64_API_KEY}", "x-account-id": STACKONE_ACCOUNT_ID } ) ) ) # Send a message and get the response response = await agent.run("List the first 5 employees") print(f"Agent response: {response.last_message.text}") asyncio.run(main()) ``` See [Authentication Guide](/a2a/auth-security) for details on obtaining your API key and account ID. ## Architecture BeeAI's `A2AAgent` allows your application to communicate with remote A2A agents: ```mermaid theme={null} flowchart LR App[Your Application] --> A2AAgent[A2AAgent
BeeAI Framework] A2AAgent -->|A2A Protocol| StackOneA2A[StackOne A2A Agent] StackOneA2A --> Providers[(Connected SaaS
HiBob, Workday, etc.)] style StackOneA2A fill:#10b981,stroke:#059669,color:#fff ``` ## Complete Example with Event Handling Here's a complete example that handles events and debug information: ```python theme={null} import asyncio import base64 from beeai_framework.adapters.a2a.agents import A2AAgent, A2AAgentUpdateEvent, A2AAgentParameters, HttpxAsyncClientParameters from beeai_framework.emitter import EventMeta from beeai_framework.memory.unconstrained_memory import UnconstrainedMemory from beeai_framework.errors import FrameworkError # Configuration STACKONE_API_KEY = "" STACKONE_ACCOUNT_ID = "" BASE64_API_KEY = base64.b64encode(f"{STACKONE_API_KEY}:".encode()).decode() async def main(): # Create A2A agent with StackOne authentication agent = A2AAgent( url="https://a2a.stackone.com", memory=UnconstrainedMemory(), parameters=A2AAgentParameters( httpx_async_client=HttpxAsyncClientParameters( headers={ "Authorization": f"Basic {BASE64_API_KEY}", "x-account-id": STACKONE_ACCOUNT_ID } ) ) ) # Define event handler for progress updates def handle_update(data: A2AAgentUpdateEvent, event: EventMeta) -> None: value = data.value debug_info = value[1] if isinstance(value, tuple) else value print(f"Agent progress: {debug_info}") # Run the agent with event handling try: response = await agent.run("List the first 5 employees").on("update", handle_update) print(f"\nAgent response: {response.last_message.text}") except FrameworkError as e: print(f"Error: {e.explain()}") asyncio.run(main()) ``` ## Interactive Chat Loop Create an interactive chat session with the StackOne agent: ```python theme={null} import asyncio import base64 from beeai_framework.adapters.a2a.agents import A2AAgent, A2AAgentParameters, HttpxAsyncClientParameters from beeai_framework.memory.unconstrained_memory import UnconstrainedMemory STACKONE_API_KEY = "" STACKONE_ACCOUNT_ID = "" BASE64_API_KEY = base64.b64encode(f"{STACKONE_API_KEY}:".encode()).decode() async def main(): agent = A2AAgent( url="https://a2a.stackone.com", memory=UnconstrainedMemory(), parameters=A2AAgentParameters( httpx_async_client=HttpxAsyncClientParameters( headers={ "Authorization": f"Basic {BASE64_API_KEY}", "x-account-id": STACKONE_ACCOUNT_ID } ) ) ) print("Connected to StackOne A2A Agent. Type 'quit' to exit.\n") while True: user_input = input("You: ").strip() if user_input.lower() == "quit": break response = await agent.run(user_input) print(f"Agent: {response.last_message.text}\n") asyncio.run(main()) ``` ## Multiple StackOne Accounts Connect to multiple StackOne accounts by creating separate `A2AAgent` instances: ```python theme={null} import base64 from beeai_framework.adapters.a2a.agents import A2AAgent, A2AAgentParameters, HttpxAsyncClientParameters from beeai_framework.memory.unconstrained_memory import UnconstrainedMemory STACKONE_API_KEY = "" BASE64_API_KEY = base64.b64encode(f"{STACKONE_API_KEY}:".encode()).decode() # Agent for HiBob integration hibob_agent = A2AAgent( url="https://a2a.stackone.com", memory=UnconstrainedMemory(), parameters=A2AAgentParameters( httpx_async_client=HttpxAsyncClientParameters( headers={ "Authorization": f"Basic {BASE64_API_KEY}", "x-account-id": "" } ) ) ) # Agent for BambooHR integration bamboo_agent = A2AAgent( url="https://a2a.stackone.com", memory=UnconstrainedMemory(), parameters=A2AAgentParameters( httpx_async_client=HttpxAsyncClientParameters( headers={ "Authorization": f"Basic {BASE64_API_KEY}", "x-account-id": "" } ) ) ) async def query_multiple_systems(): # Query HiBob hibob_response = await hibob_agent.run("List all employees") print(f"HiBob: {hibob_response.last_message.text}") # Query BambooHR bamboo_response = await bamboo_agent.run("List all employees") print(f"BambooHR: {bamboo_response.last_message.text}") ``` ## Memory Management BeeAI Framework provides memory implementations for conversation history: ```python theme={null} from beeai_framework.adapters.a2a.agents import A2AAgent, A2AAgentParameters, HttpxAsyncClientParameters from beeai_framework.memory.unconstrained_memory import UnconstrainedMemory # UnconstrainedMemory: Keeps all messages (default choice) agent = A2AAgent( url="https://a2a.stackone.com", memory=UnconstrainedMemory(), parameters=A2AAgentParameters( httpx_async_client=HttpxAsyncClientParameters( headers={ "Authorization": f"Basic {BASE64_API_KEY}", "x-account-id": STACKONE_ACCOUNT_ID } ) ) ) # Clear memory to start a new conversation async def start_new_conversation(): agent.memory = UnconstrainedMemory() # Memory must be empty before setting ``` ## Best Practices Choose the right memory implementation for your use case: ```python theme={null} from beeai_framework.memory.unconstrained_memory import UnconstrainedMemory agent = A2AAgent( url="https://a2a.stackone.com", memory=UnconstrainedMemory(), # ... ) ``` Use `UnconstrainedMemory` for typical interactions. Memory must be empty when assigned to an agent. Subscribe to update events for debugging and progress tracking: ```python theme={null} def handle_update(data: A2AAgentUpdateEvent, event: EventMeta) -> None: print(f"Debug: {data.value}") response = await agent.run("Query").on("update", handle_update) ``` This is especially useful for understanding agent behavior during development. Wrap agent calls in try-except blocks to handle errors gracefully: ```python theme={null} from beeai_framework.errors import FrameworkError from beeai_framework.agents import AgentError try: response = await agent.run("List employees") except AgentError as e: print(f"Agent error: {e}") # Handle agent-specific errors except FrameworkError as e: print(f"Framework error: {e.explain()}") # Handle framework errors ``` Use context IDs and task IDs to manage multi-turn conversations: ```python theme={null} # Start a new conversation response = await agent.run( "List employees", clear_context=True # Clears previous context ) # Continue an existing conversation (uses stored context) follow_up = await agent.run("Tell me more about the first employee") ``` ## Next Steps Build custom A2A tools with the Python SDK Use the A2A JavaScript SDK Learn about authentication and security Learn A2A basics with cURL # StackOne A2A Agents Source: https://docs.stackone.com/a2a/introduction Connect to agents built for StackOne platform actions in code or multi-agent systems via the Agent2Agent (A2A) Protocol. **Open Beta Feature**: StackOne A2A agents are currently in open beta. While fully functional, the API and features may evolve based on user feedback. **[Agent2Agent (A2A)](https://a2a-protocol.org/latest/)** is an open protocol that standardizes agent communication, i.e. sending and receiving messages in any format (text, audio, images, files, etc.), discovery, collaboration, authentication, etc. Essentially, if MCP provides a standard interface for agents to use tools, A2A provides a standard interface for users and agents to use agents. StackOne provides **production-ready A2A agents** for each linked account, giving you and your AI agents direct access to agents built for StackOne platform actions. See the complete list at [stackone.com/integrations](https://www.stackone.com/integrations). **Not sure if A2A is right for you?** Check out our [Agent Protocols Guide](/guides/calling-stackone-actions) to understand when to use A2A vs MCP vs AI Toolset (SDK). **What is A2A?** The Agent2Agent (A2A) Protocol is an open standard originally developed by Google and now maintained by the Linux Foundation. It defines how AI agents discover each other, authenticate, and exchange messages - regardless of what framework they were built with or who built them. Think of it as a common language that lets any agent talk to any other agent. **Why does A2A exist?** Without a standard protocol, connecting agents requires custom integrations for every pair of agents. A2A solves this by providing a single, universal interface. Your agent built with LangGraph can collaborate with an agent built with CrewAI, ADK, or any other framework - all using the same protocol. **How does it work?** A2A uses familiar web standards: HTTP for transport, JSON-RPC 2.0 for message format, and standard authentication methods. Agents publish an **Agent Card** (a JSON document describing their capabilities and skills) at a well-known URL. Clients fetch this card to discover what an agent can do, then send **Messages** to request work. The agent processes the request and returns either a direct response or a **Task** for longer-running operations. **A2A vs MCP**: These protocols are complementary, not competing. MCP (Model Context Protocol) standardizes how agents connect to **tools** - stateless functions like calculators or database queries. A2A standardizes how agents communicate with **other agents** - autonomous systems that can reason, plan, and have multi-turn conversations. Use MCP when you need tools; use A2A when you need to collaborate with other agents. **Simplified Authentication**: Authentication to third-party providers is completely abstracted - you only need your StackOne API key and account ID to access all integrated systems. Accounts must be linked beforehand through the [StackOne Integration Hub](/guides/accounts-section) or via [auth links](/guides/auth-link). ## A2A or MCP? You can use StackOne platform actions not only with A2A agents, but also with [MCP Servers](/mcp/quickstart) and the [Actions endpoint](/platform/api-reference/actions/make-an-rpc-call-to-an-action). Ultimately, experimentation is the best way to decide for your use case. However, here are some reasons you might try A2A vs. MCP: A2A agents are pre-built agents that are ready to go. No need to set up agent deployment, memory, etc. Just send text directly to the relevant agent with your StackOne API key and account ID. Create a multi-agent system with a single agent in your preferred framework. Just set up your custom orchestrator agent with the A2A MCP. Context bloat reduces the reliability of complex projects. Replacing tools with agents keeps tool calls out of the context, and improves accuracy and maintainability as your system grows. StackOne lets your agents discover and execute business actions with minimal setup, secured by unified enterprise authentication. ## How StackOne A2A Works ```mermaid theme={null} flowchart LR Client[A2A Client/Agent] <-->|JSON-RPC 2.0| StackOneA2A[StackOne A2A Agent] StackOneA2A <-->|MCP Streamable HTTP| StackOneMCP[StackOne MCP Server] StackOneMCP <-->|tools/list| Toolset[StackOne Tool Catalog] StackOneMCP <-->|tools/call| Providers[(Connected SaaS APIs)] IntegrationConfig[Integration & Account Settings] -->|Defines available tools| Toolset style StackOneA2A fill:#10b981,stroke:#059669,color:#fff style StackOneMCP fill:#10b981,stroke:#059669,color:#fff style Toolset fill:#10b981,stroke:#059669,color:#fff style IntegrationConfig fill:#10b981,stroke:#059669,color:#fff ``` When you send your StackOne API key and account ID to `https://a2a.stackone.com` to use the agent, its system message and [MCP server](/mcp/quickstart) are dynamically generated based on your account's configured integrations and enabled actions in the StackOne dashboard. **Agent Card Routes:** * **For connecting to agents:** Always use `https://a2a.stackone.com/.well-known/agent-card.json`. This route dynamically loads the agent card based on your account's configured integrations and enabled actions. * **For viewing available skills:** Connector-specific routes like `https://a2a.stackone.com/hibob/agent-card.json` are available for inspecting all available skills for a specific connector. These are for reference only and should not be used for agent connections. ## Key Features * **Ready to Use**: Pre-built agents with memory, etc. * **Agent per Integration**: Each StackOne Integration (aka Provider or Connector) has its own A2A agent * **Automatically Customised**: The A2A agent is dynamically generated based on your account's configured integrations and enabled actions * **Production Ready**: Authentication, rate limiting, and security built-in ## Getting Started Get started with the A2A UI and cURL Learn about headers, authentication, and security Use the official A2A SDKs to build your own tools Build agents in frameworks with A2A integrations ## Integration Options StackOne A2A agents can be used in two ways, depending on your use case: ### A2A SDK There are official A2A SDKs for [Python](https://github.com/a2aproject/a2a-python), [JavaScript](https://github.com/a2aproject/a2a-js), [Go](https://github.com/a2aproject/a2a-go), and [more](https://github.com/orgs/a2aproject/repositories). Use these to **build your own tools** in your preferred framework to send messages to StackOne's A2A agents. See a complete working example in the [StackOne A2A Agents Orchestrator Demo](https://github.com/StackOneHQ/a2a-demo), which shows how to build an orchestrator agent that routes requests to multiple StackOne A2A agents using the JavaScript SDK. ### Agent Frameworks There are official A2A integrations for [Agent Development Kit (ADK)](https://google.github.io/adk-docs/a2a/), [AG2](https://docs.ag2.ai/latest/docs/user-guide/a2a/), [BeeAI Framework](https://framework.beeai.dev/integrations/a2a), and [more](https://framework.beeai.dev/integrations/a2a). Build agents in these frameworks to easily send messages to StackOne's A2A agents. ## Next Steps Ready to connect your AI agents to StackOne? Start with our [Quickstart Guide](/a2a/quickstart) or jump directly to the [A2A Python SDK](https://github.com/a2aproject/a2a-python), [A2A JavaScript SDK](https://github.com/a2aproject/a2a-js), or your preferred framework. For more information about StackOne's platform capabilities, see our [AI Tools documentation](/agents/typescript/introduction). # A2A Quickstart Source: https://docs.stackone.com/a2a/quickstart Get started with StackOne's A2A agents with the A2A UI, and use cURL to retrieve an Agent Card, send a Message, and get a Task. ## Overview StackOne provides an A2A agent for each integration at: ```bash theme={null} https://a2a.stackone.com ``` **Authentication**: All requests require Basic authentication (`Authorization` header with base64-encoded API key) and your StackOne account ID (`x-account-id` header). This guide walks you through the basic A2A operations to help you understand the protocol before integrating with your preferred agent framework. *** ## Quick Testing Options Choose your preferred testing method to get started with StackOne's A2A agents: ### Interactive Testing Interface You can go to [a2a-ui.stackone.com](https://a2a-ui.stackone.com) and interact with StackOne agents directly in your browser. **Getting Started:** 1. Go to [a2a-ui.stackone.com](https://a2a-ui.stackone.com) 2. Click the Settings "⚙️" icon and enter your StackOne API key and account ID (see our [Authentication Guide](/a2a/auth-security) for details) 3. Click the "+ Agent" button, enter `https://a2a.stackone.com/.well-known/agent-card.json`, and click "Add Agent" 4. Start chatting with the agent! The response you get from `https://a2a.stackone.com/.well-known/agent-card.json` will depend on the `Authorization` and `x-account-id` headers you use. The A2A agent is dynamically generated based on your account's configured integrations and enabled actions. This is the fastest way to test A2A functionality without any setup! ### Programmatic Testing Perfect for developers who prefer command-line tools or want to integrate A2A into scripts. #### Step 1: Get an Agent Card Before interacting with an agent, you can fetch its Agent Card to understand its capabilities, skills, and authentication requirements. Agent Cards are available at the standard `.well-known` URI path: ```bash theme={null} curl -X GET https://a2a.stackone.com/.well-known/agent-card.json \ -H 'Authorization: Basic ' \ -H 'x-account-id: ' ``` **Example Response:** ```json theme={null} { "name": "StackOne HiBob", "description": "HiBob connector", "iconUrl": "https://stackone-logos.com/api/hibob/icon/svg", "protocolVersion": "0.3.4", "version": "0.1.0", "url": "https://a2a.stackone.com", "skills": [ { "id": "hibob_list_employees", "name": "List Employees", "description": "Get A List Of Employees", "tags": [ "stackone", "hibob", ] }, { "id": "hibob_get_employee", "name": "Get Employee", "description": "Get Details Of A Specific Employee", "tags": [ "stackone", "hibob", ] }, { "id": "hibob_update_employee", "name": "Update Employee", "description": "Update Employee Information", "tags": [ "stackone", "hibob", ] } ], "capabilities": { "streaming": true }, "defaultInputModes": [ "text/plain" ], "defaultOutputModes": [ "text/plain" ] } ``` The response you get from `https://a2a.stackone.com/.well-known/agent-card.json` will depend on the `Authorization` and `x-account-id` headers you use. The A2A agent is dynamically generated based on your account's configured integrations and enabled actions. **Agent Card Routes:** * **For connecting to agents:** Always use `https://a2a.stackone.com/.well-known/agent-card.json`. This route dynamically loads the agent card based on your account's configured integrations and enabled actions. * **For viewing available skills:** Connector-specific routes like `https://a2a.stackone.com/hibob/agent-card.json` are available for inspecting all available skills for a specific connector. These are for reference only and should not be used for agent connections. *** #### Step 2: Send a Message The `message/send` method sends a message to an agent to initiate a new interaction or continue an existing one. Each message requires a unique `messageId` (a UUID): Replace `messageId` with a unique UUID for each request. On macOS/Linux: `uuidgen | tr '[:upper:]' '[:lower:]'` ```bash theme={null} curl -X POST https://a2a.stackone.com \ -H 'Authorization: Basic ' \ -H 'x-account-id: ' \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc": "2.0", "id": "msg-1", "method": "message/send", "params": { "message": { "messageId": "550e8400-e29b-41d4-a716-446655440000", "role": "user", "parts": [ { "kind": "text", "text": "List the first 10 employees" } ], "kind": "message" }, "configuration": { "blocking": true } } }' ``` **Expected Response:** ```json theme={null} { "jsonrpc": "2.0", "id": "msg-1", "result": { "id": "task-123", "contextId": "ctx-456", "status": "completed", "artifacts": [ { "id": "artifact-1", "kind": "text", "parts": [ { "kind": "text", "text": "Here are the first 10 employees:\n1. John Doe - Software Engineer\n2. Jane Smith - Product Manager\n3. Bob Johnson - HR Specialist\n..." } ] } ], "history": [ { "role": "user", "parts": [ { "kind": "text", "text": "List the first 10 employees" } ] } ] } } ``` Pass `"configuration": { "blocking": false }` and poll `tasks/get` continuously for long-running operations. *** #### Step 3: Get Task Status Use `tasks/get` to retrieve the current state of a task (useful for long-running operations): ```bash theme={null} curl -X POST https://a2a.stackone.com \ -H 'Authorization: Basic ' \ -H 'x-account-id: ' \ -H 'Content-Type: application/json' \ -d '{ "jsonrpc": "2.0", "id": "task-query-1", "method": "tasks/get", "params": { "id": "task-123" } }' ``` **Expected Response:** ```json theme={null} { "jsonrpc": "2.0", "id": "task-query-1", "result": { "id": "task-123", "contextId": "ctx-456", "status": "completed", "artifacts": [ { "id": "artifact-1", "kind": "text", "parts": [ { "kind": "text", "text": "Here are the first 10 employees:\n1. John Doe - Software Engineer\n2. Jane Smith - Product Manager\n3. Bob Johnson - HR Specialist\n..." } ] } ], "history": [ { "role": "user", "parts": [ { "kind": "text", "text": "List the first 10 employees" } ] } ] } } ``` ## Next Steps Now that you understand the basic A2A operations, choose your integration path: Use the official A2A SDKs to build your own tools Build agents in frameworks with A2A integrations ## Common Issues & Solutions Verify your API key is correctly base64 encoded in the Authorization header Confirm that the `x-account-id` header matches your linked account ID, and ensure the account belongs to the same project as your API key Remember: A2A does not support query parameters for authentication, only headers Check that the account is active (i.e., is not in an error state or otherwise disabled) Verify your integrations are properly configured (agent skills are generated based on the enabled actions for the integration configuration associated with the linked account) # A2A JavaScript SDK Source: https://docs.stackone.com/a2a/sdk-guides/javascript Build agents that communicate with StackOne's A2A agents using the official A2A JavaScript SDK. ## Overview The [A2A JavaScript SDK](https://github.com/google-a2a/a2a-js) provides a JavaScript/TypeScript client library for building agents that interact with A2A agents following the [Agent2Agent (A2A) Protocol](https://a2a-protocol.org). Use this SDK to build agents that: * Discover and connect to StackOne's A2A agents * Send messages and receive responses * Manage multi-turn conversations with context ## Installation Install the A2A SDK using `npm`: ```bash theme={null} npm install @a2a-js/sdk ``` ## Quick Start Get an Agent Card, send a Message, and print the response. ```typescript theme={null} import { A2AClient, SendMessageSuccessResponse } from '@a2a-js/sdk/client'; import { Message, MessageSendParams } from '@a2a-js/sdk'; import { v4 as uuidv4 } from 'uuid'; const STACKONE_API_KEY = ''; const STACKONE_ACCOUNT_ID = ''; // Base64 encode the API key (with trailing colon for Basic auth) const base64EncodedApiKey = Buffer.from(`${STACKONE_API_KEY}:`).toString('base64'); async function main() { // Create a custom fetch that adds authentication headers const authenticatedFetch: typeof fetch = async (url, init) => { const headers = new Headers(init?.headers); headers.set('Authorization', `Basic ${base64EncodedApiKey}`); headers.set('x-account-id', STACKONE_ACCOUNT_ID); return fetch(url, { ...init, headers }); }; // Create a client pointing to the StackOne A2A Agent Card URL const client = await A2AClient.fromCardUrl( 'https://a2a.stackone.com/.well-known/agent-card.json', { fetchImpl: authenticatedFetch } ); // Send a message const sendParams: MessageSendParams = { message: { messageId: uuidv4(), role: 'user', parts: [{ kind: 'text', text: 'List the first 5 employees' }], kind: 'message', }, }; const response = await client.sendMessage(sendParams); if ('error' in response) { console.error('Error:', response.error.message); } else { const result = (response as SendMessageSuccessResponse).result; console.log('Agent response:', JSON.stringify(result, null, 2)); } } main(); ``` See [Authentication Guide](/a2a/auth-security) for details on obtaining your API key and account ID. ## Getting Agent Cards During agent initialisation, fetch an Agent Card for every A2A agent you want to send messages to. This is typically a setup step as opposed to a tool. Ensure to include the agent information in the agent's context (e.g. system message). ```typescript theme={null} import { A2AClient } from '@a2a-js/sdk/client'; import { AgentCard } from '@a2a-js/sdk'; const STACKONE_API_KEY = ''; const base64EncodedApiKey = Buffer.from(`${STACKONE_API_KEY}:`).toString('base64'); interface AgentCardWithAccountId { accountId: string; agentCard: AgentCard; } /** * Fetch Agent Cards from StackOne A2A agents for multiple accounts. * * The Agent URL is fixed to https://a2a.stackone.com and dynamically loads * an Agent Card based on the account ID. */ async function fetchAgentCards(accountIds: string[]): Promise> { const agentCards = new Map(); for (const accountId of accountIds) { try { // Create authenticated fetch for this account const authenticatedFetch: typeof fetch = async (url, init) => { const headers = new Headers(init?.headers); headers.set('Authorization', `Basic ${base64EncodedApiKey}`); headers.set('x-account-id', accountId); return fetch(url, { ...init, headers }); }; const client = await A2AClient.fromCardUrl( 'https://a2a.stackone.com/.well-known/agent-card.json', { fetchImpl: authenticatedFetch } ); const agentCard = client.agentCard; agentCards.set(agentCard.name, { accountId, agentCard }); } catch (error) { console.error(`Failed to fetch agent card for account ${accountId}:`, error); } } return agentCards; } /** * Get A2A agent information for agent context. */ function getA2AAgentInfo(agentCards: Map): string { const a2aAgentInfo: Array<{ name: string; description: string; skills: string[] }> = []; for (const { agentCard } of agentCards.values()) { a2aAgentInfo.push({ name: agentCard.name, description: agentCard.description, skills: (agentCard.skills || []).map((skill) => skill.name), }); } return ['', JSON.stringify(a2aAgentInfo, null, 4), ''].join('\n'); } async function main() { // Fetch agent cards from multiple StackOne accounts const accountIds = ['', '', '']; const agentCards = await fetchAgentCards(accountIds); // Get agent information for LLM context (e.g. system message) const a2aAgentInfo = getA2AAgentInfo(agentCards); console.log(a2aAgentInfo); } main(); ``` ## Sending Messages To send messages to A2A agents, give your agent access to a tool like below. The tool below gives the agent relatively high autonomy. You might want to abstract context IDs and task IDs outside of the tool. ```typescript theme={null} import { A2AClient, SendMessageSuccessResponse } from '@a2a-js/sdk/client'; import { AgentCard, Message, MessageSendParams, Task } from '@a2a-js/sdk'; import { v4 as uuidv4 } from 'uuid'; const STACKONE_API_KEY = ''; const base64EncodedApiKey = Buffer.from(`${STACKONE_API_KEY}:`).toString('base64'); interface AgentCardWithAccountId { accountId: string; agentCard: AgentCard; } // Map of agent names to their cards and account IDs const agentCards = new Map(); interface SendMessageResult { Message?: { messageId: string; role: string; parts: Array<{ kind: string; text?: string }>; contextId?: string; }; Task?: { status: { state: string; timestamp: string }; artifacts: Array<{ artifactId: string; name?: string; parts: Array<{ kind: string; text?: string }> }>; contextId: string; taskId: string; }; error?: { code: number; message: string }; } /** * Send a message to an agent and receive a response. * The response can be a Task, Message, or Error. * * To start a new conversation, set `contextId` and `taskId` to undefined. * * To continue a conversation, set `contextId` to the `contextId` of the previous conversation. * - If the previous Task has not terminated (input_required, auth_required), * set `taskId` to the `taskId` of the previous Task. * - If the previous Task has terminated (completed, canceled, rejected, or failed), * set `taskId` to undefined. */ async function sendMessageToAgent( agentName: string, message: string, contextId?: string, taskId?: string ): Promise { const agentEntry = agentCards.get(agentName); if (!agentEntry) { throw new Error(`Agent ${agentName} not found`); } const { accountId, agentCard } = agentEntry; // Create authenticated fetch for this account const authenticatedFetch: typeof fetch = async (url, init) => { const headers = new Headers(init?.headers); headers.set('Authorization', `Basic ${base64EncodedApiKey}`); headers.set('x-account-id', accountId); return fetch(url, { ...init, headers }); }; const client = await A2AClient.fromCardUrl(agentCard.url, { fetchImpl: authenticatedFetch, }); const sendParams: MessageSendParams = { message: { messageId: uuidv4(), role: 'user', parts: [{ kind: 'text', text: message }], kind: 'message', }, contextId, taskId, }; const response = await client.sendMessage(sendParams); // Handle error if ('error' in response) { return { error: response.error }; } const result = (response as SendMessageSuccessResponse).result; if (result.kind === 'message') { // Agent responded with a Message (i.e. didn't start a Task) const msg = result as Message; return { Message: { messageId: msg.messageId, role: msg.role, parts: msg.parts, contextId: msg.contextId, }, }; } else { // Agent responded with a Task (i.e. started a Task) // Tasks contain the whole message history. We typically want the // Task's status, and Artifacts (if the Task has completed). const task = result as Task; return { Task: { status: task.status, artifacts: task.artifacts || [], contextId: task.contextId, taskId: task.id, }, }; } } ``` See a complete working example in the [StackOne A2A Agents Orchestrator Demo](https://github.com/StackOneHQ/a2a-demo), which shows how to build an orchestrator agent that routes requests to multiple StackOne A2A agents, manages multi-turn conversations, and serves as an A2A server itself. ## Next Steps Use the A2A Python SDK Build agents with framework integrations Learn about authentication and security Learn A2A basics with cURL # A2A Python SDK Source: https://docs.stackone.com/a2a/sdk-guides/python Build agents that communicate with StackOne's A2A agents using the official A2A Python SDK. ## Overview The [A2A Python SDK](https://github.com/a2aproject/a2a-python) provides a Python client library for building agents that interact with A2A agents following the [Agent2Agent (A2A) Protocol](https://a2a-protocol.org). Use this SDK to build agents that: * Discover and connect to StackOne's A2A agents * Send messages and receive responses * Manage multi-turn conversations with context ## Installation Install the A2A SDK using `uv` or `pip`: ```bash theme={null} uv add a2a-sdk ``` ```bash theme={null} pip install a2a-sdk ``` ## Quick Start Get an Agent Card, send a Message, and print the response. ```python theme={null} import asyncio import base64 import uuid import httpx from a2a.client import A2ACardResolver, ClientFactory, ClientConfig from a2a.types import Message, Part, TextPart BASE64_ENCODED_STACKONE_API_KEY: str = base64.b64encode(b":").decode() STACKONE_ACCOUNT_ID: str = "" async def main() -> None: """ Fetch an Agent Card, send a message, and print the response. """ async with httpx.AsyncClient( headers={ "Authorization": f"Basic {BASE64_ENCODED_STACKONE_API_KEY}", "x-account-id": STACKONE_ACCOUNT_ID } ) as http_client: # Fetch the Agent Card card_resolver = A2ACardResolver(http_client, "https://a2a.stackone.com") card = await card_resolver.get_agent_card() print(card.model_dump_json(indent=4)) # Initialize client factory = ClientFactory(ClientConfig(httpx_client=http_client)) client = factory.create(card) # Send a message message = Message( messageId=str(uuid.uuid4()), role="user", parts=[Part(root=TextPart(kind="text", text="List the first 5 employees"))], kind="message" ) # send_message returns an async generator of streaming events async for event in client.send_message(message): if isinstance(event, tuple): task, update = event if task.status.state == "completed": for artifact in (task.artifacts or []): for part in (artifact.parts or []): if hasattr(part.root, "text"): print(part.root.text) if __name__ == "__main__": asyncio.run(main()) ``` See [Authentication Guide](/a2a/auth-security) for details on obtaining your API key and account ID. ## Getting Agent Cards During agent initialisation, fetch an Agent Card for every A2A agent you want to send messages to. This is typically a setup step as opposed to a tool. Ensure to include the agent information in the agent's context (e.g. system message). ```python theme={null} import asyncio import base64 import json from typing import Any import httpx from a2a.client import A2ACardResolver from a2a.types import AgentCard BASE64_ENCODED_STACKONE_API_KEY: str = base64.b64encode(b":").decode() async def fetch_agent_cards(account_ids: list[str]) -> dict[str, AgentCard]: """ Fetch Agent Cards from StackOne A2A agents for multiple accounts. The Agent URL is fixed to https://a2a.stackone.com and dynamically loads an Agent Card based on the account ID. Args: account_ids (list[str]): List of StackOne account IDs Returns: dict[str, AgentCard]: Dictionary mapping account IDs to their Agent Cards """ agent_cards: dict[str, AgentCard] = {} for account_id in account_ids: try: async with httpx.AsyncClient( headers={ "Authorization": f"Basic {BASE64_ENCODED_STACKONE_API_KEY}", "x-account-id": account_id } ) as http_client: card_resolver = A2ACardResolver(http_client, "https://a2a.stackone.com") card: AgentCard = await card_resolver.get_agent_card() agent_cards[card.name] = card except Exception as e: print(f"Failed to fetch agent card for account {account_id}: {e}") return agent_cards async def get_a2a_agent_info(agent_cards: dict[str, AgentCard]) -> str: """ Get A2A agent information for agent context. Args: agent_cards (dict[str, AgentCard]): Dictionary mapping account IDs to their Agent Cards Returns: str: Agent information for LLM context """ a2a_agent_info: list[dict[str, Any]] = [] for agent_card in agent_cards.values(): a2a_agent_info.append({ "name": agent_card.name, "description": agent_card.description, "skills": [ skill.name for skill in (agent_card.skills or []) ] }) return "\n".join([ "", json.dumps(a2a_agent_info, indent=4), "" ]) async def main() -> None: """ Fetch agent cards from multiple StackOne accounts and format for LLM context. """ # Fetch agent cards from multiple StackOne accounts account_ids: list[str] = ["", "", ""] agent_cards: dict[str, AgentCard] = await fetch_agent_cards(account_ids) # Get agent information for LLM context (e.g. system message) a2a_agent_info: str = await get_a2a_agent_info(agent_cards) print(a2a_agent_info) if __name__ == "__main__": asyncio.run(main()) ``` ## Sending Messages To send messages to A2A agents, give your agent access to a tool like below. The tool below gives the agent relatively high autonomy. You might want to abstract context IDs and task IDs outside of the tool. ```python theme={null} import uuid import base64 from typing import Any, List, Tuple import httpx from a2a.client import A2ACardResolver, ClientFactory, ClientConfig from a2a.types import ( AgentCard, Message, Part, TextPart, Task, ) BASE64_ENCODED_STACKONE_API_KEY: str = base64.b64encode(b":").decode() # Dictionary mapping agent names to their (account_id, AgentCard) pairs agent_cards: dict[str, tuple[str, AgentCard]] = {} async def send_message_to_agent( agent_name: str, message: str, ) -> dict[str, Any]: """ Send a message to an agent and receive a response. Args: agent_name (str): The name of the agent to send the message to message (str): The message text to send Returns: dict[str, Any]: A dictionary containing the response from the agent """ if agent_name not in agent_cards: raise ValueError(f"Agent {agent_name} not found") account_id, card = agent_cards[agent_name] async with httpx.AsyncClient( headers={ "Authorization": f"Basic {BASE64_ENCODED_STACKONE_API_KEY}", "x-account-id": account_id } ) as http_client: factory = ClientFactory(ClientConfig(httpx_client=http_client)) client = factory.create(card) msg = Message( messageId=str(uuid.uuid4()), role="user", parts=[Part(root=TextPart(kind="text", text=message))], kind="message" ) final_task: Task | None = None async for event in client.send_message(msg): if isinstance(event, tuple): task, update = event final_task = task if final_task is None: return {"error": "No response received"} # Tasks contain the whole message history. We typically want the # Task's status, and Artifacts (if the Task has completed). return { "Task": { "status": final_task.status.model_dump(), "artifacts": [artifact.model_dump() for artifact in (final_task.artifacts or [])], "context_id": final_task.contextId, "task_id": final_task.id, } } ``` See a complete working example in the [StackOne A2A Agents Orchestrator Demo](https://github.com/StackOneHQ/a2a-demo), which shows how to build an orchestrator agent that routes requests to multiple StackOne A2A agents, manages multi-turn conversations, and serves as an A2A server itself. ## Next Steps Use the A2A JavaScript SDK Build agents with framework integrations Learn about authentication and security Learn A2A basics with cURL # Basic Usage Source: https://docs.stackone.com/agents/python/basic-usage Learn the fundamentals of using the StackOne Python SDK for AI agents ## Quick Start The most common pattern: create a toolset and give your agent search and execute tools. The agent discovers and runs tools on demand. ```python theme={null} from stackone_ai import StackOneToolSet # search_and_execute returns the tool_search + tool_execute meta tools, so search # must be enabled on the toolset (pass search={} for defaults). toolset = StackOneToolSet(search={"method": "auto"}) # Your agent gets 2 tools: tool_search + tool_execute tools = toolset.openai(mode="search_and_execute", account_ids=["your-account-id"]) ``` Pass `tools` to any OpenAI-compatible model and the agent will search for relevant tools, then execute them automatically. See the [OpenAI integration guide](/agents/python/frameworks/openai-integration) for a full agent loop. ## Fetch Tools When you need specific tools instead of search and execute: ```python theme={null} tools = toolset.fetch_tools(account_ids=["your-account-id"]) openai_tools = tools.to_openai() ``` ```python theme={null} # Filter by providers tools = toolset.fetch_tools( providers=["hibob", "workday"], account_ids=["your-account-id"], ) # Filter by actions with exact match tools = toolset.fetch_tools( actions=["hibob_list_employees", "hibob_create_employees"], account_ids=["your-account-id"], ) # Filter by actions with glob patterns tools = toolset.fetch_tools( actions=["*_list_*"], account_ids=["your-account-id"], ) # Combine multiple filters tools = toolset.fetch_tools( providers=["hibob"], actions=["*_list_*"], account_ids=["your-account-id"], ) ``` See [Tool Filtering](/agents/python/tool-filtering) for the full reference. ```python theme={null} # Set accounts on the toolset for all subsequent calls toolset.set_accounts(["account-123", "account-456"]) tools = toolset.fetch_tools() # Or pass account IDs per request tools = toolset.fetch_tools(account_ids=["account-123", "account-456"]) # Loop over customer accounts dynamically customer_accounts = ["account-1", "account-2", "account-3"] for account_id in customer_accounts: tools = toolset.fetch_tools( actions=["workday_list_workers"], account_ids=[account_id], ) employee_tool = tools.get_tool("workday_list_workers") if employee_tool: employees = employee_tool.call() print(f"Found {len(employees['data'])} employees") ``` ## Tool Execution Direct execution is useful for testing and debugging. In production, your agent framework handles tool calls automatically. ```python theme={null} tools = toolset.fetch_tools(account_ids=["your-account-id"]) employee_tool = tools.get_tool("workday_list_workers") # call() with keyword arguments result = employee_tool.call(id="employee-123", include_details=True) print(result["data"]) # execute() with dictionary payloads result = employee_tool.execute({"id": "employee-123", "include_details": True}) # execute() with OpenAI-style arguments payload = {"arguments": {"id": "employee-123"}} result = employee_tool.execute(payload) ``` ```python theme={null} from stackone_ai.models import StackOneError, StackOneAPIError try: result = employee_tool.call(id="employee-123") print("Success:", result) except StackOneAPIError as e: print(f"API error: {e.message}") except StackOneError as e: print(f"StackOne error: {e.message}") except Exception as e: print(f"Unexpected error: {e}") ``` Actions that download a file (for example `googledrive_unified_download_file`, `documents_download_file`, or any `*_unified_download_file`) return raw bytes plus metadata, not parsed JSON. The SDK decides from the response `Content-Type`: JSON is parsed as usual, and anything else is treated as a file download. `call()` and `execute()` both return this dict directly (it isn't wrapped in a result object), so read its values with dict keys like `result["content"]`. ```python theme={null} tools = toolset.fetch_tools(actions=["googledrive_*"], account_ids=["your-account-id"]) download = tools.get_tool("googledrive_unified_download_file") result = download.execute({"id": "file-id"}) # result describes the file. Write the bytes straight to disk: with open(result["file_name"] or "download.bin", "wb") as f: f.write(result["content"]) ``` The returned dict, from both `call()` and `execute()`: | Key | Type | Description | | -------------- | ------------- | ------------------------------------------------------------------------------ | | `content` | `bytes` | Raw file bytes. Not JSON-serializable (see note). | | `content_type` | `str` | File MIME type (for example `application/pdf`), or `application/octet-stream`. | | `status_code` | `int` | HTTP status of the download response. | | `headers` | `dict` | Response headers. | | `file_name` | `str \| None` | Filename from `Content-Disposition` (RFC 5987 `filename*` aware), else `None`. | `content` is raw bytes and is not JSON-serializable. If you forward tool results to an LLM (or anything that re-serializes to JSON), handle or strip the `content` key. For example, base64-encode it on the LLM-facing path. ## Environment Configuration ```bash theme={null} export STACKONE_API_KEY=your_api_key ``` Account IDs are passed per-request when fetching tools or via `toolset.openai(mode="search_and_execute", account_ids=[...])`. ## Next Steps * [OpenAI Integration](/agents/python/frameworks/openai-integration) for building agents with function calling * [LangChain Integration](/agents/python/frameworks/langchain-integration) for framework-based workflows * [Tool Search](/agents/python/tool-search) for natural language tool discovery * [Tool Filtering](/agents/python/tool-filtering) for advanced filtering patterns # CrewAI Integration Source: https://docs.stackone.com/agents/python/frameworks/crewai-integration Build multi-agent AI workflows with StackOne tools and CrewAI Build sophisticated multi-agent AI systems where specialised agents collaborate to complete complex business tasks using StackOne's infrastructure of thousands of turnkey tools, RPC orchestration, and MCP/A2A entry points. CrewAI may have compatibility issues with StackOne's LangChain tools depending on your CrewAI and pydantic versions. If you encounter a `ValidationError` for `BaseTool`, check for version updates or use the [OpenAI integration](/agents/python/frameworks/openai-integration) instead. ## Overview * **Multi-agent collaboration** with business data access * **Specialized agents** for different business functions * **Complex workflow automation** with task dependencies * **Enhanced error handling** and monitoring ```python theme={null} from crewai import Agent, Crew, Task from stackone_ai import StackOneToolSet def create_hr_crew(account_id: str): """ Create multi-agent crew with tools for a specific account. In production, account_id comes from: - User/tenant context - Authentication middleware - Database lookup """ # Initialize toolset toolset = StackOneToolSet() # Fetch tools dynamically for this account tools = toolset.fetch_tools( account_ids=[account_id] ).to_langchain() # Optional: Filter by operation type for security # read_only_tools = toolset.fetch_tools( # account_ids=[account_id], # actions=["*_list_*", "*_get_*"] # Only list and get operations # ).to_langchain() # Create specialized agents hr_manager = Agent( role="HR Manager", goal="Manage employee data and handle HR requests", backstory="Expert in human resources with experience in employee management", tools=tools, llm="gpt-5.4", verbose=True ) recruiter = Agent( role="Recruiter", goal="Find and evaluate candidates for open positions", backstory="Experienced recruiter with talent acquisition expertise", tools=tools, llm="gpt-5.4", verbose=True ) # Create collaborative tasks employee_analysis_task = Task( description="Analyze current employee distribution by department", agent=hr_manager, expected_output="Employee distribution report with staffing recommendations" ) # Execute multi-agent crew crew = Crew( agents=[hr_manager, recruiter], tasks=[employee_analysis_task], verbose=True ) return crew # Usage: Get account from user context # Get account ID from your app's auth context or StackOne dashboard account_id = "your-account-id" crew = create_hr_crew(account_id) result = crew.kickoff() print(result) ``` ## Example