# A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport /development/api-reference/openapi.json get /api/v1/a2a/{provider_id}/http/{path} # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-1 /development/api-reference/openapi.json put /api/v1/a2a/{provider_id}/http/{path} # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-10 /development/api-reference/openapi.json delete /api/v1/a2a/{provider_id}/http # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-11 /development/api-reference/openapi.json options /api/v1/a2a/{provider_id}/http # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-12 /development/api-reference/openapi.json head /api/v1/a2a/{provider_id}/http # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-13 /development/api-reference/openapi.json patch /api/v1/a2a/{provider_id}/http # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-2 /development/api-reference/openapi.json post /api/v1/a2a/{provider_id}/http/{path} # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-3 /development/api-reference/openapi.json delete /api/v1/a2a/{provider_id}/http/{path} # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-4 /development/api-reference/openapi.json options /api/v1/a2a/{provider_id}/http/{path} # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-5 /development/api-reference/openapi.json head /api/v1/a2a/{provider_id}/http/{path} # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-6 /development/api-reference/openapi.json patch /api/v1/a2a/{provider_id}/http/{path} # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-7 /development/api-reference/openapi.json get /api/v1/a2a/{provider_id}/http # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-8 /development/api-reference/openapi.json put /api/v1/a2a/{provider_id}/http # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-9 /development/api-reference/openapi.json post /api/v1/a2a/{provider_id}/http # A2A Proxy Jsonrpc Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-jsonrpc-transport /development/api-reference/openapi.json post /api/v1/a2a/{provider_id}/ # A2A Proxy Jsonrpc Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-jsonrpc-transport-1 /development/api-reference/openapi.json post /api/v1/a2a/{provider_id} # Get Agent Card Source: https://agentstack.beeai.dev/api-reference/a2a/get-agent-card /development/api-reference/openapi.json get /api/v1/a2a/{provider_id}/.well-known/agent-card.json # Get Configuration Source: https://agentstack.beeai.dev/api-reference/configurations/get-configuration /development/api-reference/openapi.json get /api/v1/configurations/system # Update Configuration Source: https://agentstack.beeai.dev/api-reference/configurations/update-configuration /development/api-reference/openapi.json put /api/v1/configurations/system # Add Context History Item Source: https://agentstack.beeai.dev/api-reference/contexts/add-context-history-item /development/api-reference/openapi.json post /api/v1/contexts/{context_id}/history # Create Context Source: https://agentstack.beeai.dev/api-reference/contexts/create-context /development/api-reference/openapi.json post /api/v1/contexts # Delete Context Source: https://agentstack.beeai.dev/api-reference/contexts/delete-context /development/api-reference/openapi.json delete /api/v1/contexts/{context_id} # Delete Context History From Id Source: https://agentstack.beeai.dev/api-reference/contexts/delete-context-history-from-id /development/api-reference/openapi.json delete /api/v1/contexts/{context_id}/history # Generate Context Token Source: https://agentstack.beeai.dev/api-reference/contexts/generate-context-token /development/api-reference/openapi.json post /api/v1/contexts/{context_id}/token # Get Context Source: https://agentstack.beeai.dev/api-reference/contexts/get-context /development/api-reference/openapi.json get /api/v1/contexts/{context_id} # List Context Source: https://agentstack.beeai.dev/api-reference/contexts/list-context /development/api-reference/openapi.json get /api/v1/contexts # List Context History Source: https://agentstack.beeai.dev/api-reference/contexts/list-context-history /development/api-reference/openapi.json get /api/v1/contexts/{context_id}/history # Patch Context Metadata Source: https://agentstack.beeai.dev/api-reference/contexts/patch-context-metadata /development/api-reference/openapi.json patch /api/v1/contexts/{context_id}/metadata # Update Context Source: https://agentstack.beeai.dev/api-reference/contexts/update-context /development/api-reference/openapi.json put /api/v1/contexts/{context_id} # Create Text Extraction Source: https://agentstack.beeai.dev/api-reference/files/create-text-extraction /development/api-reference/openapi.json post /api/v1/files/{file_id}/extraction Create or return text extraction for a file. - If extraction is completed, returns existing result - If extraction failed, retries the extraction - If extraction is pending/in-progress, returns current status - If no extraction exists, creates a new one # Delete File Source: https://agentstack.beeai.dev/api-reference/files/delete-file /development/api-reference/openapi.json delete /api/v1/files/{file_id} # Delete Text Extraction Source: https://agentstack.beeai.dev/api-reference/files/delete-text-extraction /development/api-reference/openapi.json delete /api/v1/files/{file_id}/extraction # Get File Source: https://agentstack.beeai.dev/api-reference/files/get-file /development/api-reference/openapi.json get /api/v1/files/{file_id} # Get File Content Source: https://agentstack.beeai.dev/api-reference/files/get-file-content /development/api-reference/openapi.json get /api/v1/files/{file_id}/content # Get Text Extraction Source: https://agentstack.beeai.dev/api-reference/files/get-text-extraction /development/api-reference/openapi.json get /api/v1/files/{file_id}/extraction # Get Text File Content Source: https://agentstack.beeai.dev/api-reference/files/get-text-file-content /development/api-reference/openapi.json get /api/v1/files/{file_id}/text_content # List Files Source: https://agentstack.beeai.dev/api-reference/files/list-files /development/api-reference/openapi.json get /api/v1/files # Upload File Source: https://agentstack.beeai.dev/api-reference/files/upload-file /development/api-reference/openapi.json post /api/v1/files # Create Model Provider Source: https://agentstack.beeai.dev/api-reference/model_providers/create-model-provider /development/api-reference/openapi.json post /api/v1/model_providers # Delete Model Provider Source: https://agentstack.beeai.dev/api-reference/model_providers/delete-model-provider /development/api-reference/openapi.json delete /api/v1/model_providers/{model_provider_id} # Get Model Provider Source: https://agentstack.beeai.dev/api-reference/model_providers/get-model-provider /development/api-reference/openapi.json get /api/v1/model_providers/{model_provider_id} # List Model Providers Source: https://agentstack.beeai.dev/api-reference/model_providers/list-model-providers /development/api-reference/openapi.json get /api/v1/model_providers # Match Source: https://agentstack.beeai.dev/api-reference/model_providers/match /development/api-reference/openapi.json post /api/v1/model_providers/match # Create Chat Completion Source: https://agentstack.beeai.dev/api-reference/openai/create-chat-completion /development/api-reference/openapi.json post /api/v1/openai/chat/completions # Create Embedding Source: https://agentstack.beeai.dev/api-reference/openai/create-embedding /development/api-reference/openapi.json post /api/v1/openai/embeddings # List Models Source: https://agentstack.beeai.dev/api-reference/openai/list-models /development/api-reference/openapi.json get /api/v1/openai/models # Create Provider Build Source: https://agentstack.beeai.dev/api-reference/provider_builds/create-provider-build /development/api-reference/openapi.json post /api/v1/provider_builds # Delete Source: https://agentstack.beeai.dev/api-reference/provider_builds/delete /development/api-reference/openapi.json delete /api/v1/provider_builds/{id} # Get Provider Build Source: https://agentstack.beeai.dev/api-reference/provider_builds/get-provider-build /development/api-reference/openapi.json get /api/v1/provider_builds/{id} # List Provider Builds Source: https://agentstack.beeai.dev/api-reference/provider_builds/list-provider-builds /development/api-reference/openapi.json get /api/v1/provider_builds # Preview Provider Build Source: https://agentstack.beeai.dev/api-reference/provider_builds/preview-provider-build /development/api-reference/openapi.json post /api/v1/provider_builds/preview # Stream Logs Source: https://agentstack.beeai.dev/api-reference/provider_builds/stream-logs /development/api-reference/openapi.json get /api/v1/provider_builds/{id}/logs # Create Provider Discovery Source: https://agentstack.beeai.dev/api-reference/provider_discovery/create-provider-discovery /development/api-reference/openapi.json post /api/v1/providers/discovery # Get Provider Discovery Source: https://agentstack.beeai.dev/api-reference/provider_discovery/get-provider-discovery /development/api-reference/openapi.json get /api/v1/providers/discovery/{id} # Create Provider Source: https://agentstack.beeai.dev/api-reference/providers/create-provider /development/api-reference/openapi.json post /api/v1/providers # Delete Provider Source: https://agentstack.beeai.dev/api-reference/providers/delete-provider /development/api-reference/openapi.json delete /api/v1/providers/{id} # Get Provider Source: https://agentstack.beeai.dev/api-reference/providers/get-provider /development/api-reference/openapi.json get /api/v1/providers/{id} # Get Provider By Location Source: https://agentstack.beeai.dev/api-reference/providers/get-provider-by-location /development/api-reference/openapi.json get /api/v1/providers/by-location/{location} # List Provider Variables Source: https://agentstack.beeai.dev/api-reference/providers/list-provider-variables /development/api-reference/openapi.json get /api/v1/providers/{id}/variables # List Providers Source: https://agentstack.beeai.dev/api-reference/providers/list-providers /development/api-reference/openapi.json get /api/v1/providers # Patch Provider Source: https://agentstack.beeai.dev/api-reference/providers/patch-provider /development/api-reference/openapi.json patch /api/v1/providers/{id} # Preview Provider Source: https://agentstack.beeai.dev/api-reference/providers/preview-provider /development/api-reference/openapi.json post /api/v1/providers/preview # Stream Logs Source: https://agentstack.beeai.dev/api-reference/providers/stream-logs /development/api-reference/openapi.json get /api/v1/providers/{id}/logs # Update Provider Variables Source: https://agentstack.beeai.dev/api-reference/providers/update-provider-variables /development/api-reference/openapi.json put /api/v1/providers/{id}/variables # Get User Source: https://agentstack.beeai.dev/api-reference/user/get-user /development/api-reference/openapi.json get /api/v1/user # Get Me Source: https://agentstack.beeai.dev/api-reference/users/get-me /development/api-reference/openapi.json get /api/v1/users/me Get current user # List User Variables Source: https://agentstack.beeai.dev/api-reference/variables/list-user-variables /development/api-reference/openapi.json get /api/v1/variables # Update User Variables Source: https://agentstack.beeai.dev/api-reference/variables/update-user-variables /development/api-reference/openapi.json put /api/v1/variables # Add Items Source: https://agentstack.beeai.dev/api-reference/vector_stores/add-items /development/api-reference/openapi.json put /api/v1/vector_stores/{vector_store_id} # Create Vector Store Source: https://agentstack.beeai.dev/api-reference/vector_stores/create-vector-store /development/api-reference/openapi.json post /api/v1/vector_stores Create a new vector store. # Delete Document Source: https://agentstack.beeai.dev/api-reference/vector_stores/delete-document /development/api-reference/openapi.json delete /api/v1/vector_stores/{vector_store_id}/documents/{document_id} Delete a document by ID. # Delete Vector Store Source: https://agentstack.beeai.dev/api-reference/vector_stores/delete-vector-store /development/api-reference/openapi.json delete /api/v1/vector_stores/{vector_store_id} Delete a vector store by ID. # Get Vector Store Source: https://agentstack.beeai.dev/api-reference/vector_stores/get-vector-store /development/api-reference/openapi.json get /api/v1/vector_stores/{vector_store_id} Get a vector store by ID. # List Documents Source: https://agentstack.beeai.dev/api-reference/vector_stores/list-documents /development/api-reference/openapi.json get /api/v1/vector_stores/{vector_store_id}/documents List all documents in a vector store. # Search With Vector Source: https://agentstack.beeai.dev/api-reference/vector_stores/search-with-vector /development/api-reference/openapi.json post /api/v1/vector_stores/{vector_store_id}/search Search a vector store using either text or a vector. # Customize Agent Details Source: https://agentstack.beeai.dev/stable/agent-integration/agent-details Configure how your agent appears and behaves in the GUI When building your agent, you can configure certain attributes that affect how it appears and behaves in the user interface. The `@server.agent` decorator accepts a `detail` parameter with an `AgentDetail` object that controls the visual representation and behavior in the UI. You can customize various aspects of your agent's presentation, such as: * The type of user interface the agent uses (chat, form, etc.) * Custom user greetings * Metadata about tools the agent provides * Author and contributor information * License, framework, and source code details ## Basic Configuration Configuring agent details is straightforward. Import `AgentDetail` and related types, then pass them to the `detail` parameter in the `@server.agent` decorator: ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from textwrap import dedent from a2a.types import AgentSkill, Message from agentstack_sdk.a2a.extensions import AgentDetail, AgentDetailContributor, AgentDetailTool from agentstack_sdk.server import Server from agentstack_sdk.server.context import RunContext server = Server() @server.agent( name="Example Research Assistant", detail=AgentDetail( interaction_mode="multi-turn", # or single-turn user_greeting="Hi there! I can help you research topics or summarize uploaded documents.", tools=[ AgentDetailTool(name="Web Search", description="Looks up recent and relevant information from the web."), AgentDetailTool( name="Document Reader", description="Reads and extracts key insights from uploaded PDFs or text files." ), ], framework="BeeAI Framework", author=AgentDetailContributor( name="Agent Stack Team", email="team@example.com", ), source_code_url="https://github.com/example/example-research-assistant", ), skills=[ AgentSkill( id="research", name="Research", description=dedent( """\ Finds up-to-date information on a given topic, synthesizes key points, and summarizes findings in clear, useful responses. """ ), tags=["Search", "Knowledge"], examples=[ "Find recent news about AI ethics in 2025.", "What are the main challenges in renewable energy adoption?", "Give me an overview of current space exploration missions.", ], ), AgentSkill( id="summarization", name="Summarization", description=dedent( """\ Reads and summarizes uploaded text or documents, highlighting the most important ideas, statistics, and conclusions. """ ), tags=["Documents", "Summaries"], examples=[ "Summarize this PDF report about electric vehicle trends.", "What are the main points from this research article?", "Condense this document into a short summary I can share.", ], ), ], ) async def basic_configuration_example(input: Message, context: RunContext): """An example agent with detailed configuration""" yield "Hello World!" def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` ## Key Fields ### UI Configuration * **`interaction_mode`**: Set to `"multi-turn"` for conversational agents or `"single-turn"` for one-shot agents * **`user_greeting`**: Welcome message displayed when users first interact with your agent ### Metadata * **`framework`**: The framework used to build your agent (e.g., "BeeAI", "LangGraph", "CrewAI") * **`programming_language`**: Primary language (e.g., "Python", "JavaScript") * **`license`**: Software license (e.g., "Apache 2.0", "MIT") ### Tools * **`tools`**: List of `AgentDetailTool` objects describing your agent's capabilities * Each tool has a `name` and `description` to help users understand each tool's purpose ### Skills * **`skills`**: List of `AgentSkill` objects that represent distinct, user-facing abilities your agent can perform * Each skill includes an id, name, description, tags, and examples * Examples are displayed in the user interface as starter questions — they help users understand what kinds of requests your agent can handle and encourage exploration * Skills define what your agent can do — for example, “Research” for finding and summarizing information, or “Summarization” for condensing long documents ### Links and Attribution * **`source_code_url`**: Link to the source code repository * **`author`**: Primary author information (name, email, url) * **`contributors`**: List of additional contributors The Agent Details extension helps users understand your agent's capabilities before they start using it. Provide clear descriptions and accurate tool information to improve the user experience. # Configure Agent Settings Source: https://agentstack.beeai.dev/stable/agent-integration/agent-settings Allow users to configure agent behavior with interactive UI components Sometimes you need to give users control over how your agent behaves during a conversation. For example, users might want to enable or disable thinking mode, choose between different response styles, or configure other agent-specific parameters. The Agent Stack platform provides a settings as a part of Form extension that creates an interactive UI component where users can configure these options before or during their interaction with your agent. Settings extensions are a type of form [Service Extension](../agent-integration/overview#dependency-injection-service-extensions) that allows you to easily "inject dependencies" into your agent. This follows the inversion of control principle where your agent defines what it needs, and the platform provides those dependencies. ## Quickstart Import the necessary components from the Agent Stack SDK fomr extension. Inject the extension into your agent function using the `Annotated` type hint. Create a `SettingsFormRender` object with the fields you want users to configure. The only allowed field types for settings are `CheckboxGroupField` and `SingleSelectField`. Use `parse_settings_form()` to access the user's configuration choices. It is also possible to combine setting with initial form using `FormServiceExtensionSpec.demand_forms`, but they need to be parsed separately. ## Basic Settings Example Here's how to add simple user defined settings to your agent: ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from collections.abc import AsyncGenerator from typing import Annotated from a2a.types import Message from agentstack_sdk.a2a.extensions import ( CheckboxField, CheckboxGroupField, OptionItem, SettingsFormRender, SingleSelectField, FormServiceExtensionServer, FormServiceExtensionSpec, ) from agentstack_sdk.a2a.types import RunYield from agentstack_sdk.server import Server from agentstack_sdk.server.context import RunContext from pydantic import BaseModel class SettingsModel(BaseModel): checkbox_settings: dict[str, bool] | None = None response_style: str | None = None server = Server() @server.agent() async def basic_settings_example( message: Message, context: RunContext, form: Annotated[ FormServiceExtensionServer, FormServiceExtensionSpec.demand_settings( settings_form=SettingsFormRender( fields=[ CheckboxGroupField( id="checkbox_settings", label="Thinking Options", fields=[ CheckboxField( id="thinking", label="Enable Thinking", content="Show agent's reasoning process", default_value=True, ), CheckboxField( id="memory", label="Enable Memory", content="Use available memory system", default_value=True, ), ], ), SingleSelectField( id="response_style", label="Response Style", options=[ OptionItem(id="concise", label="Concise"), OptionItem(id="detailed", label="Detailed"), OptionItem(id="humorous", label="Humorous"), ], default_value="concise", ), ], ) ), ], ) -> AsyncGenerator[RunYield, Message]: """Demonstrate settings form extension""" if not form: yield "Form extension hasn't been activated, no settings are available" return parsed_settings = form.parse_settings_form(model=SettingsModel) if not parsed_settings: yield "No settings provided" return if not isinstance(parsed_settings, SettingsModel): yield f"Parsed settings in unexpected format: {parsed_settings}" return response = "Settings:\n" if parsed_settings.checkbox_settings: thinking_enabled = parsed_settings.checkbox_settings["thinking"] choice = "enabled" if thinking_enabled else "disabled" response += f"- Thinking is {choice}.\n" memory_enabled = parsed_settings.checkbox_settings["memory"] choice = "enabled" if memory_enabled else "disabled" response += f"- Memory is {choice}.\n" response_style = parsed_settings.response_style choice = response_style if response_style else "not set" response += f"- Response style: {choice}\n" yield response # Warning: the code below is not automatically tested, check the implementation if you want to use unparsed settings. # Alternatively, you can use the raw data without parsing into a model: # parsed_settings = form.parse_settings_form() # if not parsed_settings: # yield "No settings provided" # return # thinking_field = parsed_settings.values.get("checkbox_settings") # if thinking_field and thinking_field.type == "checkbox_group": # thinking_enabled = thinking_field.value and thinking_field.value.get("thinking", False) # if thinking_enabled: # yield "Thinking is enabled. " # else: # yield "Thinking is disabled. " # response_style_field = parsed_settings.values.get("response_style") # if response_style_field and response_style_field.type == "singleselect": # yield f"Response style: {response_style_field.value}" def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` ## How to work with settings Here's what you need to know to add settings capabilities to your agent: **Import the form extension**: Import `FormServiceExtensionServer`, `FormServiceExtensionSpec`, `SettingsFormRender`, and field types from `agentstack_sdk.a2a.extensions`. **Inject the extension**: Add a form parameter to your agent function using the `Annotated` type hint with `FormServiceExtensionServer` and `FormServiceExtensionSpec.demand_settings()`. **Define your settings structure**: Create a `SettingsFormRender` object with the fields you want users to configure. Only `CheckboxGroupField` and `SingleSelectField` are allowed for settings. **Check if the extension exists**: Always verify that the form extension is provided before using it, as service extensions are optional. **Parse settings data**: Use `form.parse_settings_form()` to access the user's configuration choices. **Access field values**: Use `parsed_settings.values['field_id']` to access the submitted values from your settings fields. Settings are presented to users in a clean, organized interface that makes it easy to configure your agent's behavior. The platform automatically handles the UI rendering and data collection. **Check if the extension exists**: Always verify that the form extension is provided before using it, as service extensions are optional. ## Settings Field Types Agent Stack supports various field types for collecting different kinds of configuration data: ### CheckboxGroupField Groups multiple checkboxes together for related boolean options. A single checkbox must still be wrapped inside the `CheckboxGroupField`. ```python theme={null} from agentstack_sdk.a2a.extensions import CheckboxField, CheckboxGroupField CheckboxGroupField( id="features", label="Features", fields=[ CheckboxField( id="thinking", label="Show Thinking Process", content="Display the agent's reasoning process", default_value=True, ), CheckboxField( id="citations", label="Include Citations", content="Add source citations to responses", default_value=False, ), ], ) ``` ### SingleSelectField Dropdown fields for choosing an option from a list: ```python theme={null} from agentstack_sdk.a2a.extensions import OptionItem, SingleSelectField SingleSelectField( id="response_style", label="Response Style", options=[ OptionItem(id="formal", label="Formal"), OptionItem(id="casual", label="Casual"), OptionItem(id="technical", label="Technical"), ], default_value="casual", ) ``` # Work with Canvas Source: https://agentstack.beeai.dev/stable/agent-integration/canvas Handle artifact editing requests from users The Canvas extension enables users to request edits to specific parts of artifacts (like code, documents, or other structured content) that your agent has generated. Instead of users describing what they want to change in text, they can select a portion of an artifact in the UI and request an edit, giving your agent precise information about what to modify. ## How Canvas Works When a user selects a portion of an artifact and requests an edit: 1. The selection's **start and end indices** (character positions) are captured. 2. The user provides a **description** of what they want to change. 3. The **artifact ID** identifies which artifact to edit. 4. Your agent receives this structured information to process the edit request. This allows your agent to: * Know exactly which part of the artifact the user wants to modify. * Access the original artifact content from history. * Make targeted edits based on the user's description. * Generate a new artifact with the changes (using the same artifact\_id to replace the previous version in the UI). ## Example: Canvas with LLM Here's how to use canvas with an LLM, adapting your system prompt based on whether you're generating new content or editing existing content: ````python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 from typing import Annotated from a2a.types import Message, TextPart from agentstack_sdk.a2a.extensions import ( LLMServiceExtensionServer, LLMServiceExtensionSpec, CanvasExtensionServer, CanvasExtensionSpec, ) from agentstack_sdk.a2a.types import AgentArtifact from agentstack_sdk.server import Server from agentstack_sdk.server.context import RunContext server = Server() BASE_PROMPT = """\ You are a helpful coding assistant. Generate code enclosed in triple-backtick blocks tagged ```python. The first line should be a comment with the code's purpose. """ EDIT_PROMPT = ( BASE_PROMPT + """ You are editing existing code. The user selected this portion: ```python {selected_code} ``` They want: {description} Respond with the FULL updated code. Only change the selected portion. """ ) def get_system_prompt(canvas_edit): if not canvas_edit: return BASE_PROMPT # Check if parts list is not empty and first part is TextPart if not canvas_edit.artifact.parts or not isinstance(canvas_edit.artifact.parts[0].root, TextPart): return BASE_PROMPT original_code = canvas_edit.artifact.parts[0].root.text # Validate indices are within bounds if not (0 <= canvas_edit.start_index <= canvas_edit.end_index <= len(original_code)): return BASE_PROMPT selected = original_code[canvas_edit.start_index : canvas_edit.end_index] return EDIT_PROMPT.format(selected_code=selected, description=canvas_edit.description) async def call_llm(llm: LLMServiceExtensionServer, system_prompt: str, message: Message): """Call your LLM with the adapted prompt (implementation depends on your LLM framework).""" # As a placeholder, we return a mock response. example = "```python\n# Hard-coded example (no LLM used). Above is the prompt to use. This is the fake response.\nprint('Hello from LLM!')\n```" artifact = AgentArtifact( name="Response", parts=[ TextPart(text=system_prompt), # This is just for demonstration. Replace with actual LLM call. TextPart(text=example), # This is just for demonstration. Replace with actual LLM call. ], ) return artifact @server.agent() async def code_agent( message: Message, context: RunContext, llm: Annotated[LLMServiceExtensionServer, LLMServiceExtensionSpec.single_demand()], canvas: Annotated[CanvasExtensionServer, CanvasExtensionSpec()], ): await context.store(message) canvas_edit = await canvas.parse_canvas_edit_request(message=message) # Adapt system prompt based on whether this is an edit or new generation system_prompt = get_system_prompt(canvas_edit) artifact = await call_llm(llm, system_prompt, message) yield artifact await context.store(artifact) if __name__ == "__main__": server.run() ```` Import `CanvasExtensionServer` and `CanvasExtensionSpec` from `agentstack_sdk.a2a.extensions`. Add a canvas parameter to your agent function using the `Annotated` type hint with `CanvasExtensionSpec()`. Call `await canvas.parse_canvas_edit_request(message=message)` to check if the incoming message contains a canvas edit request. This returns `None` if no edit request is present, or a `CanvasEditRequest` object with:
start\_index The starting character position of the selected text
end\_index The ending character position of the selected text
description The user's description of what they want to change
artifact The full original artifact object from history
Extract the text from `artifact.parts[0].root.text` (for text artifacts) into a `content` variable and use the start/end indices to get the selected portion: `selected_text = content[start_index:end_index]`. Create a new artifact with your changes.
## How to work with Canvas **Artifacts in history**: The extension automatically retrieves the original artifact from history using the `artifact_id`. If not found, a `ValueError` is raised. **Text parts filtering**: The extension filters out fallback text messages (sent for agents that don't support canvas) so you only work with structured edit request data. ## Best Practices **Adapt your system prompt**: Provide different instructions to your LLM depending on whether you're generating new content or editing existing content. **Validate indices**: Ensure start and end indices are within bounds before slicing the artifact text. ## Examples For more examples, see: * [canvas\_ui\_code\_agent.py](https://github.com/i-am-bee/agentstack/blob/main/apps/agentstack-sdk-py/examples/canvas_ui_code_agent.py) * [canvas\_ui\_test\_agent.py](https://github.com/i-am-bee/agentstack/blob/main/apps/agentstack-sdk-py/examples/canvas_ui_test_agent.py) # Add Citations to Agent Responses Source: https://agentstack.beeai.dev/stable/agent-integration/citations Add source references to agent responses with clickable links The Citation extension allows agents to include source references in their responses. Citations appear as highlighted text with hover tooltips and clickable source links in the Agent Stack UI, making it easy for users to verify the information. Each citation requires: | Field | Description | | ------------- | ---------------------- | | `url` | Source link | | `title` | Display title | | `description` | Brief explanation | | `start_index` | Start position in text | | `end_index` | End position in text | ## Example: Basic Usage ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from typing import Annotated from a2a.types import Message from agentstack_sdk.a2a.extensions import Citation, CitationExtensionServer, CitationExtensionSpec from agentstack_sdk.server import Server from agentstack_sdk.server.context import RunContext server = Server() @server.agent() async def citation_basic_usage_example( input: Message, context: RunContext, citation: Annotated[CitationExtensionServer, CitationExtensionSpec()] ): response_text = "Python is the most popular programming language for AI development." citations = [ Citation( url="https://survey.stackoverflow.com/2023", title="Stack Overflow Developer Survey 2023", description="Annual survey of developer preferences and trends", start_index=0, end_index=47, # "Python is the most popular programming language" ) ] yield citation.message(text=response_text, citations=citations) def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` Import `CitationExtensionServer` and `CitationExtensionSpec` from `agentstack_sdk.a2a.extensions`. Add a citation parameter to your agent function using the `Annotated` type hint with `CitationExtensionSpec()`. Call `yield citation.message()` with `text` and `citations`. ## Multiple Citations Yield multiple citations in a single response: ```python theme={null} response_text = "Python leads AI development while JavaScript dominates web development." citations = [ { "url": "https://ai-survey.com", "title": "AI Language Survey", "description": "Programming language usage in AI", "start_index": 0, "end_index": 31 # "Python leads AI development" }, { "url": "https://web-stats.com", "title": "Web Development Report", "description": "Web programming language statistics", "start_index": 38, "end_index": 67 # "JavaScript dominates web development" } ] ``` ## Example: Advanced Usage For a more advanced example, see [citation\_agent.py](https://github.com/i-am-bee/agentstack/blob/main/apps/agentstack-sdk-py/examples/citation_agent.py). # Environment Variables Source: https://agentstack.beeai.dev/stable/agent-integration/env-variables Declare which environment variables the agent requires so they can be provided by the Agent Stack runtime. Sometimes you need to configure your agent with environment variables that should be provided by the runtime. For example, you might want to globally enable or disable thinking mode for all users of your agent, or configure API endpoints, feature flags, or other environment settings. Normally, you would set these environment variables directly in your deployment environment. However, when your agent is deployed to Agent Stack via the `agentstack add` command, the platform manages the runtime environment and you can't directly control it. The Agent Stack platform allows you to declare environment variable requirements through `AgentDetail`. The platform will then provide these variables to your agent at runtime, and you can access them using standard `os.getenv()` calls. Environment variables declared in `AgentDetail` are automatically provided by the Agent Stack platform when your agent is deployed via `agentstack add`. When running your agent locally or with auto-registration via SDK, you're responsible for providing these variables yourself. ## Basic Environment Variables Example Here's how to request environment variables for your agent: ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from a2a.types import Message from agentstack_sdk.a2a.extensions import AgentDetail, EnvVar from agentstack_sdk.server import Server from agentstack_sdk.server.context import RunContext server = Server() @server.agent( detail=AgentDetail( interaction_mode="multi-turn", variables=[ EnvVar(name="THINKING_ENABLED", description="Whether to enable thinking mode for all users", required=True) ], ) ) async def basic_environment_variables_example(input: Message, context: RunContext): """Agent that uses environment variables for configuration""" thinking_enabled = os.getenv("THINKING_ENABLED", "false").lower() == "true" if thinking_enabled: yield "Thinking mode is enabled. I'll show my reasoning process." else: yield "Thinking mode is disabled. I'll provide direct responses." def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` Import the necessary components from the Agent Stack SDK. Add an `EnvVar` list to the `variables` field in your `AgentDetail` configuration. Use `agentstack add` command to [deploy your agent in Agent Stack](../deploy-agents/deploy-your-agents) Configure environment variables for your agent using `agentstack env add "Name or ID of your Agent" KEY=VALUE`. Use `os.getenv()` to access the environment variables in your agent code. ## Managing Environment Variables Once your agent is deployed to Agent Stack, you need to provide the environment variables it requires. You can manage these variables using the `agentstack env` CLI commands. ### Adding Environment Variables To provide environment variables to your agent, use the `agentstack env add` command: ```bash theme={null} agentstack env add "Name or ID of the agent" FIRST_VAR=VALUE SECOND_VAR=VALUE ... ``` You can add multiple variables at once by including them all in a single command. ### Listing Environment Variables To view the environment variables configured for an agent: ```bash theme={null} agentstack env list "Name or ID of the agent" ``` ### Removing Environment Variables To remove an environment variable from an agent: ```bash theme={null} agentstack env remove "Name or ID of the agent" VARIABLE_NAME ``` ### Checking Missing Variables When a required variable hasn't been provided, you can see this in the agent list. Use `agentstack list` to view all agents - the `INFO` column shows which variables still need to be configured. # Handle Errors Source: https://agentstack.beeai.dev/stable/agent-integration/error Learn how your agent can report formatted errors to the UI. The Error extension provides a standardized way to report errors from your agent to the UI. The Agent Stack SDK automatically handles exceptions raised within your agent function and uses the Error extension to report them. **No configuration is required** for standard error reporting. ## Standard Error Reporting Simply raise exceptions in your agent code. The SDK will catch them and use the extension to format the error message. ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from a2a.types import Message from agentstack_sdk.server import Server from agentstack_sdk.server.context import RunContext server = Server() @server.agent() async def standard_error_reporting_example(input: Message, context: RunContext): # ... do some work ... raise ValueError("Something went wrong!") def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` ## Advanced Configuration If you want to customize the error reporting, for example to include stack traces, you can inject the extension with custom parameters. ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from typing import Annotated from a2a.types import Message from agentstack_sdk.a2a.extensions import ( ErrorExtensionParams, ErrorExtensionServer, ErrorExtensionSpec, ) from agentstack_sdk.server import Server server = Server() @server.agent() async def advanced_error_reporting_example( input: Message, # Configure to include stack traces error_ext: Annotated[ ErrorExtensionServer, ErrorExtensionSpec(params=ErrorExtensionParams(include_stacktrace=True)), ], ): """Agent that demonstrates error handling with stack traces""" yield "Working..." # This exception will be caught and formatted by the extension # The stack trace will be included because of the configuration above raise ValueError("Something went wrong!") def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` Import `ErrorExtensionServer`, `ErrorExtensionSpec`, and `ErrorExtensionParams`. Inject the extension using `Annotated` and set `include_stacktrace=True`. Enable stack traces during development for easier debugging, but consider disabling them in production to avoid leaking implementation details. ## Handling Multiple Errors The Error extension supports `ExceptionGroup` to report multiple errors at once. When an `ExceptionGroup` is raised, it is formatted as a group of errors in the UI. ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from a2a.types import Message from agentstack_sdk.server import Server from agentstack_sdk.server.context import RunContext server = Server() @server.agent() async def multiple_errors_handling_example(input: Message, context: RunContext): raise ExceptionGroup("Multiple failures", [ValueError("First error"), TypeError("Second error")]) def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` ## Adding Context You can attach arbitrary context to errors by accessing the injected `ErrorExtensionServer` instance. This context will be included in the error metadata sent to the client. ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from typing import Annotated, cast from a2a.types import Message from agentstack_sdk.a2a.extensions import ( ErrorExtensionParams, ErrorExtensionServer, ErrorExtensionSpec, ) from agentstack_sdk.server import Server from pydantic import JsonValue server = Server() @server.agent() async def adding_error_context_example( input: Message, error_ext: Annotated[ ErrorExtensionServer, ErrorExtensionSpec(params=ErrorExtensionParams(include_stacktrace=False)) ], ): # Add context before an error might occur or in an except block ctx = cast(dict[str, JsonValue], error_ext.context) ctx["request_id"] = "req-123" ctx["user_id"] = 42 # ... do work ... # If an exception is raised, the context is included raise ValueError("Something went wrong!") def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` ## Examples For more examples, see: * [error\_agent.py](https://github.com/i-am-bee/agentstack/blob/main/apps/agentstack-sdk-py/examples/error_agent.py) # Working with Files Source: https://agentstack.beeai.dev/stable/agent-integration/files Upload and generate files in agents One of the most common use cases for AI agents is working with files. Your agent should be able to read files from user uploads and generate new files as outputs. The Agent Stack makes this seamless through the A2A protocol's `FilePart`. ## Example of File Processing Here's how to build an agent that can accept and modify files: ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from typing import Annotated from a2a.types import FilePart, Message from agentstack_sdk.a2a.extensions import PlatformApiExtensionServer, PlatformApiExtensionSpec from agentstack_sdk.platform import File from agentstack_sdk.server import Server from agentstack_sdk.util.file import load_file server = Server() @server.agent( default_input_modes=["text/plain", "application/pdf", "image/*"], default_output_modes=["text/plain", "application/pdf", "image/*"], ) async def file_processing_example( input: Message, _: Annotated[PlatformApiExtensionServer, PlatformApiExtensionSpec()], ): """Agent that handles both text and binary files""" for file_part in input.parts: file_part_root = file_part.root if isinstance(file_part_root, FilePart): mime_type = file_part_root.file.mime_type or "application/octet-stream" async with load_file(file_part_root) as loaded_content: # Determine if file is text or binary based on MIME type is_text_file = mime_type.startswith("text/") or mime_type in [ "application/json", "application/xml", "text/xml", ] if is_text_file: # For text files, use .text and encode to bytes content = loaded_content.text.encode() else: # For binary files (PDFs, images, etc.), use .content directly content = loaded_content.content # Create new file with appropriate content new_file = await File.create( filename=f"processed_{file_part_root.file.name}", content_type=mime_type, content=content, ) yield new_file.to_file_part() yield "File processing complete" def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` Add the `default_input_modes` parameter to your agent decorator only if you want users to upload files to your agent. This specifies which file types users can upload. Import and use the `PlatformApiExtensionServer` to access file creation capabilities. This extension provides your agent with the proper context and authentication needed to use the Agent Stack API for creating and managing files. If not provided, your agent will receive unauthorized responses when working with files. Iterate through message parts to find `FilePart` objects and load their content using `load_file` helper. Use the `File.create()` method to generate new files and yield them as `FilePart` objects with `to_file_part()`. ## How to work with files Here's what you need to know to add file processing capabilities to your agent: **Enable file uploads**: Add `default_input_modes` to your agent decorator with a list of MIME types you want to accept (e.g., `["text/plain", "application/pdf", "image/jpeg"]`). **Enable producing of files**: Add `default_output_modes` to your agent decorator with a list of MIME types that your agent can potentially produce (e.g., `["text/plain", "application/pdf", "image/jpeg"]`). **Access the Platform API**: Import and use `PlatformApiExtensionServer` to get access to file manipulation capabilities. **Process message parts**: Iterate through `input.parts` to find FilePart objects that represent uploaded files. **Load file content**: Use `load_file()` with an async context manager to safely access file content. **Create new files**: Use `File.create()` to generate new files with custom names, content types, and content. **Yield file outputs**: The `File` object created by the SDK can be easily converted to a `FilePart` using the `to_file_part()` method and then yielded as agent outputs. ## File Upload Configuration The `default_input_modes` parameter controls which file types users can upload: ```python theme={null} @server.agent( default_input_modes=[ "text/plain", # Plain text files "application/pdf", # PDF documents "image/jpeg", # JPEG images "image/png", # PNG images "application/json", # JSON files "text/csv" # CSV files ] ) ``` The `default_output_modes` parameter controls which file agent can produce: ```python theme={null} @server.agent( default_output_modes=[ "text/plain", # Plain text files "application/pdf", # PDF documents "image/jpeg", # JPEG images "image/png", # PNG images "application/json", # JSON files "text/csv" # CSV files ] ) ``` Common MIME types you might want to support: | Category | MIME Types | | -------------- | -------------------------------------------------------------------------------------------------------------------------- | | **Text files** | `text/plain`
`text/markdown`
`text/csv` | | **Documents** | `application/pdf`
`application/msword`
`application/vnd.openxmlformats-officedocument.wordprocessingml.document` | | **Images** | `image/jpeg`
`image/png`
`image/gif`
`image/svg+xml` | | **Data** | `application/json`
`application/xml`
`text/xml` | ## Handling Text vs Binary Files When processing files, it's important to handle text and binary files differently. The `load_file` helper provides both `.text` and `.content` properties: **Key differences:** * **Text files** (text/plain, text/markdown, application/json, etc.): Use `loaded_content.text.encode()` to get bytes. * **Binary files** (application/pdf, image/\*, etc.): Use `loaded_content.content` directly to preserve binary content. # Collect Input with Forms Source: https://agentstack.beeai.dev/stable/agent-integration/forms Collect structured input from users One of the most powerful features of the Agent Stack is the ability to request structured data from users through interactive forms. Instead of relying on free-form text input, your agent can present users with specific fields, dropdowns, and other form elements to gather precise information. The Agent Stack provides a Form extensions that allows you to collect structured data from users in two ways: 1. **Initial form rendering** - Present a form as the first interaction before users start a conversation with your agent 2. **Dynamic form requests** - Request forms at any point during a multi-turn conversation when your agent needs specific structured input ## Initial Form Rendering For initial form rendering, you specify the form structure when injecting the extension and then parse the response using a Pydantic model. The form is presented to users before they start a conversation with your agent. ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from typing import Annotated from a2a.types import Message from agentstack_sdk.a2a.extensions import ( FormRender, TextField, FormServiceExtensionServer, FormServiceExtensionSpec, ) from agentstack_sdk.server import Server from pydantic import BaseModel server = Server() class UserInfo(BaseModel): first_name: str | None last_name: str | None @server.agent() async def initial_form_rendering_example( _message: Message, form: Annotated[ FormServiceExtensionServer, FormServiceExtensionSpec.demand( initial_form=FormRender( title="Welcome! Please tell us about yourself", columns=2, fields=[ TextField(id="first_name", label="First Name", col_span=1), TextField(id="last_name", label="Last Name", col_span=1), ], ) ), ], ): """Agent that collects user information through an initial form""" # Parse the form data using a Pydantic model user_info = form.parse_initial_form(model=UserInfo) if user_info is None: yield "No form data received." else: yield f"Hello {user_info.first_name} {user_info.last_name}! Nice to meet you." def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` Import `FormServiceExtensionServer`, `FormServiceExtensionSpec`, `FormRender`, and field types from the Agent Stack SDK. Create a Pydantic model with fields matching your form field IDs. Inject the form extension into your agent function using `FormServiceExtensionSpec.demand(initial_form=FormRender(...))`. Call `form.parse_initial_form(model=YourModel)` to extract the submitted form data. ## Dynamic Form Requests For dynamic form requests during conversation, you can request forms at any point when your agent needs structured input. This is useful when your agent needs to collect additional information based on the conversation flow. ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from typing import Annotated from a2a.types import Message from a2a.utils.message import get_message_text from agentstack_sdk.a2a.extensions import ( FormRender, TextField, FormRequestExtensionServer, FormRequestExtensionSpec, ) from agentstack_sdk.server import Server from pydantic import BaseModel server = Server() class ContactInfo(BaseModel): email: str | None phone: str | None company: str | None @server.agent() async def dynamic_form_requests_example( message: Message, form_request: Annotated[ FormRequestExtensionServer, FormRequestExtensionSpec(), ], ): """Agent that requests forms dynamically during conversation""" user_input = get_message_text(message) # Check if user wants to provide contact information if "contact" in user_input.lower() or "reach" in user_input.lower(): # Request contact form dynamically contact_info = await form_request.request_form( form=FormRender( title="Please provide your contact information", columns=2, fields=[ TextField(id="email", label="Email Address", col_span=2), TextField(id="phone", label="Phone Number", col_span=1), TextField(id="company", label="Company", col_span=1), ], ), model=ContactInfo, ) if contact_info is None: yield "No contact information received." else: yield f"Thank you! I'll contact you at {contact_info.email} or {contact_info.phone} regarding {contact_info.company}." else: yield "Hello! If you'd like me to contact you, just let me know and I'll ask for your details." def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` Import `FormRequestExtensionServer`, `FormRequestExtensionSpec`, `FormRender`, and field types from the Agent Stack SDK. Create a Pydantic model with fields matching your form field IDs. Inject the request form extension into your agent function using `FormRequestExtensionSpec()`. Call `await form_request.request_form(form=FormRender(...), model=YourModel)` when you need to collect structured input. ## How to work with forms Here's what you need to know to add form capabilities to your agent: **Import the form components**: * For form fields and `FormRender`, import from `agentstack_sdk.a2a.extensions` * For initial forms, import `FormServiceExtensionServer` and `FormServiceExtensionSpec` from `agentstack_sdk.a2a.extensions` * For dynamic forms, import `FormRequestExtensionServer` and `FormRequestExtensionSpec` from `agentstack_sdk.a2a.extensions` **Inject the extension**: Add a form parameter to your agent function using the `Annotated` type hint. **For initial forms**: Use `FormServiceExtensionSpec.demand(initial_form=FormRender(...))` to specify the form structure and call `form.parse_initial_form(model=YourModel)` to extract data. **For dynamic forms**: Use `FormRequestExtensionSpec()` and call `await form_request.request_form(form=FormRender(...), model=YourModel)` when needed. **Access form data**: The recommended approach is to use a Pydantic model (or `TypedDict`, `dataclass`, or any class supported by `pydantic.TypeAdapter`) to load form data. Define a model with fields matching your form field IDs: ```python theme={null} from pydantic import BaseModel class ContactInfo(BaseModel): email: str | None phone: str | None company: str | None ``` Then, pass `model=ContactInfo` to `parse_initial_form(...)` or `request_form(...)` to get the form data directly as an instance of `ContactInfo`: ```python theme={null} # For initial forms contact_info: ContactInfo | None = form.parse_initial_form(model=ContactInfo) # For dynamic forms contact_info: ContactInfo | None = await form_request.request_form( form=FormRender(...), model=ContactInfo ) ``` If you don't use a model, the methods return `FormResponse` which has a `values` dictionary. You can access values using `form_data.values['field_id'].value`. Different field types return different value types: | Field Type | Return Type | Notes | | --------------------- | ------------------------ | -------------------------------------------------- | | **TextField** | `str \| None` | | | **DateField** | `str \| None` | | | **FileField** | `list[FileInfo] \| None` | Each `FileInfo` has `uri`, `name`, and `mime_type` | | **SingleSelectField** | `str \| None` | Selected option ID | | **MultiSelectField** | `list[str] \| None` | List of selected option IDs | | **CheckboxField** | `bool \| None` | | ## Form Field Types The Agent Stack supports various field types for collecting different kinds of structured data: ### TextField Basic text input fields for collecting strings, names, descriptions, etc. ```python theme={null} from agentstack_sdk.a2a.extensions import TextField TextField( id="username", label="Username", col_span=1, required=True, placeholder="Enter your username", default_value="", type="text" # Optional, defaults to "text" ) ``` ### DateField Date input fields for collecting dates and timestamps. ```python theme={null} from agentstack_sdk.a2a.extensions import DateField DateField( id="birth_date", label="Birth Date", col_span=1, required=True, placeholder="YYYY-MM-DD", default_value="1990-01-01" ) ``` ### FileField File upload fields for collecting files from users. ```python theme={null} from agentstack_sdk.a2a.extensions import FileField FileField( id="document", label="Upload Document", col_span=2, required=True, accept=["application/pdf", "image/jpeg", "image/png"] ) ``` ### SingleSelectField Single-select dropdown fields for choosing single option from a list. ```python theme={null} from agentstack_sdk.a2a.extensions import OptionItem, SingleSelectField SingleSelectField( id="contact_method", label="Preferred Contact Method", col_span=2, required=False, options=[ OptionItem(id="email", label="Email"), OptionItem(id="phone", label="Phone"), OptionItem(id="sms", label="SMS"), OptionItem(id="none", label="Do not contact") ], default_value="email" ) ``` ### MultiSelectField Multi-select dropdown fields for choosing multiple options from a list. ```python theme={null} from agentstack_sdk.a2a.extensions import OptionItem, MultiSelectField MultiSelectField( id="interests", label="Your Interests", col_span=2, required=False, options=[ OptionItem(id="tech", label="Technology"), OptionItem(id="sports", label="Sports"), OptionItem(id="music", label="Music"), OptionItem(id="travel", label="Travel") ], default_value=["tech", "music"] ) ``` ### CheckboxField Single checkbox fields for boolean values. ```python theme={null} from agentstack_sdk.a2a.extensions import CheckboxField CheckboxField( id="newsletter", label="Subscribe to Newsletter", col_span=1, required=False, content="I agree to receive marketing emails", default_value=False ) ``` ## Form Layout Configuration Control how your form appears using the `FormRender` configuration: ```python theme={null} from agentstack_sdk.a2a.extensions import FormRender FormRender( title="Form Title", description="Optional description text below the title", columns=2, # Number of columns in the form grid submit_label="Custom Submit Button Text", fields=[ # Your field definitions here ] ) ``` **FormRender properties**: | Property | Description | | ------------------ | --------------------------------------------------------------- | | **`title`** | Main heading displayed above the form (optional) | | **`description`** | Optional description text displayed below the title | | **`columns`** | Number of columns in the form grid (1-4, optional) | | **`submit_label`** | Custom text for the submit button (optional, default: "Submit") | | **`fields`** | List of form field definitions (required) | Use `col_span` on individual fields to control how they span across the grid. For example, with `columns=2`, a field with `col_span=2` will take the full width, while `col_span=1` will take half the width. ## Examples For more examples, see: * [form\_agent.py](https://github.com/i-am-bee/agentstack/blob/main/apps/agentstack-sdk-py/examples/form_agent.py) * [form\_request\_agent.py](https://github.com/i-am-bee/agentstack/blob/main/apps/agentstack-sdk-py/examples/form_request_agent.py) # LLM Proxy Service Source: https://agentstack.beeai.dev/stable/agent-integration/llm-proxy-service Leverage Agent Stack's model and provider agnostic LLM inference When building AI agents, one of the first requirements is connecting to a Large Language Model (LLM). Agent Stack provides built-in, OpenAI-compatible LLM inference that is model and provider agnostic. In order to effectively implement the LLM Proxy Service there are 3 steps to follow: Import the necessary components and add the LLM service extension to your agent function. Specify which model your agent prefers and how you want to access it. Access the optionally provided LLM configuration and use it with your preferred LLM client. Service Extensions are a type of [A2A Extension](https://a2a-protocol.org/latest/topics/extensions/) that allows you to easily "inject dependencies" into your agent. This follows the inversion of control principle where your agent defines what it needs, and the platform (in this case, Agent Stack) is responsible for providing those dependencies. Service extensions are optional by definition, so you should always check if they exist before using them. ## Implementing Steps ### 1. Add the LLM service extension to your agent Import the `LLMServiceExtensionServer` and `LLMServiceExtensionSpec` from the SDK. You will use these within a type hint to let the platform know your agent requires LLM access. ```python theme={null} from agentstack_sdk.a2a.extensions import LLMServiceExtensionServer, LLMServiceExtensionSpec from a2a.types import Message from typing import Annotated # The extension is added as an Annotated parameter in your agent function async def my_agent( input: Message, llm: Annotated[ LLMServiceExtensionServer, ...] ): # agent logic pass ``` ### 2. Configure your LLM request Use `LLMServiceExtensionSpec.single_demand()` to request a model. By passing a suggested tuple, you tell the platform which model you'd prefer to use. ```python theme={null} from agentstack_sdk.a2a.extensions import LLMServiceExtensionServer, LLMServiceExtensionSpec from a2a.types import Message from typing import Annotated # The llm parameter is configured with a specific model demand async def my_agent( input: Message, llm: Annotated[ LLMServiceExtensionServer, LLMServiceExtensionSpec.single_demand(suggested=("ibm/granite-3-3-8b-instruct",)) ] ): # agent logic pass ``` When you specify a suggested model like `"ibm/granite-3-3-8b-instruct"`the platform: 1. Checks if the requested model is available in your configured environment 2. Allocates the best available model that matches your requirements 3. Provides you with the exact model identifier and endpoint details The platform handles the complexity of model provisioning and endpoint management, so you can focus on building your agent logic. ### 3. Use the LLM in your agent Once the platform provides the extension, you can extract the OpenAI-compatible configuration. ```python theme={null} from typing import Annotated from a2a.utils.message import get_message_text from a2a.types import Message from agentstack_sdk.a2a.extensions import LLMServiceExtensionServer, LLMServiceExtensionSpec async def my_agent( input: Message, llm: Annotated[ LLMServiceExtensionServer, LLMServiceExtensionSpec.single_demand(suggested=("ibm/granite-3-3-8b-instruct",)) ] ) -> None: # Verify that the optional extension was provided if llm and llm.data and llm.data.llm_fulfillments: user_message = get_message_text(input) # Access the resolved LLM configuration llm_config = llm.data.llm_fulfillments.get("default") if llm_config: # These credentials work with any OpenAI-compatible client library api_model = llm_config.api_model api_key = llm_config.api_key api_base = llm_config.api_base ``` The platform automatically provides you with: * **`api_model`**: The specific model identifier that was allocated to your request * **`api_key`**: Authentication key for the LLM service * **`api_base`**: The base URL for the OpenAI-compatible API endpoint These credentials work with any OpenAI-compatible client library, making it easy to integrate with popular frameworks like: * BeeAI Framework * LangChain * LlamaIndex * OpenAI Python client * Custom implementations This complete example shows how to receive a user message and respond using the credentials provided by the LLM Proxy Service: ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from typing import Annotated from a2a.types import Message from a2a.utils.message import get_message_text from agentstack_sdk.a2a.extensions import LLMServiceExtensionServer, LLMServiceExtensionSpec from agentstack_sdk.a2a.types import AgentMessage from agentstack_sdk.server import Server server = Server() @server.agent() async def llm_access_example( input: Message, llm: Annotated[ LLMServiceExtensionServer, LLMServiceExtensionSpec.single_demand(suggested=("ibm/granite-3-3-8b-instruct",)) ], ): """Agent that uses LLM inference to respond to user input""" if llm and llm.data and llm.data.llm_fulfillments: # Extract the user's message user_message = get_message_text(input) # Get LLM configuration # Single demand is resolved to default (unless specified otherwise) llm_config = llm.data.llm_fulfillments.get("default") if llm_config: # Use the LLM configuration with your preferred client # The platform provides OpenAI-compatible endpoints api_model = llm_config.api_model api_key = llm_config.api_key api_base = llm_config.api_base yield AgentMessage(text=f"LLM access configured for model: {api_model}") else: yield AgentMessage(text="LLM configuration not found.") else: yield AgentMessage(text="LLM service not available.") def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` # MCP integration Source: https://agentstack.beeai.dev/stable/agent-integration/mcp Learn how Agent Stack helps with authentication when using MCP. ## What are Connectors? A connector is an MCP endpoint exposed by the Agent Stack that forwards MCP requests to a remote MCP server, acting as a proxy and handling authentication. It allows users to set up a connection to a third-party service (like GitHub, Box etc.) and then exposes a local MCP server that can be consumed by agents. This architecture eliminates the need for user authentication in your agent code. The connector handles authentication with the third-party service on behalf of the authenticated user, and the MCP interface is properly scoped to that user's permissions and data. Users can work with connectors in two ways via the Agent Stack UI: * **Connect an existing connector**: Go through an OAuth flow to obtain an authentication token for a pre-registered connector. * **Register a custom connector**: Provide OAuth client ID, client secret, and URL to register their own service as a connector, then connect it. When a connector is registered and connected, it becomes available for the agents to use through the MCP extension. Each connector has a name (like "github", "slack", or "notion") that agents can reference when requesting MCP access. The platform automatically routes agent's MCP requests to the appropriate connector, ensuring secure, user-scoped access to third-party services. ## MCP Service Extension The MCP Service Extension provides a standardized way for your agents to access MCP servers (connectors) without managing connection details, authentication, or URLs manually. The extension automatically matches your agent's MCP requirements with available connectors and provides you with a ready-to-use MCP client session, eliminating the need for manual configuration. One of the key features of Agent Stack's MCP integration is automatic connector matching. When you specify a suggested connector name in your agent, the platform automatically: 1. Searches for connected connectors with matching names (case-insensitive) 2. Creates an MCP client connection to the matched connector 3. Provides you with a ready-to-use MCP client session This means you don't need to manually configure URLs or manage connector connections. The platform handles it all for you. Service Extensions are a type of [A2A Extension](https://a2a-protocol.org/latest/topics/extensions/) that allows you to easily "inject dependencies" into your agent. This follows the inversion of control principle where your agent defines what it needs, and the Client (in this case, Agent Stack UI) is responsible for providing those dependencies. Service extensions are optional by definition, so you should always check if they exist before using them. ## Example: GitHub MCP Agent This GitHub MCP Agent uses the connector-based approach. It requires pre-configuration of a GitHub connector. For information on pre-configuration, see [Experimental/MCP Connectors](../experimental/connectors). Here's an example of how to setup the GitHub connector using the Agent Stack SDK (use your own personal access token where noted in the example code): ```python theme={null} import asyncio from agentstack_sdk.platform import Connector, ConnectorState async def main(): connector = await Connector.create(url="https://api.githubcopilot.com/mcp") connector = await connector.connect(access_token="") connector = await connector.wait_for_state(state=ConnectorState.connected) if __name__ == "__main__": asyncio.run(main()) ``` Here's an example agent that uses the GitHub MCP Connector and returns your profile details: ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import json import os from collections.abc import AsyncGenerator from typing import Annotated from a2a.types import Message from agentstack_sdk.a2a.extensions import MCPServiceExtensionServer, MCPServiceExtensionSpec from agentstack_sdk.a2a.types import RunYield from agentstack_sdk.server import Server from mcp import ClientSession server = Server() @server.agent() async def github_mcp_agent_example( mcp_service: Annotated[ MCPServiceExtensionServer, MCPServiceExtensionSpec.single_demand(suggested=("github",)), ], ) -> AsyncGenerator[RunYield, Message]: """Show connected GitHub profile information""" if not mcp_service: yield "MCP extension hasn't been activated, no tools are available" return async with mcp_service.create_client() as client: if client is None: yield "MCP client not available." return read, write = client async with ClientSession(read_stream=read, write_stream=write) as session: _ = await session.initialize() me_result = await session.call_tool("get_me", {}) result_dict = me_result.model_dump() if hasattr(me_result, "model_dump") else me_result yield json.dumps(result_dict, indent=2, default=str) def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` Import the necessary components and add the MCP service extension to your agent function. Use the `suggested` parameter to indicate which connector you want to use (e.g., `"github"`). Use `mcp_service.create_client()` to get a connected MCP client. Use an MCP client session to call tools, list resources, or interact with prompts. ## Custom MCP Client with OAuth If you prefer not to use the MCP Service Extension (which automatically matches connectors), you can still create a standard MCP client and use the OAuth extension for user authentication. Unlike the connector-based approach, this method does not require users to set up a connection ahead of the conversation. The OAuth extension handles the authentication flow through the Agent Stack UI, allowing users to authorize your agent to access third-party services during the conversation itself. This makes it ideal for scenarios where you want users to authenticate on-demand without pre-configuration. ### Example: Direct MCP Client with OAuth Here's how to build an agent that creates a custom MCP client and uses OAuth for authentication: ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from typing import Annotated import pydantic from agentstack_sdk.a2a.extensions import OAuthExtensionServer, OAuthExtensionSpec from agentstack_sdk.server import Server from mcp import ClientSession from mcp.client.streamable_http import streamablehttp_client server = Server() @server.agent() async def custom_mcp_client_with_oauth_example( oauth: Annotated[OAuthExtensionServer, OAuthExtensionSpec.single_demand()], ): """Agent that uses OAuth to authenticate with a custom MCP server""" mcp_url = os.getenv("MCP_URL", "https://mcp.stripe.com") if not oauth: yield "OAuth extension not available. Authentication required." return async with streamablehttp_client( url=mcp_url, auth=await oauth.create_httpx_auth(resource_url=pydantic.AnyUrl(mcp_url)) ) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() result = await session.call_tool("get_stripe_account_info") # Extract text content from CallToolResult if result.content: content = result.content[0] if hasattr(content, "text"): yield str(content.text) # type: ignore else: yield "No content returned" def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` ### Key Differences **MCP Service Extension approach:** * Automatically matches connectors by name * No need to specify MCP server URLs * Handles connection setup automatically * Requires users to set up connectors before the conversation * Best for using registered connectors **Custom MCP Client with OAuth approach:** * Manual MCP client creation with explicit URLs * Full control over connection parameters * Uses OAuth extension for in-conversation user authentication * No pre-setup required from users * Best for direct MCP server connections or on-demand authentication Always check if the OAuth extension is available before using it to comply with plain A2A clients that may not support OAuth authentication. # Working with Messages Source: https://agentstack.beeai.dev/stable/agent-integration/messages Learn how to structure, send, and receive messages - the foundation of agent communication. In Agent Stack, you communicate with the client by yielding data. This concept consists of two parts: 1. **Yielding data**: Sending data from your agent implementation to the client. 2. **The Wrapper**: Using convenience objects like `AgentMessage` to simplify the underlying [A2A Protocol](https://github.com/a2aproject/A2A) requirements. ## Choosing Your Message Type | Type | Best For... | Key Benefit | | :------------------------------ | :------------------------------------ | :--------------------------------------------------------- | | **Simple Data** (`str`, `dict`) | Simple text or JSON | Automatic conversion | | **AgentMessage** | Standard conversational responses | Auto-generates IDs and handles `TextPart` wrapping | | **AgentArtifact** | Returning files, documents, or assets | Automatically wraps content into A2A `Part` objects | | **A2A Components** | Advanced protocol-level control | Full control over `Role`, `UUID`, and `Part` lists | | **TaskStatus** | Tracking stateful progress | Allows you to signal specific states like `input_required` | ## Simplified Yielding The Agent Stack SDK allows you to yield simple Python types. These are automatically converted into textual A2A `Message` objects for ease of use. #### Plain Strings ```python theme={null} yield "Hello, world!" ``` #### Plain Dictionaries ```python theme={null} yield {"status": "processing", "progress": 50} ``` ## Convenience Wrappers Wrappers provide a balance between ease of use and metadata support. #### AgentMessage The most common way to respond with text. It simplifies creating A2A `Message` objects and supports metadata. ```python theme={null} yield AgentMessage(text="This is my text", metadata={"foo": "bar"}) ``` #### AgentArtifact Similar to `AgentMessage`, but specifically designed to simplify the creation and handling of Artifacts (tangible outputs). ```python theme={null} yield AgentArtifact( parts=[ TextPart(text="Here is the documentation you requested."), FilePart(file=FileWithUri( uri="https://example.com/report.pdf", mime_type="application/pdf", name="Report.pdf" )) ] ) ``` ## Direct A2A Components For advanced use cases, you can yield direct [A2A protocol components](https://a2a-protocol.org/latest/topics/key-concepts/#core-actors-in-a2a-interactions). While yielding plain A2A components is supported, the Agent Stack is optimized for convenience wrappers. Using wrappers ensures the best UX in the GUI. #### Message The basic unit representing a single turn in a conversation. ```python theme={null} import uuid from a2a.types import Message, TextPart, Part, Role # @server.agent(...) async def example_agent(): yield Message(role=Role.agent, message_id=str(uuid.uuid4()), parts=[Part(root=TextPart(text="Hello from the agent!"))]) ``` #### Part The fundamental unit of content. A `Message` consists of multiple `Part` objects such as `TextPart`, `FilePart`, or `DataPart`. ```python theme={null} from a2a.types import TextPart # @server.agent(...) async def example_agent(): yield TextPart(text="Hello from the agent!") ``` #### Artifact Tangible outputs produced by the agent, such as documents or other generated content. ```python theme={null} import uuid from a2a.types import Artifact, FilePart, FileWithUri, Part # @server.agent(...) async def example_agent(): yield Artifact( artifact_id=str(uuid.uuid4()), parts=[Part(root=FilePart(file=FileWithUri(uri="https://www.ibm.com/us-en", mime_type="text/html", name="IBM Website")))], ) ``` #### TaskStatus A stateful unit of work used to annotate a transaction spanning multiple messages (e.g., tracking progress or requiring input). ```python theme={null} import uuid from a2a.types import TaskStatus, TextPart, Message, TaskState, Part, Role # @server.agent(...) async def example_agent(): yield TaskStatus( message=Message(message_id=str(uuid.uuid4()), role=Role.agent, parts=[Part(root=TextPart(text="Please provide some input."))]), state=TaskState.input_required, ) ``` # Multi-Turn Conversations Source: https://agentstack.beeai.dev/stable/agent-integration/multi-turn Manage conversation state and history to build context-aware agents in Agent Stack When building conversational AI agents, one of the key requirements is maintaining context across multiple interactions. While agent functions are designed to be stateless, Agent Stack provides mechanisms to access and manage conversation history and interactions with agents. The context memory system provided by Agent Stack enables the conversation continuity that users expect from AI assistants. ## History Management Control | Operation | Purpose | | :---------------------------------------- | :---------------------------------------------------------------------------------------------------------------------- | | **await context.store(input)** | Stores current user message in conversation history. Storage of messages must be explicitly requested | | **await context.store(response)** | Stores agent’s responses in conversation history, and must be explicitly requested | | **context: RunContext)** | Sets up a RunContext instance for storing and accessing the conversation history | | **context\_store=PlatformContextStore()** | Configures server to use the platform’s persistent context store to maintain conversation history across agent restarts | ## Simple History Access Example Here's an example agent that maintains conversation history and counts the number of interactions: ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from a2a.types import Message from a2a.utils.message import get_message_text from agentstack_sdk.a2a.types import AgentMessage from agentstack_sdk.server import Server from agentstack_sdk.server.context import RunContext server = Server() @server.agent() # Set the context as an instance of the conversation history to store and load previous messages async def basic_history_example(input: Message, context: RunContext): """Agent that demonstrates conversation history access""" # Store the current message in the context store await context.store(input) # Get the current user message current_message = get_message_text(input) print(f"Current message: {current_message}") # Load all messages from conversation history (including current message) history = [message async for message in context.load_history() if isinstance(message, Message) and message.parts] # Filter and process the conversation history print(f"Found {len(history)} messages in conversation (including current)") # Your agent logic here - you can now reference all messages in the conversation message = AgentMessage(text=f"Hello! I can see we have {len(history)} messages in our conversation.") yield message # Store the message in the context store await context.store(message) def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` ### Steps 1. **Access conversation history:** Use `RunContext` to set up an instance of the conversation history to store and load previous messages. 2. **Store incoming messages:** Use `await context.store(input)` to store the current user message in the conversation history. 3. **Filter and process history:** Retrieve the conversation history with `load_history()` and filter to get the messages relevant to your agent's logic. 4. **Store agent responses:** Use `await context.store(response)` to store your agent's responses for future conversation context. ## Streaming with Buffered History Example Use this pattern when you want to stream partial outputs to users while keeping one clean assistant message in conversation history. This is usecase-specific and one my opt for a combination of this and previous approach. ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import asyncio import os from a2a.types import Message from a2a.utils.message import get_message_text from agentstack_sdk.a2a.types import AgentMessage from agentstack_sdk.server import Server from agentstack_sdk.server.context import RunContext from agentstack_sdk.server.store.platform_context_store import PlatformContextStore server = Server() async def example_tool() -> str: await asyncio.sleep(.1) # doing some agent work return "tool result" async def history_counter(history: list[Message]) -> str: """Create a concise conversation-state summary.""" await asyncio.sleep(.1) # doing some agent work user_count = sum(1 for item in history if item.role.value == "user") agent_count = sum(1 for item in history if item.role.value == "agent") history_count = len(history) return f"total={history_count}, user={user_count}, agent={agent_count}" @server.agent() async def streaming_agent_w_single_history_write_example(input: Message, context: RunContext): """ Stream partial answers, execute tools, and persist one finalized assistant message. See other examples for actual implementation of multi-turn conversation agent with tool use. """ # Store the user input as the first persisted item for this turn. await context.store(data=input) history = [message async for message in context.load_history() if isinstance(message, Message) and message.parts] current_message = get_message_text(input) # Stream user-facing partial output as each step completes. # This simulates an agent that produces intermediate outputs throughout its turn which are immediately useful to the user and so sent to them buffered_parts: list[str] = [] try: part_1 = f"Received input: '{current_message}'" buffered_parts.append(part_1) yield AgentMessage(text=part_1) tool_result = await example_tool() part_2 = f"Tool call completed with result: '{tool_result}'" buffered_parts.append(part_2) yield AgentMessage(text=part_2) if len(history) > 3: raise ValueError("History is too long!") history_summary = await history_counter(history) history_part = f"History message counts including last user message, not including any of the current agent output: {history_summary}" buffered_parts.append(history_part) yield AgentMessage(text=history_part) except Exception as e: error_part = f"Error during execution: {e!s}" buffered_parts.append(error_part) yield AgentMessage(text=error_part) finally: # IMPORTANT: Persisting only once after streaming finishes. # # The finally block ensures the aggregated response is always at least partially persisted up until the point of failure. # This does not need to be the go-to approach in all cases, sometimes the partial outputs are of no value and one does not want them to be properly stored. # # Why not store each chunk? # - Calling `context.store()`, PlatformContextStore saves every message as a distinct history item. # - Storing per chunk would fragment one assistant turn into many partial messages. # - A single aggregated write keeps replay, memory, and history semantics clean. # aggregated_response = AgentMessage(text="\n".join(buffered_parts)) yield "Final result check:\n" + str(aggregated_response.text) await context.store(data=aggregated_response) def run(): server.run( host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", "8000")), context_store=PlatformContextStore(), ) if __name__ == "__main__": run() ``` ### When to use buffering * Use **simple yield + store** when your agent emits a single final response. * Use **stream + buffer + single store** when your agent emits multiple partial chunks which are streamed to the user. * With `PlatformContextStore`, each `context.store()` call creates a persisted history item, so buffering prevents chunk-level history fragmentation. ## Advanced BeeAI Framework Example Here's a sophisticated example using the BeeAI Framework to build a multi-turn chat agent that leverages conversation history and LLM capabilities: ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from typing import Annotated from a2a.types import Message, Role from a2a.utils.message import get_message_text from agentstack_sdk.a2a.extensions import LLMServiceExtensionServer, LLMServiceExtensionSpec from agentstack_sdk.a2a.types import AgentMessage from agentstack_sdk.server import Server from agentstack_sdk.server.context import RunContext from agentstack_sdk.server.store.platform_context_store import PlatformContextStore from beeai_framework.adapters.agentstack.backend.chat import AgentStackChatModel from beeai_framework.agents.requirement import RequirementAgent from beeai_framework.agents.requirement.requirements.conditional import ConditionalRequirement from beeai_framework.backend import AssistantMessage, UserMessage from beeai_framework.tools.think import ThinkTool server = Server() FrameworkMessage = UserMessage | AssistantMessage def to_framework_message(message: Message) -> FrameworkMessage: """Convert A2A Message to BeeAI Framework Message format""" message_text = "".join(part.root.text for part in message.parts if part.root.kind == "text") if message.role == Role.agent: return AssistantMessage(message_text) elif message.role == Role.user: return UserMessage(message_text) else: raise ValueError(f"Invalid message role: {message.role}") @server.agent() async def advanced_history_example( input: Message, context: RunContext, llm: Annotated[LLMServiceExtensionServer, LLMServiceExtensionSpec.single_demand()], ): """Multi-turn chat agent with conversation memory and LLM integration""" await context.store(input) # Load conversation history history = [message async for message in context.load_history() if isinstance(message, Message) and message.parts] # Initialize BeeAI Framework LLM client llm_client = AgentStackChatModel(tool_choice_support={"none", "auto"}) llm_client.set_context(llm) # Create a RequirementAgent with conversation memory agent = RequirementAgent( name="Agent", llm=llm_client, role="helpful assistant", instructions="You are a helpful assistant that is supposed to remember users name. Ask them for their name and remember it.", tools=[ThinkTool()], requirements=[ConditionalRequirement(ThinkTool, force_at_step=1)], save_intermediate_steps=False, middlewares=[], ) # Load conversation history into agent memory await agent.memory.add_many(to_framework_message(item) for item in history) # Process the current message and generate response async for event, meta in agent.run(get_message_text(input)): if meta.name == "success" and event.state.steps: step = event.state.steps[-1] if not step.tool: continue tool_name = step.tool.name if tool_name == "final_answer": response = AgentMessage(text=step.input["response"]) yield response await context.store(response) def run(): server.run( host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", "8000")), context_store=PlatformContextStore(), # Enable persistent storage ) if __name__ == "__main__": run() ``` This advanced example demonstrates several key concepts: * **LLM Integration:** Uses the platform's LLM service extension to get model access * **Framework Integration:** Leverages the BeeAI Framework for sophisticated agent capabilities * **Memory Management:** Converts conversation history to framework format and loads it into agent memory * **Tool Usage:** Includes thinking tools and conditional requirements for better reasoning * **Persistent Storage:** Uses `PlatformContextStore` for conversation persistence ## Using Content History ### Persistent Storage Example By default, conversation history is stored in memory and is lost when the agent process restarts. For production applications, you'll want to use persistent context storage to maintain conversation history across agent restarts. The `PlatformContextStore` automatically handles conversation persistence, ensuring that users can continue their conversations even after agent restarts or deployments. ```python theme={null} import os from agentstack_sdk.server import Server from agentstack_sdk.server.store.platform_context_store import PlatformContextStore server = Server() def run(): server.run( host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000)), context_store=PlatformContextStore() ) ``` ### History Contents The `context.load_history()` method returns an async iterator containing all items in the conversation, including the current message. This can include: * **A2A Messages**: Both user and assistant messages from the conversation, including the current A2A message * **Artifacts**: Any files, documents, or other artifacts shared during the conversation For multi-turn conversations, you'll primarily work with A2A messages, which include: * **User messages**: Messages sent by the user * **Assistant messages**: Previous responses from your agent The history includes the current message, so if you want only previous messages, you may need to filter out the last message or use the current message separately. The history iterator returns all message types. Always filter messages using `isinstance(message, Message)` to ensure you're working with the correct message format. ### Editing and Removing Messages from History Sometimes you may need to edit a previous message in a conversation or remove messages that are no longer relevant. The Agent Stack provides a mechanism to delete history items from a specific point onward, allowing you to effectively “rewind” the conversation and replace a message with an edited version. Possible use cases include editing a previous message, clearing irrelevant exchanges, or removing messages that resulted from processing errors. Here's an example of a function for editing a user message in a conversation using the context API. This assumes you know the context message id, which can be obtained as an id field of an object returned by `RunContext.load_history(load_history_items=True)`, `Context.list_history` or `Context.list_all_history`. ```python theme={null} import uuid from uuid import UUID from typing import Any from a2a.types import Message, Part, Role, TextPart from agentstack_sdk.platform.context import Context from agentstack_sdk.server.context import RunContext async def edit_message_in_context(run_context: RunContext, id: UUID, new_text: str, metadata: dict[str, Any] | None = None): # Step 1: Delete from this message onwards await run_context.delete_history_from_id(from_id=id) # Step 2: Create the corrected message corrected_message = Message( message_id=str(uuid.uuid4()), parts=[Part(TextPart(text=new_text))], role=Role.user, kind="message", metadata=metadata, ) # Step 3: Store the corrected message await run_context.store(data=corrected_message) ``` When you delete history from a specific message onwards, all messages created after that point (including the message itself) are removed. This effectively creates a new conversation branch starting from the message before the deleted one. This operation is permanent. Once messages are deleted, they cannot be recovered. Consider informing users about this operation or implementing a confirmation step for important conversations. ### Message Storage Guidelines Since messages are not automatically stored, you need to explicitly call `context.store()` for any message you want to be available in future interactions. Here are the key guidelines: #### Store Request Example ```python theme={null} @server.agent() async def my_agent(input: Message, context: RunContext): # Store the incoming user message immediately await context.store(input) # Process the message and generate response response = AgentMessage(text="Your response here") yield response # Store the agent's response after yielding await context.store(response) ``` #### What to Store Store all of the messages you may want to use later. If you don't store messages, they won't be available in `context.load_history()` for future interactions, causing your agent to lose conversation context. Stored material can include: * **User messages**: Always store incoming user messages to maintain conversation context * **Agent responses**: Store your agent's responses so they're available for future reference * **Important artifacts**: Store any files, documents, or other artifacts that should persist #### Storage Best Practices * **Store early**: Store user messages at the beginning of your agent function * **Store after yielding**: Store agent responses after yielding them to the user * **Be selective**: Only store messages that are relevant for future conversation context * **Handle errors**: Consider what happens if storage fails - your agent should still function # Monitoring & Observability Source: https://agentstack.beeai.dev/stable/agent-integration/observability Monitor traces, logs, and metrics from your agents and platform Agent Stack provides built-in observability through [OpenTelemetry (OTLP)](https://opentelemetry.io/). This allows you to monitor agent health, performance metrics, and execution traces in real-time. By default, the stack integrates with [Arize Phoenix](https://docs.arize.com/phoenix) for local visualization, but it can be routed to any OTLP-compliant backend. ```mermaid theme={null} graph LR %% Sources AgentStack[Agent Stack Server] Agent[Agent Providers] %% Collector OTLP[OpenTelemetry
Collector
:4318] %% Destinations Phoenix[Phoenix
:6006
✓ Built-in] Langfuse[Langfuse
Cloud
⚙ Config Required] Custom[Custom
Backend
⚙ Config Required] %% Data flows AgentStack -->|traces/metrics| OTLP Agent -->|traces/metrics| OTLP OTLP --> Phoenix OTLP --> Langfuse OTLP --> Custom %% Simple styling classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px,color:#000 classDef collector fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#000 classDef builtin fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#000 classDef optional fill:#fff3e0,stroke:#f57c00,stroke-width:2px,stroke-dasharray: 5 5,color:#000 class OTLP collector class Phoenix builtin class Langfuse,Custom optional ``` For telemetry to flow successfully, it must be enabled at three levels: You must instrument your agent logic to capture LLM calls and tool usage. The server must be configured to export that data by setting `configure_telemetry=True`. The infrastructure must be running a collector and a backend (like Arize Phoenix) to receive and display the data. ## Agent SDK Configuration Before configuring an observability platform, your agent code must be "telemetry-aware." This configuration applies to all implementations, whether you are using local Phoenix or a cloud provider like Langfuse. You must initialize instrumentation at the agent logic level and enable the export flag in the Agent Stack SDK: ```python theme={null} from agentstack_sdk.server import Server # 1. Agent Level: Import your specific instrumentor # from openinference.instrumentation.beeai import BeeAIInstrumentor # Initialize the instrumentor to capture framework events # BeeAIInstrumentor().instrument() server = Server() @server.agent() async def my_agent(): ... # 2. SDK Level: Enable the bridge to the platform collector server.run(configure_telemetry=True) ``` For an enhanced user experience and richer trace detail, consider instrumenting agents using the [OpenInference](https://github.com/Arize-ai/openinference/) standard for custom instrumentation. ## Simple Monitoring: Agent Logs The quickest way to see what your agent is doing is by streaming its logs directly to your terminal. This is ideal for debugging container lifecycle events and immediate request errors. ```bash theme={null} agentstack logs ``` What you'll see: * Agent startup and initialization * Request processing steps * Error messages and stack traces * Container lifecycle events Logs are only available for managed (containerized) agents that are currently running on Agent Stack. ## Advanced Observability: Traces & Metrics By default, Agent Stack integrates with [Arize Phoenix](https://phoenix.arize.com/) for local visualization of agent traces. For cloud-based observability and production monitoring, you can easily integrate [Langfuse](https://langfuse.com/). Telemetry details include: * Platform version and runtime details * Agent execution traces ### Enable Phoenix Observability **License Notice**: Phoenix is [licensed under the Elastic License v2 (ELv2)](https://github.com/Arize-ai/phoenix/blob/main/LICENSE). It is disabled by default in Agent Stack. By enabling it, you acknowledge responsibility for ensuring compliance with these terms in your specific use case. Install and start Phoenix using the `agentstack platform start` command: ```sh theme={null} agentstack platform start --set phoenix.enabled=true ``` You can run this even if your platform is already running; it will update the configuration without losing existing data. Spinning up the Phoenix container can take a moment, even after the CLI reports success. Go to [http://localhost:6006](http://localhost:6006) and check if it's running. If not, please wait a few moments. Execute an agent to generate data: ```sh theme={null} agentstack run chat "Hello" ``` Open [http://localhost:6006](http://localhost:6006) in your browser and navigate to the **default** project to explore the collected traces. ### Enable Langfuse Observability To route traces to Langfuse, provide a custom OTLP configuration: 1. Sign up at [cloud.langfuse.com](https://cloud.langfuse.com) 2. Create a project and generate API keys 3. Encode your keys: `echo -n "public_key:secret_key" | base64` ```yaml theme={null} collector: exporters: otlphttp/langfuse: endpoint: "https://cloud.langfuse.com/api/public/otel" # EU data region headers: Authorization: "Basic " pipelines: traces: receivers: [ otlp ] processors: [ memory_limiter, filter/phoenix, batch ] exporters: [ otlphttp/langfuse ] ``` ```bash theme={null} agentstack platform start -f config.yaml ``` Execute an agent to generate data: ```sh theme={null} agentstack run chat "Hello" ``` Check your Langfuse project dashboard for incoming traces and metrics. ## Additional Resources * **OpenTelemetry Docs**: [https://opentelemetry.io/docs/](https://opentelemetry.io/docs/) * **Langfuse Docs**: [https://langfuse.com/docs](https://langfuse.com/docs) * **Phoenix Docs**: [https://docs.arize.com/phoenix](https://docs.arize.com/phoenix) * **Prometheus Docs**: [https://prometheus.io/docs/](https://prometheus.io/docs/) * **Grafana Docs**: [https://grafana.com/docs/](https://grafana.com/docs/) # Server SDK (Python) Source: https://agentstack.beeai.dev/stable/agent-integration/overview Enhance your existing AI agents with platform capabilities The Server SDK is a Python library that enhances your existing AI agents with platform capabilities. Whether you've built your agent with LangGraph, CrewAI, or custom logic, the SDK connects it to the Agent Stack platform, giving you instant access to runtime-configurable services, interactive UI components, and deployment infrastructure. Built on top of the [Agent2Agent Protocol (A2A)](https://a2a-protocol.org/), the SDK wraps your agent implementation and adds powerful functionality through [A2A extensions](https://a2a-protocol.org/latest/topics/extensions/). This enables your agent to leverage platform services like [LLM providers](./llm-proxy-service), [file storage](./files), [vector databases](./rag), and rich UI components. That's all without rewriting your core agent logic. ## What the SDK Provides | Feature | Purpose | | :---------------------------------- | :------------------------------------------------------------------------------------- | | **Server wrapper** | Simplified server creation and agent registration | | **Convenience wrappers** | Simplified message types like `AgentMessage` that reduce boilerplate | | **Context management** | Built-in conversation history and state management | | **Async generator pattern** | Natural task-based execution with pause/resume capabilities | | **Dependency Injection Extensions** | Dependency injection for LLM service, RAG embeddings, file storage and MCP integration | | **UI Extensions** | Enables advanced interactive components including forms, citations and trajectory | ## Server Wrapper Example Here we show server creation and agent registration: ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from a2a.types import Message from agentstack_sdk.a2a.types import AgentMessage from agentstack_sdk.server import Server from agentstack_sdk.server.context import RunContext server = Server() @server.agent() async def server_wrapper_example(input: Message, context: RunContext): """Your agent implementation""" yield AgentMessage(text="Hello from my agent!") def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` The SDK uses a server-based architecture where you: 1. Create a `Server` instance and 2. Register your agent function ## Advanced Server SDK Example Here's an example that incorporates multiple Server SDK capabilities: ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from typing import Annotated from a2a.types import Message from agentstack_sdk.a2a.extensions import ( FormRender, TextField, FormRequestExtensionServer, FormRequestExtensionSpec, ) from agentstack_sdk.a2a.types import AgentMessage from agentstack_sdk.server import Server from agentstack_sdk.server.context import RunContext from pydantic import BaseModel server = Server() class UserDetails(BaseModel): name: str | None email: str | None @server.agent() async def advanced_server_wrapper_example( input: Message, context: RunContext, form_request: Annotated[FormRequestExtensionServer, FormRequestExtensionSpec()] ): """Agent that pauses execution to request user input""" yield AgentMessage(text="I need some information from you.") # Execution pauses here - task enters input_required state # User fills out the form in the UI form_data = await form_request.request_form( form=FormRender( title="Please provide your details", fields=[ TextField(id="name", label="Your Name"), TextField(id="email", label="Email Address"), ], ), model=UserDetails, ) # Execution resumes after user submits the form if form_data: yield AgentMessage(text=f"Thank you, {form_data.name}! I'll contact you at {form_data.email}.") else: yield AgentMessage(text="Form was not filled out.") def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` 1. Use the **Asynchronous Generator Pattern** to await the form request and pause the task while the user completes the form. 2. **UI Extensions** are used to create the forms and add the metadata to messages. ### Asynchronous Generator Pattern Agent functions are asynchronous generators that yield responses. This pattern aligns with A2A's task model: * **One function execution** = **One A2A task** * **Yielding data** = **Sending messages to the client** * **Pausing execution** = **Waiting for user input** The generator pattern is particularly powerful when your agent needs to request structured input from users. When you await a form request, execution pauses the task, allowing the user to fill out the form. Once submitted, execution resumes with the form data. The generator pattern enables agents to: * Stream responses incrementally * Yield multiple messages during a single task * Handle long-running operations gracefully ### Extension System Agent Stack utilizes A2A extensions to extend the protocol with Agent Stack-specific capabilities. They enable your agent to access platform services and enhance the user interface beyond what the base A2A protocol provides. There are two types of extensions: #### UI Extensions UI extensions add extra metadata to messages, enabling the Agent Stack UI to render more advanced interactive components: * **Forms**: Collect structured user input through interactive forms * **Citations**: Display source references with clickable inline links * **Trajectory**: Visualize agent reasoning steps with execution traces These extensions enhance messages with metadata that the UI interprets to create rich, interactive experiences beyond standard text responses. #### Dependency Injection Service Extensions Service extensions use a dependency injection pattern where each run of the agent declares a demand that must be fulfilled by the client (consumer). The platform provides configured access to external services based on these demands: * **LLM Service**: Language model access with automatic provider selection * **Embedding Service**: Text embedding generation for RAG * **Platform API**: File storage, vector databases, and platform services * **MCP**: Model Context Protocol integration ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from typing import Annotated from a2a.types import Message from agentstack_sdk.a2a.extensions import LLMServiceExtensionServer, LLMServiceExtensionSpec from agentstack_sdk.a2a.types import AgentMessage from agentstack_sdk.server import Server from agentstack_sdk.server.context import RunContext server = Server() @server.agent() async def dependency_injection_example( input: Message, context: RunContext, llm: Annotated[LLMServiceExtensionServer, LLMServiceExtensionSpec.single_demand()], ): # The demand is fulfilled by the client - llm is provided if available if llm: # response = await llm.chat(messages=[...]) # ... yield AgentMessage(text="LLM service is available.") else: yield AgentMessage(text="LLM service not available") def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` # Build RAG Pipelines Source: https://agentstack.beeai.dev/stable/agent-integration/rag Use vector stores, embedding, and text-extraction services to build RAG pipelines Retrieval Augmented Generation (RAG) is one of the keystones for efficient data processing and search in the age of AI agents. The goal is to surface information from a knowledge base relevant to a specific user query and provide curated context to the LLM. This is a complex topic with many variants. We will focus on the fundamental building blocks that any RAG pipeline needs. The document processing pipeline: 1. **Text extraction:** Process complex document formats (PDF, CSV, etc.). 2. **Text splitting:** Create meaningful chunks out of long pages of text. 3. **Embedding:** Vectorize chunks (extract semantic meaning). 4. **Store:** Insert chunks to a specialized database. Retrieval: 1. **Embedding:** Vectorize the user query. 2. **Search:** Retrieve the document chunks most similar to the user query. ## Steps Use `agentstack platform start --set docling.enabled=true` to start Agent Stack with Docling. Add the `default_input_modes` parameter to your agent decorator to allow users to upload files to your agent. This also specifies which file types users can upload. Import `PlatformApiExtensionServer`, `PlatformApiExtensionSpec`, `EmbeddingServiceExtensionServer` and `EmbeddingServiceExtensionSpec` from `agentstack_sdk.a2a.extensions`. Add parameters to your agent function using the `Annotated` type hints. Implement functions to handle text extraction, text splitting, embedding generation, and vector storing. Implement functions to generate embeddings for the user query and search the vector store for similar document chunks. Put it all together in an agent that 1) processes uploaded documents and 2) answers questions about them. The examples here skip this part and return details from the document. In practice, you'll want to use an LLM to help form a response about the selected document chunks instead of returning the actual chunk text (i.e., implement an assistant and not just a search tool). Let's break down how each step can be implemented with the Agent Stack API. ## Building blocks ### Enable File Uploads Add the `default_input_modes` parameter to your agent decorator to allow users to upload files to your agent. This also specifies which file types users can upload. Agent Stack uses [docling](https://docling-project.github.io/docling/) for extracting text out of documents in various [supported formats](https://docling-project.github.io/docling/usage/supported_formats/). ```python theme={null} from agentstack_sdk.server import Server # File formats supported by the text-extraction service (docling) default_input_modes = [ "text/plain", "application/pdf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", # DOCX "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", # XLSX "application/vnd.openxmlformats-officedocument.presentationml.presentation", # PPTX "text/markdown", # Markdown "text/asciidoc", # AsciiDoc "text/html", # HTML "application/xhtml+xml", # XHTML "text/csv", # CSV "image/png", # PNG "image/jpeg", # JPEG "image/tiff", # TIFF "image/bmp", # BMP "image/webp", # WEBP ] server = Server() @server.agent( default_input_modes=default_input_modes, default_output_modes=["text/plain"] ) async def rag_agent( # Parameter details below... ): """Agent code to follow... """ ``` ### Platform and Embedding Extensions Make sure you have the Platform API and Embedding Service extensions imported and injected in your agent parameters: ```python theme={null} import json from typing import Annotated from a2a.types import Message, Part, DataPart, FilePart, TextPart, FileWithUri from agentstack_sdk.server import Server from agentstack_sdk.a2a.extensions import ( PlatformApiExtensionServer, PlatformApiExtensionSpec, EmbeddingServiceExtensionServer, EmbeddingServiceExtensionSpec, ) from agentstack_sdk.a2a.types import AgentMessage from agentstack_sdk.server.context import RunContext from agentstack_sdk.util.file import PlatformFileUrl # File formats supported by the text-extraction service (docling) default_input_modes = [ "text/plain", "application/pdf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", # DOCX "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", # XLSX "application/vnd.openxmlformats-officedocument.presentationml.presentation", # PPTX "text/markdown", # Markdown "text/asciidoc", # AsciiDoc "text/html", # HTML "application/xhtml+xml", # XHTML "text/csv", # CSV "image/png", # PNG "image/jpeg", # JPEG "image/tiff", # TIFF "image/bmp", # BMP "image/webp", # WEBP ] server = Server() @server.agent( default_input_modes=default_input_modes, default_output_modes=["text/plain"] ) async def rag_agent( input: Message, context: RunContext, embedding: Annotated[ EmbeddingServiceExtensionServer, EmbeddingServiceExtensionSpec.single_demand() ], _: Annotated[PlatformApiExtensionServer, PlatformApiExtensionSpec()], ): """Agent code to follow... """ ``` Next, let's build a set of functions to process the documents which we will then use in the agent. ### Text Extraction To extract text from a `File` uploaded to the Platform API, simply use `file.create_extraction()` and wait for the result. After extraction is completed, the `extraction` object will contain `extracted_files`, which is a list of extracted files in different formats. ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import asyncio from agentstack_sdk.platform import File async def extract_file(file: File): extraction = await file.create_extraction() while extraction.status in {"pending", "in_progress"}: await asyncio.sleep(1) extraction = await file.get_extraction() if extraction.status != "completed": raise ValueError(f"Extraction failed with status: {extraction.status}") ``` #### Extraction Formats Text extraction produces two extraction formats and you can request either subset by passing `formats` to `create_extraction` (e.g., `["markdown"]` if you only need plain text): * **markdown**: The extracted text formatted as Markdown (`file.load_text_content()`) * **vendor\_specific\_json**: The Docling-specific JSON format containing document structure (`file.load_json_content()`) > **WARNING**: > The `vendor_specific_json` format is not generated for plain text or markdown files, as Docling does not support these formats as input. ### Text Splitting In this example we will use `MarkdownTextSplitter` from the [langchain-text-splitters](https://reference.langchain.com/python/langchain_text_splitters/) package. This will split a long document into reasonably sized chunks based on the Markdown header structure. ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 from langchain_text_splitters import MarkdownTextSplitter def chunk_markdown(markdown_text: str) -> list[str]: return MarkdownTextSplitter().split_text(markdown_text) ``` ### Embedding Now we need to generate embeddings for each chunk using the embedding service. Similarly to LLM, Agent Stack implements OpenAI-compatible embedding API. You can use any preferred client, in this example we will use the embedding extension to create an `AsyncOpenAI` client: ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 from agentstack_sdk.a2a.extensions import EmbeddingServiceExtensionServer from openai import AsyncOpenAI def get_embedding_client( embedding: EmbeddingServiceExtensionServer, ) -> tuple[AsyncOpenAI, str]: if not embedding or not embedding.data: raise ValueError("Embedding extension not provided") embedding_config = embedding.data.embedding_fulfillments.get("default") if not embedding_config: raise ValueError("Default embedding configuration not found") embedding_client = AsyncOpenAI(api_key=embedding_config.api_key, base_url=embedding_config.api_base) embedding_model = embedding_config.api_model return embedding_client, embedding_model ``` Now we can use this client to generate embeddings for our chunks and create vector store items: ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 from agentstack_sdk.platform import File, VectorStoreItem from openai import AsyncOpenAI async def embed_chunks( file: File, chunks: list[str], embedding_client: AsyncOpenAI, embedding_model: str ) -> list[VectorStoreItem]: vector_store_items = [] embedding_result = await embedding_client.embeddings.create( input=chunks, model=embedding_model, encoding_format="float", ) for i, embedding_data in enumerate(embedding_result.data): item = VectorStoreItem( document_id=file.id, document_type="platform_file", model_id=embedding_model, text=chunks[i], embedding=embedding_data.embedding, metadata={"chunk_index": str(i)}, # add arbitrary string metadata ) vector_store_items.append(item) return vector_store_items ``` ### Store Finally, to insert the prepared items, we need a function to create a vector store. For this we will need to know the dimension of the embeddings and model\_id. Because the model is chosen by the embedding extension and we don't know it in advance, we will create a test embedding request to calculate the dimension: ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 from agentstack_sdk.platform import VectorStore from openai import AsyncOpenAI async def create_vector_store(embedding_client: AsyncOpenAI, embedding_model: str): embedding_response = await embedding_client.embeddings.create(input="test", model=embedding_model) dimension = len(embedding_response.data[0].embedding) return await VectorStore.create( name="rag-example", dimension=dimension, model_id=embedding_model, ) ``` We can then add the prepared items using `vector_store.add_documents`, this will become clear in the final example. ### Query vector store Assuming we have our knowledge base of documents prepared, we can now easily search the store according to the user query. The following function will retrieve five document chunks most similar to the query embedding: ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 from agentstack_sdk.platform import VectorStore, VectorStoreSearchResult from openai import AsyncOpenAI async def search_vector_store( vector_store: VectorStore, query: str, embedding_client: AsyncOpenAI, embedding_model: str, ) -> list[VectorStoreSearchResult]: embedding_response = await embedding_client.embeddings.create(input=query, model=embedding_model) query_vector = embedding_response.data[0].embedding return await vector_store.search(query_vector=query_vector, limit=5) ``` ## Putting all together Having all the pieces in place, we can now build the agent. ### Simple agent This is a simplified agent that expects a message with one or more files attached as `FilePart` and a user query as `TextPart`. A new vector store is created for each message. ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import json import os from collections.abc import AsyncGenerator from typing import Annotated from a2a.types import FilePart, FileWithUri, Message, TextPart from agentstack_sdk.a2a.extensions import ( EmbeddingServiceExtensionServer, EmbeddingServiceExtensionSpec, PlatformApiExtensionServer, PlatformApiExtensionSpec, ) from agentstack_sdk.a2a.types import RunYield from agentstack_sdk.platform import File, PlatformFileUrl from agentstack_sdk.server import Server from .embedding.client import get_embedding_client from .embedding.embed import embed_chunks from .extraction import extract_file from .text_splitting import chunk_markdown from .vector_store.create import create_vector_store from .vector_store.search import search_vector_store # File formats supported by the text-extraction service (docling) default_input_modes = [ "text/plain", "application/pdf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", # DOCX "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", # XLSX "application/vnd.openxmlformats-officedocument.presentationml.presentation", # PPTX "text/markdown", # Markdown "text/asciidoc", # AsciiDoc "text/html", # HTML "application/xhtml+xml", # XHTML "text/csv", # CSV "image/png", # PNG "image/jpeg", # JPEG "image/tiff", # TIFF "image/bmp", # BMP "image/webp", # WEBP ] server = Server() @server.agent(default_input_modes=default_input_modes, default_output_modes=["text/plain"]) async def simple_rag_agent_example( input: Message, embedding: Annotated[EmbeddingServiceExtensionServer, EmbeddingServiceExtensionSpec.single_demand()], _: Annotated[PlatformApiExtensionServer, PlatformApiExtensionSpec()], ) -> AsyncGenerator[RunYield, None]: # Create embedding client embedding_client, embedding_model = get_embedding_client(embedding) # Extract files and query from input files: list[File] = [] query = "" for part in input.parts: match part.root: case FilePart(file=FileWithUri(uri=uri)): files.append(await File.get(PlatformFileUrl(uri).file_id)) case TextPart(text=text): query = text case _: raise NotImplementedError(f"Unsupported part: {type(part.root)}") if not files or not query: raise ValueError("No files or query provided") # Create vector store vector_store = await create_vector_store(embedding_client, embedding_model) # Process files, add to vector store for file in files: await extract_file(file) async with file.load_text_content() as loaded_file: chunks = chunk_markdown(loaded_file.text) items = await embed_chunks(file, chunks, embedding_client, embedding_model) await vector_store.add_documents(items=items) # Search vector store results = await search_vector_store(vector_store, query, embedding_client, embedding_model) # TODO: You can add LLM result processing here snippet = [res.model_dump() for res in results] yield f"# Results:\n{json.dumps(snippet, indent=2)}" def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` Instead of simply returning the output of the vector store, you would typically plug this as a tool into your favorite agentic framework. ### Conversational agent Having a new vector store for each message is not really a good practice. Typically, you would want to search through all documents uploaded in the conversation. Below is a version of the agent which will reuse the vector store across messages so you can ask multiple queries and or additional documents later on. ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import json import os from typing import Annotated from a2a.types import DataPart, FilePart, FileWithUri, Message, Part, TextPart from agentstack_sdk.a2a.extensions import ( EmbeddingServiceExtensionServer, EmbeddingServiceExtensionSpec, PlatformApiExtensionServer, PlatformApiExtensionSpec, ) from agentstack_sdk.a2a.types import AgentMessage from agentstack_sdk.platform import File, PlatformFileUrl, VectorStore from agentstack_sdk.server import Server from agentstack_sdk.server.context import RunContext from .embedding.client import get_embedding_client from .embedding.embed import embed_chunks from .extraction import extract_file from .text_splitting import chunk_markdown from .vector_store.create import create_vector_store from .vector_store.search import search_vector_store # File formats supported by the text-extraction service (docling) default_input_modes = [ "text/plain", "application/pdf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", # DOCX "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", # XLSX "application/vnd.openxmlformats-officedocument.presentationml.presentation", # PPTX "text/markdown", # Markdown "text/asciidoc", # AsciiDoc "text/html", # HTML "application/xhtml+xml", # XHTML "text/csv", # CSV "image/png", # PNG "image/jpeg", # JPEG "image/tiff", # TIFF "image/bmp", # BMP "image/webp", # WEBP ] server = Server() @server.agent( default_input_modes=default_input_modes, default_output_modes=["text/plain"], ) async def conversation_rag_agent_example( input: Message, context: RunContext, embedding: Annotated[EmbeddingServiceExtensionServer, EmbeddingServiceExtensionSpec.single_demand()], _: Annotated[PlatformApiExtensionServer, PlatformApiExtensionSpec()], ): # Create embedding client embedding_client, embedding_model = get_embedding_client(embedding) # Extract files and query from input files: list[File] = [] query = "" for part in input.parts: match part.root: case FilePart(file=FileWithUri(uri=uri)): files.append(await File.get(PlatformFileUrl(uri).file_id)) case TextPart(text=text): query = text case _: raise NotImplementedError(f"Unsupported part: {type(part.root)}") # Check if vector store exists vector_store = None async for message in context.load_history(): match message: case Message(parts=[Part(root=DataPart(data=data))]): vector_store = await VectorStore.get(data["vector_store_id"]) # Create vector store if it does not exist if not vector_store: vector_store = await create_vector_store(embedding_client, embedding_model) # store vector store id in context for future messages data_part = DataPart(data={"vector_store_id": vector_store.id}) await context.store(AgentMessage(parts=[data_part])) # Process files, add to vector store for file in files: await extract_file(file) async with file.load_text_content() as loaded_file: chunks = chunk_markdown(loaded_file.text) items = await embed_chunks(file, chunks, embedding_client, embedding_model) await vector_store.add_documents(items=items) # Search vector store if query: results = await search_vector_store(vector_store, query, embedding_client, embedding_model) snippet = [res.model_dump() for res in results] # TODO: You can add LLM result processing here yield f"# Results:\n{json.dumps(snippet, indent=2)}" elif files: yield f"{len(files)} file(s) processed" else: yield "Nothing to do" def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` ### Next steps To further improve the agent, learn how to use other parts of the platform such as LLMs, file uploads and conversations: * [LLM extension](./llm-proxy-service) * [Multi-turn conversations](./multi-turn) * [File handling](./files) # Manage Runtime Secrets Source: https://agentstack.beeai.dev/stable/agent-integration/secrets Learn how your agent can request secrets (e.g. API keys) from the user The **Secrets Extension** provides a secure way for agent builders to request sensitive values, such as API keys, directly from the user. Rather than hardcoding credentials or relying on local environment variables that may not exist in a production environment, the Secrets Extension allows your agent to demand specific values. These can be provided by the user before execution (pre-configured) or requested dynamically during the run if the agent hits a blocker. Once a secret is provided, the platform stores it securely so the user isn't prompted repeatedly in future sessions. Users can revoke or update the secret at any time through the GUI. ## Basic Secrets Implementation Here's how to add secrets capabilities to your agent: ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from typing import Annotated from a2a.types import Message from agentstack_sdk.a2a.extensions import ( SecretDemand, SecretsExtensionServer, SecretsExtensionSpec, SecretsServiceExtensionParams, ) from agentstack_sdk.server import Server server = Server() @server.agent() async def basic_secrets_example( input: Message, secrets: Annotated[ SecretsExtensionServer, SecretsExtensionSpec.single_demand(key="SLACK_API_KEY", name="Slack", description="Access to Slack"), ], ): """Agent that requests a secret that can be provided during runtime""" if secrets and secrets.data and secrets.data.secret_fulfillments: yield f"Slack API key: {secrets.data.secret_fulfillments['SLACK_API_KEY'].secret.get_secret_value()}" else: try: runtime_provided_secrets = await secrets.request_secrets( params=SecretsServiceExtensionParams( secret_demands={"SLACK_API_KEY": SecretDemand(description="I really need Slack Key", name="Slack")} ) ) except ValueError: runtime_provided_secrets = None if runtime_provided_secrets and runtime_provided_secrets.secret_fulfillments: yield f"Slack API key: {runtime_provided_secrets.secret_fulfillments['SLACK_API_KEY'].secret.get_secret_value()}" else: yield "No Slack API key provided" def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` Import `SecretsExtensionServer`, `SecretsExtensionSpec`, `SecretDemand`, and `SecretsServiceExtensionParams` from `agentstack_sdk.a2a.extensions`. Add a secrets parameter to your agent function using the `Annotated` type hint with `SecretsExtensionServer` and `SecretsExtensionSpec`. Create `SecretDemand` objects for each secret your agent needs, specifying the name and description. Check if secrets are provided and use `await secrets.request_secrets(params=...)` to request them dynamically if missing. Ensure your agent handles cases where secrets are not provided. Always check if the secrets extension is available before using it to comply with plain A2A clients. ## Usage Patterns There are two main patterns for working with secrets in your agents: ### Pre-configured Secrets When secrets are provided before the agent runs, they're available immediately in the `secrets.data.secret_fulfillments` object. This is the preferred approach as it provides a smoother user experience. ```python theme={null} # Check if secrets are pre-configured if secrets and secrets.data and secrets.data.secret_fulfillments: api_key = secrets.data.secret_fulfillments['default'].secret # Use the secret immediately ``` ### Dynamic Secret Requests When secrets aren't pre-configured, you can request them during runtime using `await secrets.request_secrets()`. This is useful when your agent needs to ask for secrets based on user input or when the secret requirement is conditional. ```python theme={null} # Request secrets dynamically runtime_secrets = await secrets.request_secrets( params=SecretsServiceExtensionParams( secret_demands={"default": SecretDemand(description="API key needed", name="API Key")} ) ) ``` # Approve Tool Calls Source: https://agentstack.beeai.dev/stable/agent-integration/tool-calls Implement a human-in-the-loop workflow by requiring user authorization before an agent executes specific tools. Many agent frameworks support the ability to request user approval before executing certain actions. This is especially useful when an agent is calling external tools that may have significant effects or costs associated with their usage. The **Approval Extension** pauses execution and prompts the user with the tool details, resuming only after approval is granted. ## Basic Implementation To implement approvals, you inject the extension and call `request_approval()` within your tool execution logic or a framework handler. This example uses the [BeeAI Framework](https://framework.beeai.dev/modules/agents/requirement-agent#ask-permission-requirement) to request user approval before executing a tool call: ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from typing import Annotated, Any from a2a.types import Message, TextPart from agentstack_sdk.a2a.extensions import ( ApprovalExtensionParams, ApprovalExtensionServer, ApprovalExtensionSpec, ToolCallApprovalRequest, ) from agentstack_sdk.server import Server from agentstack_sdk.server.context import RunContext from beeai_framework.adapters.mcp.serve.server import _tool_factory from beeai_framework.agents.requirement import RequirementAgent from beeai_framework.agents.requirement.requirements.ask_permission import AskPermissionRequirement from beeai_framework.backend import ChatModel from beeai_framework.tools import AnyTool from beeai_framework.tools.think import ThinkTool server = Server() @server.agent() async def basic_approve_example( input: Message, context: RunContext, approval_ext: Annotated[ApprovalExtensionServer, ApprovalExtensionSpec(params=ApprovalExtensionParams())], ): async def handler(tool: AnyTool, input: dict[str, Any]) -> bool: response = await approval_ext.request_approval( # using MCP Tool data model as intermediary to simplify conversion ToolCallApprovalRequest.from_mcp_tool(_tool_factory(tool), input=input), context=context, ) return response.approved think_tool = ThinkTool() agent = RequirementAgent( llm=ChatModel.from_name(os.getenv("LLM_MODEL", "ollama:gpt-oss:20b")), tools=[think_tool], requirements=[AskPermissionRequirement([think_tool], handler=handler)], ) result = await agent.run("".join(part.root.text for part in input.parts if isinstance(part.root, TextPart))) yield result.output[0].text def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` Import `ApprovalExtensionServer`, `ApprovalExtensionSpec`, `ApprovalExtensionParams`, and `ToolCallApprovalRequest` from `agentstack_sdk.a2a.extensions`. Add an approval parameter to your agent function using the `Annotated` type hint with `ApprovalExtensionServer` and `ApprovalExtensionSpec`. Within your agent logic or tool handler, call await `approval_ext.request_approval()`. This triggers a UI prompt for the user and pauses execution until they respond. Access the approved boolean from the response. If `True`, proceed with the tool execution; if `False`, handle the rejection gracefully (e.g., by yielding a message to the user). # Visualize Agent Trajectories Source: https://agentstack.beeai.dev/stable/agent-integration/trajectory Show users the step-by-step reasoning process of your agent. The Trajectory extension allows you to visualize the step-by-step reasoning process of your agent as it runs. Trajectory steps appear as expandable sections in the UI, helping users understand your agent's thought process, making the interaction more transparent and trustworthy. ## Example: Basic Usage ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from time import sleep from typing import Annotated from a2a.types import Message from agentstack_sdk.a2a.extensions import TrajectoryExtensionServer, TrajectoryExtensionSpec from agentstack_sdk.server import Server from agentstack_sdk.server.context import RunContext server = Server() @server.agent( name="Docs Trajectory Agent", ) async def trajectory_basic_usage_example( input: Message, context: RunContext, trajectory: Annotated[TrajectoryExtensionServer, TrajectoryExtensionSpec()] ): yield trajectory.trajectory_metadata( title="Planning", content="Analyzing the user request to determine the best approach..." ) sleep(3) # Sleep so that you can watch the trajectory steps unfold yield trajectory.trajectory_metadata(title="Execution", content="Processing data with temperature=0.7...") sleep(3) yield "Final result goes here" def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` Import `TrajectoryExtensionServer` and `TrajectoryExtensionSpec` from `agentstack_sdk.a2a.extensions`. Add a trajectory parameter to your agent function using the `Annotated` type hint with `TrajectoryExtensionSpec()`. Call `yield trajectory.trajectory_metadata()` with a `title` and `content` to add a step to the trajectory. ## Markdown Support The `content` field of `trajectory_metadata` supports Markdown, which is rendered directly in the UI. Supported elements include: * Headers * Bold and italic text * Ordered and unordered lists * Tables * Code blocks * Links * Checklists ```python theme={null} yield trajectory.trajectory_metadata( title="Checklist", content=""" - [x] Load data - [x] Validate schema - [ ] Run inference - [ ] Generate report """ ) ``` ## Grouping Trajectories Use the `group_id` parameter to update trajectory content in the UI. Each update replaces the previous content (and title, if defined) for the same `group_id`: ```python theme={null} # Initial search status yield trajectory.trajectory_metadata( title="Web search", content="Querying search engines...", group_id="websearch" ) # Update with results sleep(3) yield trajectory.trajectory_metadata( content="Found 8 results", group_id="websearch" ) sleep(3) yield trajectory.trajectory_metadata( content="Found 8 results\nAnalyzed 3/8 results", group_id="websearch" ) sleep(3) yield trajectory.trajectory_metadata( content="Found 8 results\nAnalyzed 8/8 results", group_id="websearch" ) sleep(3) # Final update yield trajectory.trajectory_metadata( title="Web search finished", content="Found 8 results\nAnalyzed 3/8 results\nExtracted key information", group_id="websearch" ) ``` ## Common Patterns **Progress Steps:** ```python theme={null} yield trajectory.trajectory_metadata(title="Step 1", content="Loading data...") yield trajectory.trajectory_metadata(title="Step 2", content="Processing...") yield trajectory.trajectory_metadata(title="Step 3", content="Generating output...") ``` **Decision Points:** ```python theme={null} yield trajectory.trajectory_metadata( title="Tool Selection", content="Choosing search tool based on query type: factual" ) ``` **Error Handling:** ```python theme={null} yield trajectory.trajectory_metadata( title="Retry Attempt", content="First attempt failed, trying alternative approach..." ) ``` **Multi-step Process with Live Updates:** ```python theme={null} # Use group_id to show progress updates in a single section yield trajectory.trajectory_metadata( title="Data Processing", content="Starting...", group_id="processing" ) yield trajectory.trajectory_metadata( content="Processing batch 1/10...", group_id="processing" ) yield trajectory.trajectory_metadata( content="Processing batch 10/10...", group_id="processing" ) yield trajectory.trajectory_metadata( content="Processing complete! Processed 10 items", group_id="processing" ) ``` ## Example: Advanced Usage For a more advanced example, see [trajectory\_agent.py](https://github.com/i-am-bee/agentstack/blob/main/apps/agentstack-sdk-py/examples/trajectory_agent.py). # Connect A2A Agents Source: https://agentstack.beeai.dev/stable/deploy-agents/a2a-agents Instant UI for existing A2A agents Got an existing A2A agent that is not wrapped using Agent Stack SDK? No problem, you can still connect it to Agent Stack by providing a Docker image or its running URL. ## Add Docker A2A Agent When you want your agent to be managed via Agent Stack, provide a Docker image: ```bash theme={null} agentstack add ``` For example: ```bash theme={null} agentstack add ghcr.io/my-org/my-agent:latest ``` ### Requirements Your A2A agent Docker image must: 1. **Expose a valid agent card** at `/.well-known/agent-card.json` 2. **Start without required environment variables** — Agent Stack temporarily runs the container to extract metadata, then terminates it. Your image must be able to start with sensible defaults (no mandatory env vars). ### How It Works When you register an A2A agent: 1. Agent Stack pulls and starts your Docker image 2. Probes `/.well-known/agent-card.json` to extract agent metadata 3. Terminates the container 4. Uses the metadata to make the agent available in the interface When users interact with your agent, Agent Stack manages the container lifecycle automatically. ## Add Unmanaged A2A Agent An **unmanaged agent** is an A2A agent whose lifecycle is not controlled by Agent Stack. You are responsible for starting, stopping, and maintaining it. Agent Stack only proxies requests to it. If your A2A agent is already running and you don't want Agent Stack to manage its lifecycle (unmanaged), you can add it directly via its URL: ```bash theme={null} agentstack add ``` For example: ```bash theme={null} agentstack add http://localhost:8080 ``` ### Requirements Your running A2A agent must expose a valid agent card at `/.well-known/agent-card.json`. For the example above, this would be at `http://localhost:8080/.well-known/agent-card.json`. ### How It Works When you register an unmanaged A2A agent, Agent Stack fetches the agent card from the `/.well-known/agent-card.json` endpoint of the provided URL. It then enhances the metadata from the card to make the agent available in the interface. Agent Stack will not manage the lifecycle of this agent. When users interact with it, requests are proxied to the provided agent URL. # Build New Agents Source: https://agentstack.beeai.dev/stable/deploy-agents/building-agents Start building your own agent with a simple Hello World example To help you get started quickly, we’ve created a ready-to-use starter repository. The starter repo provides you basic scaffolding, a GitHub workflow, and a Dockerfile. You can clone it and start coding right away—no setup headaches or boilerplate required. Then customize the agent with your own logic. ## Prerequisites * Agent Stack installed ([Quickstart](../introduction/quickstart)) and the platform running with `agentstack platform start` * [uv](https://docs.astral.sh/uv/) package manager (should be already installed if you followed the quickstart) ## 1. Start From Template ```bash theme={null} git clone https://github.com/i-am-bee/agentstack-starter my-agent cd my-agent ``` 1. Go to the [template repository](https://github.com/i-am-bee/agentstack-starter) 2. Click "Use this template" → "Create a new repository" 3. Clone your new repository locally ```bash theme={null} uv run server ``` **Enable auto-reload during development:** Add `watchfiles` to automatically restart your server when code changes: ```bash theme={null} uv run watchfiles agentstack_agents.agent.run ``` In another terminal: ```bash theme={null} agentstack run example_agent "Alice" ``` You should see: "Ciao Alice!" 🎉 With your first agent running, you can now modify it to do anything you want. ## 2. Implement Your Agent Logic In the starter repo, navigate to [src/agentstack\_agents/agent.py](https://github.com/i-am-bee/agentstack-starter/blob/main/src/agentstack_agents/agent.py) and replace the example with your agent logic. The starter example is minimal and intended for demonstration purposes only: ```python theme={null} # Copyright 2025 © BeeAI a Series of LF Projects, LLC # SPDX-License-Identifier: Apache-2.0 import os from a2a.types import Message from a2a.utils.message import get_message_text from agentstack_sdk.a2a.types import AgentMessage from agentstack_sdk.server import Server from agentstack_sdk.server.context import RunContext server = Server() @server.agent() async def implement_your_agent_logic_example(input: Message, context: RunContext): """Polite agent that greets the user""" hello_template: str = os.getenv("HELLO_TEMPLATE", "Ciao %s!") yield AgentMessage(text=hello_template % get_message_text(input)) def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` An agent is essentially an HTTP server. Use `server.run()` to define how your agent listens for requests: * Host: Defaults to `127.0.0.1` (localhost). * Port: Defaults to `8000`. If you run multiple agents on the same machine, ensure each has a unique port. Add the `@server.agent` decorator to your function so the platform recognizes it as an agent. * Identity: The function name (e.g., example\_agent) becomes the agent’s unique identifier. * Metadata: Write a clear docstring. The SDK extracts this text to serve as the agent’s description in UIs and registries, helping understand what your agent does. * First argument: An [A2A `Message`](https://a2a-protocol.org/latest/specification/#64-message-object). Use `get_message_text(input)` to pull the raw string from the user. This is where you would typically pass data into your LLM or custom processing logic. * Second argument: A `RunContext` object with run details provides metadata about the current execution (e.g., `task_id`, `context_id`). Use this if your agent logic needs to track session state or reference specific task parameters. Agents should be async and use `yield`. This allows the server to stream responses back to the client in real time. Yield an `AgentMessage` (a handy wrapper around A2A Message) for convenience or a plain `str`, which will be automatically converted into an A2A Message. If you prefer not to use our starter template, you must ensure your project includes these core components to integrate correctly with the Agent Stack platform. * The SDK Inegration * Add agentstack-sdk to your project dependencies. * Your code must initialize a Server() instance from the SDK. * You need a designated function that calls server.run(host, port). This is what allows the platform (and Docker) to start your agent. * Agent logic remains the same as in the [Implement Your Agent Logic](#2-implement-your-agent-logic) section * Containerization Requirements * If you plan to deploy your agent to the platform (rather than just running it locally), your repository must contain a `Dockerfile` * Git and Platform Metadata * The repository must be accessible to the Agent Stack orchestrator if you are using the `agentstack add` command via URL. * Use Git tags (e.g., v1.0.0) if you want to manage stable releases of your agent. ## Next Steps After building your agent, you can enhance it and learn more: Customize your agent's name, description, and how it appears in the UI Learn how agents and clients communicate through structured messaging Understand how to handle multi-turn conversations and maintain context Work with files to provide inputs or store outputs for your agent # Deploy Your Agents Source: https://agentstack.beeai.dev/stable/deploy-agents/deploy-your-agents Package and deploy your agent to Agent Stack as a managed service Once you've wrapped your agent with the Agent Stack server, you need to containerize it and deploy it, so that Agent Stack can run it as a managed service. ## Prerequisites * Agent Stack installed ([Quickstart](/stable/introduction/quickstart)) * Agent wrapped with Agent Stack SDK ([Wrap Existing Agents](/stable/deploy-agents/wrap-existing-agents) or [Build New Agent](/stable/deploy-agents/building-agents)) ## Agent Deploy Process To deploy your agent to Agent Stack, you need to create a github repository with your agent and then deploy from that repository to Agent Stack. ### 1. Create an Agent github repository You can quickly create an agent repository by using the Agent Stack starter template. The [agentstack-starter template](https://github.com/i-am-bee/agentstack-starter) includes everything you need: * Production-ready Dockerfile * GitHub Actions for automated builds * Agent Stack deployment configuration Clone it and modify it for your agent as follows: ```bash theme={null} git clone https://github.com/i-am-bee/agentstack-starter my-agent cd my-agent # Replace the example agent with your code ``` Alternatively, if you want to start from scratch, you can create your own github repository. You will need to include a `Dockerfile` with the following information: ```dockerfile theme={null} FROM python:3.13-alpine3.23 COPY --from=ghcr.io/astral-sh/uv:0.10.4 /uv /bin/ WORKDIR /app ADD . . RUN HOME=/tmp uv sync --no-cache --locked --link-mode copy ENV PRODUCTION_MODE=true CMD ["/app/.venv/bin/server"] ``` ### 2. Deploy your Agent to Agent Stack To deploy from your my-agent github repo, run the following with your organization name substituted for myorg: ```bash theme={null} agentstack add https://github.com/myorg/my-agent ``` This command automatically: 1. ✓ Builds your Docker image from github using the provided Dockerfile in the github root directory 2. ✓ Copies the image into Agent Stack's VM 3. ✓ Registers it as an available agent As a specific example, to deploy the agentstack-starter example agent without modification, you can issue the following command: ```bash theme={null} agentstack add https://github.com/i-am-bee/agentstack-starter ``` **Why "copy into VM"?** Agent Stack runs in an isolated VM (Lima on Mac/Linux, WSL on Windows). Even though Docker Desktop builds your image, Agent Stack needs it copied into its VM to run it. The `add` command handles this automatically. ## Github Deploy Options The `agentstack add` command supports various URL formats to specify the repository, version, and location of your agent's code. The supported formats include: * **Basic URL**: `https://github.com/myorg/myrepo` * **Git Protocol URL**: `git+https://github.com/myorg/myrepo` * **URL with .git suffix**: `https://github.com/myorg/myrepo.git` * **URL with Version Tag**: `https://github.com/myorg/myrepo@v1.0.0` * **URL with Branch Name**: `https://github.com/myorg/myrepo@my-branch` * **URL with Subfolder Path**: `https://github.com/myorg/myrepo#path=/path/to/agent` * **Combined Formats**: `https://github.com/myorg/myrepo.git@v1.0.0#path=/path/to/agent` * **Enterprise GitHub**: `https://github.mycompany.com/myorg/myrepo` #### Example To deploy an agent from a specific branch and subfolder, you would run: ```bash theme={null} agentstack add "https://github.com/my-org/my-awesome-agents@main#path=/my-agent" ``` This command tells Agent Stack to: 1. Fetch the `main` branch of the `my-awesome-agents` repository. 2. Look for the agent's `Dockerfile` and source code in the `/my-agent` directory. 3. Build the Docker image and register it with the platform. ### Verify Deployment You can check that your agent is registered by running: ```bash theme={null} agentstack list ``` Your agent should appear in the list with status information. ## Test Your Agent Test via CLI: ```bash theme={null} agentstack run my-agent "Hello!" ``` Or open the web UI: ```bash theme={null} agentstack ui ``` Your agent will be available at `http://127.0.0.1:8333` ## Advanced Options ### Two-step build process If you need more control over the build: ```bash theme={null} # Step 1: Build and copy to Agent Stack VM agentstack build https://github.com/myorg/myrepo # Step 2: Register the agent agentstack add agentstack.local/my-agent-abc123:latest ``` ## Next Steps Now that your agent is deployed, enhance it with extensions: Change your agent's LLM at runtime and manage model connections dynamically Visualize your agent’s decision-making and interactions over time in the UI Display references and link sources for transparency, directly in the UI Guide your users to provide consistent information with the form extension # Wrap Your Existing Agents Source: https://agentstack.beeai.dev/stable/deploy-agents/wrap-existing-agents Deploy your existing agent to Agent Stack by wrapping it with the Agent Stack server If you already have an agent built with any framework (LangGraph, CrewAI, or your own custom implementation), you can deploy it to Agent Stack by wrapping it with the Agent Stack server. This gives you instant access to the Agent Stack UI, observability features, and deployment infrastructure without rewriting your agent logic. ## Prerequisites * Agent Stack installed ([Quickstart](../introduction/quickstart)) * An existing agent implementation * Python 3.12+ environment ## How It Works The Agent Stack server wraps your existing agent code and exposes it through the A2A protocol. Your agent logic stays exactly the same - you just add a thin server wrapper that handles: * Protocol translation (A2A) * Auto-registration with Agent Stack * Session management * Extension support ## Quick Start ### 1. Install the SDK ```bash theme={null} uv add agentstack-sdk ``` If you are starting a new `uv` project, run `uv init` to set up the project structure before adding packages. ### 2. Create a Server Wrapper Create a new file (e.g., `server.py`) that wraps your existing agent: ```python theme={null} # Import your existing agent logic from my_agent import run_my_agent # Your existing agent code import os from a2a.types import Message from a2a.utils.message import get_message_text from agentstack_sdk.server import Server from agentstack_sdk.server.context import RunContext from agentstack_sdk.a2a.types import AgentMessage server = Server() @server.agent() async def my_wrapped_agent(input: Message, context: RunContext): """Wrapper around my existing agent""" # Extract the user's message user_message = get_message_text(input) # Call your existing agent logic # This can be synchronous or asynchronous result = await run_my_agent(user_message) # Yield the response back to Agent Stack yield AgentMessage(text=result) def run(): server.run( host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000)) ) if __name__ == "__main__": run() ``` ### 3. Run Your Server ```bash theme={null} uv run server.py ``` Your agent will automatically register with Agent Stack! **Enable auto-reload during development:** Add `watchfiles` to automatically restart your server when code changes: ```bash theme={null} uv run watchfiles agentstack_agents.agent.run ``` ## Advanced Implementations ### Streaming Responses If your agent generates responses incrementally, you can stream them: ```python theme={null} @server.agent() async def streaming_agent(input: Message, context: RunContext): user_message = get_message_text(input) # Stream results as they come async for chunk in my_streaming_agent(user_message): yield AgentMessage(text=chunk) ``` ### With Context History Access previous messages in the conversation: ```python theme={null} @server.agent() async def contextual_agent(input: Message, context: RunContext): # Get conversation history previous_messages = context.history # Your agent can use this context result = await my_agent_with_context( current_message=get_message_text(input), history=previous_messages ) yield AgentMessage(text=result) ``` # Quickstart Source: https://agentstack.beeai.dev/stable/introduction/quickstart Get Agent Stack up and running in minutes. Then explore running pre-built agents. ## Prerequisites * Basic terminal/command line experience * LLM API key (OpenAI, Anthropic, Gemini, watsonx, or free via [OpenRouter](https://openrouter.ai/) / [Groq](https://groq.com/)) or [Ollama](https://ollama.com/) (requires a powerful computer) * Fast internet (2–3GB download) ## Installation ## One-Line Install Open the terminal and run this command to install Agent Stack: ```bash theme={null} sh -c "$(curl -LsSf https://agentstack.beeai.dev/install.sh)" ``` The installer sets up `uv` and the `agentstack-cli` on your host machine. Agent Stack uses a virtual machine (managed via a bundled [Lima](https://github.com/lima-vm/lima) binary) to keep your host machine clean. The Kubernetes cluster running the Agent Stack server, agent containers, and associated storage and services live entirely inside the VM and not on your host directly. For details on what happens under the hood, refer to the Manual Install section or read the [`install.sh`](https://github.com/i-am-bee/agentstack/blob/install/install.sh) source code. Follow the [installation instructions](https://docs.astral.sh/uv/getting-started/installation/). Agent Stack uses `uv` to manage its own internal Python environment. On Linux, Lima requires QEMU. Install it via these ([instructions](https://www.qemu.org/download/)). Open a new terminal and run: ```bash theme={null} uv python install --quiet --python-preference=only-managed --no-bin 3.14 && uv tool install --refresh --force --python-preference=only-managed --python=3.14 agentstack-cli && agentstack self install ``` Follow the interactive prompts to finish setup and optionally start the VM. Windows support is experimental and requires Windows 11 22H2 or later. For the best experience, we recommend Linux or macOS. Open PowerShell as Administrator and run: ```powershell theme={null} wsl.exe --update ``` Follow the instructions displayed, if any, and continue by running: ```powershell theme={null} wsl.exe --install ``` The command output may instruct you to restart your PC. Do so, and afterwards run the same command again. It may take up to two restarts to complete the installation process. WSL is properly set up once you reach a working Linux shell. You can verify this by running `wsl.exe` without arguments. Once you have a working shell, you can close it and proceed to the next step. Even though WSL2 is required, Agent Stack CLI can't be installed *inside* WSL2. All subsequent commands should be run from Windows PowerShell. Open PowerShell and run: ```bash theme={null} powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" ``` ```bash theme={null} winget install astral-sh.uv uv tool update-shell ``` ```bash theme={null} scoop install uv uv tool update-shell ``` ```bash theme={null} choco install uv uv tool update-shell ``` Close and reopen your terminal after installation. If you plan to use Ollama, we recommend installing the [Windows app](https://ollama.com/download/windows). In the Ollama app settings, it's necessary to enable *"Expose Ollama to the network"* in order for it to be accessible by Agent Stack. After enabling this option, you may get a firewall prompt where you need to select *"Allow"*. Run in PowerShell: ```bash theme={null} setx HOST 0.0.0.0 ``` Close and reopen your terminal after this. This will make local agents accessible over the local network, which is needed for Agent Stack to access them. When running local agents, you may encounter firewall prompts where you need to select *"Allow"*. Run in PowerShell: ```bash theme={null} uv python install --quiet --python-preference=only-managed --no-bin 3.14; uv tool install --refresh --force --python-preference=only-managed --python=3.14 agentstack-cli; agentstack self install ``` Follow the interactive prompts to finish the installation and setup. ## Usage ```sh theme={null} agentstack ui # Launch web interface agentstack list # See what agents are available agentstack run chat "Hi, who are you" # Send a message to chat agent agentstack run chat # Try interactive mode agentstack info chat # View agent details agentstack --help # See all options ``` ## Platform Management ```sh theme={null} agentstack platform start # Start platform agentstack model setup # Configure LLM provider agentstack platform stop # Stop (keeps data) agentstack platform delete # Delete all data ``` ## Version & Upgrades If you are having issues with these commands, you may be running an old version of Agent Stack. Run the one-line install script above to upgrade to the latest version. ```sh theme={null} agentstack self version # Check version agentstack self upgrade # Update agentstack self uninstall # Remove completely ``` # Welcome Source: https://agentstack.beeai.dev/stable/introduction/welcome Agent Stack is open infrastructure for turning AI agents into running services in minutes. **Writing an agent is easy. Shipping one is not.** Agent Stack gives you everything you need to run agents as backend services: LLM routing, vector storage, authentication, file handling, and deployment tooling out of the box. You write agent logic in any framework. Agent Stack provides the infrastructure so you can ship to users in minutes, not weeks. | Component | What's Included | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Agent Runtime** | - Self-hostable server to run agents in production | | **LLM & AI Services** | - LLM service with support for 15+ providers (Anthropic, OpenAI, watsonx.ai, Ollama)
- Embeddings and vector search for RAG and semantic search | | **Agent Deployment & Management** | - CLI for deploying, updating, and managing agents | | **Storage & Documents** | - S3-compatible file storage for uploads and downloads
- Document text extraction via Docling | | **Interfaces & Tooling** | - Out-of-the-box Web UI for testing and sharing agents
- Client SDK for building custom UIs and applications | | **Integrations** | - External integrations via MCP protocol (APIs, Slack, Google Drive, etc.) with OAuth | | **Security** | - Secrets management for API keys and credentials
- OAuth support for secure external integrations | | **Agent Stack Deployment** | - Helm chart for Kubernetes with customizable storage, databases, and authentication | | **Framework Interoperability** | - Build agents using LangGraph, CrewAI, or your own framework
- Agents are automatically exposed as A2A-compatible agents
- `agentstack-sdk` handles runtime service requests and agent-to-agent communication | ## Get running in one command Start a complete agent runtime locally — models, storage, and services included. ```bash theme={null} sh -c "$(curl -LsSf https://agentstack.beeai.dev/install.sh)" ``` You now have a working environment for running agents as services. Follow the [quickstart](./quickstart) for full installation instructions and requirements. ## Getting from “agent” to “running service” is harder than it should be When you go from running your agent locally to powering a real application, you run into a wall of infrastructure work: * Choosing and wiring an LLM gateway * Configuring vector storage and embeddings * Handling auth, secrets, files, and artifacts * Making local experiments behave the same way in deployment * Rebuilding everything once production enters the picture **Agent Stack removes that friction.** It gives you a ready-made runtime for running agents as services, so you can focus on agent logic instead of infrastructure glue. With Agent Stack: * Agents run locally and in deployed environments without major refactoring * Agents are exposed as stable, callable services * Applications can integrate with agents like any backend dependency * Infrastructure is decoupled from application logic You keep full freedom in how agents are authored. Agent Stack standardizes how agents are run, deployed, and integrated. ## How does it work? Agent Stack runs agents as services alongside your applications. ``` Application | | SDK / HTTP v Agent Stack (agent services) | v LLMs • Vector Store • Auth • Files ``` Agent Stack provides sensible defaults for instant usability. However, it's fully pluggable, giving you the freedom to swap in your own custom services and configurations whenever needed. ## Getting started ### 1. Wrap an existing agent Agent Stack lets you take an agent you already have and expose it as a service by providing the sensible infrastructure defaults they need. Here’s an example wrapping a simple agent function: ```shell theme={null} uv add agentstack-sdk ``` ```python theme={null} import os from a2a.types import Message from a2a.utils.message import get_message_text from agentstack_sdk.server import Server from agentstack_sdk.server.context import RunContext from agentstack_sdk.a2a.types import AgentMessage server = Server() @server.agent() async def example_agent(input: Message, context: RunContext): """Polite agent that greets the user""" hello_template: str = os.getenv("HELLO_TEMPLATE", "Ciao %s!") yield AgentMessage(text=hello_template % get_message_text(input)) def run(): server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000))) if __name__ == "__main__": run() ``` ```shell theme={null} uv run example_agent.py ``` Your agent can be: * A LangGraph workflow * A custom reasoning loop * A thin wrapper around an LLM call Agent Stack doesn’t care—as long as it can be executed. ### 2. Deploy the agent as service Run the agent locally or on infrastructure you control. ```shell theme={null} uv run example_agent "Emily" ``` Your agent is now running as a backend service. ### 3. Call the agent from your application Integrate the agent into your app using the [Client SDK](../custom-ui/getting-started), HTTP API, or our out of the box UI extensions. Agent Stack takes care of running and deploying agents so you can wire them into your application like any other backend service. ## What Agent Stack is (and isn’t) Agent Stack is: * A bundled runtime for deploying agent services * Optimized for experimentation and fast iteration * Flexible enough to grow with you from local testing to a sandboxed deployment platform Agent Stack isn't: * An agent building framework * A hosted AI service * A replacement for enterprise AI platforms ## Getting help, contributing, and staying up to date Agent Stack is an open-source project maintained as part of the Linux Foundation community. * **Documentation**: Start here for guides and examples * **Issues**: Use [GitHub Issues](https://github.com/i-am-bee/agentstack/issues) to report bugs or request features * **Discussions**: Use [Github Discussions](https://github.com/i-am-bee/agentstack/discussions) to ask questions, share ideas, and compare approaches * **Contributing**: Contributions are welcome— see the [contributing guide](https://github.com/i-am-bee/agentstack/blob/main/CONTRIBUTING.md) for details Agent Stack is being actively developed and intended to evolve alongside the agent ecosystem. Defaults may change, components may improve, but the goal remains the same: make agents easy to run, easy to deploy, and easy to integrate. Get up and running in one command Deploy your existing agents to Agent Stack Build your first agent with the SDK Deploy Agent Stack to Kubernetes for your team # CLI Reference Source: https://agentstack.beeai.dev/stable/reference/cli-reference Complete reference for the Agent Stack command-line interface The Agent Stack command-line interface (CLI) provides tools to manage agents, configure models, and control the platform directly from your terminal. **Usage**: ```console theme={null} $ agentstack [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--help`: Show this message and exit. * `--install-completion`: Install completion for the current shell. * `--show-completion`: Show completion for the current shell, to copy it or customize the installation. **Commands**: * `version`: Print version of the Agent Stack CLI. * `ui`: Launch the graphical interface. * `model`: Manage model providers. * `agent`: Manage agents. * `connector`: Manage connectors to external services. * `platform`: Manage Agent Stack platform. * `client-side-build`: Build agent locally using Docker. * `build`: Build agent from a GitHub repository in... * `server`: Manage Agent Stack servers and... * `self`: Manage Agent Stack installation. * `add`: Add a docker image or GitHub repository. * `update`: Upgrade agent to a newer docker image or... * `remove | uninstall | rm | delete`: Remove agent. * `logs`: Stream agent provider logs. * `run`: Run an agent. * `list`: List agents. * `info`: Show agent details. * `env` * `feedback`: Manage user feedback for your agents ## `agentstack version` Print version of the Agent Stack CLI. **Usage**: ```console theme={null} $ agentstack version [OPTIONS] ``` **Options**: * `-v, --verbose`: Show verbose output * `--help`: Show this message and exit. ## `agentstack ui` Launch the graphical interface. **Usage**: ```console theme={null} $ agentstack ui [OPTIONS] ``` **Options**: * `--help`: Show this message and exit. ## `agentstack model` Manage model providers. \[Admin only] **Usage**: ```console theme={null} $ agentstack model [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--help`: Show this message and exit. **Commands**: * `list`: List all available models. * `setup`: Interactive setup for LLM and embedding... * `change | select | default`: Change the default model \[Admin only] * `add`: Add a new model provider. * `remove | rm | delete` * `provider` ### `agentstack model list` List all available models. **Usage**: ```console theme={null} $ agentstack model list [OPTIONS] ``` **Options**: * `--help`: Show this message and exit. ### `agentstack model setup` Interactive setup for LLM and embedding provider environment variables \[Admin only] **Usage**: ```console theme={null} $ agentstack model setup [OPTIONS] ``` **Options**: * `-v, --verbose`: Show verbose output * `--llm-provider TEXT`: LLM provider type (e.g. openai, anthropic, watsonx) * `--llm-api-key TEXT`: LLM provider API key * `--llm-base-url TEXT`: Base URL for 'other' LLM provider * `--llm-watsonx-region TEXT`: IBM watsonx region for LLM (e.g. us-south) * `--llm-watsonx-project-or-space TEXT`: IBM watsonx: 'project' or 'space' * `--llm-watsonx-project-or-space-id TEXT`: IBM watsonx project or space ID for LLM * `--llm-model TEXT`: Default LLM model ID (e.g. openai:gpt-4o) * `--llm-bedrock-region TEXT`: AWS region for Bedrock LLM (e.g. us-east-1) * `--llm-bedrock-access-key TEXT`: AWS Access Key ID for Bedrock LLM * `--llm-bedrock-secret-key TEXT`: AWS Secret Access Key for Bedrock LLM * `--embedding-provider TEXT`: Embedding provider type (e.g. openai, watsonx) * `--embedding-api-key TEXT`: Embedding provider API key * `--embedding-base-url TEXT`: Base URL for 'other' embedding provider * `--embedding-watsonx-region TEXT`: IBM watsonx region for embedding * `--embedding-watsonx-project-or-space TEXT`: IBM watsonx: 'project' or 'space' * `--embedding-watsonx-project-or-space-id TEXT`: IBM watsonx project or space ID for embedding * `--embedding-model TEXT`: Default embedding model ID * `--embedding-bedrock-region TEXT`: AWS region for Bedrock embedding (e.g. us-east-1) * `--embedding-bedrock-access-key TEXT`: AWS Access Key ID for Bedrock embedding * `--embedding-bedrock-secret-key TEXT`: AWS Secret Access Key for Bedrock embedding * `--skip-embedding`: Skip embedding provider setup * `-y, --yes`: Skip confirmation prompts and auto-select recommended models * `--help`: Show this message and exit. ### `agentstack model change | select | default` Change the default model \[Admin only] **Usage**: ```console theme={null} $ agentstack model change | select | default [OPTIONS] [CAPABILITY]:[llm|embedding] [MODEL_ID] ``` **Arguments**: * `[CAPABILITY]:[llm|embedding]`: Which default model to change (llm/embedding) * `[MODEL_ID]`: Model ID to be used as default **Options**: * `-y, --yes`: Skip confirmation prompts. * `--help`: Show this message and exit. ### `agentstack model add` Add a new model provider. \[Admin only] **Usage**: ```console theme={null} $ agentstack model add [OPTIONS] [CAPABILITY]:[llm|embedding] ``` **Arguments**: * `[CAPABILITY]:[llm|embedding]`: Which default model to change (llm/embedding) **Options**: * `--provider TEXT`: Provider type (e.g. openai, anthropic, watsonx) * `--api-key TEXT`: Provider API key * `--base-url TEXT`: Base URL for 'other' provider * `--watsonx-region TEXT`: IBM watsonx region * `--watsonx-project-or-space TEXT`: IBM watsonx: 'project' or 'space' * `--watsonx-project-or-space-id TEXT`: IBM watsonx project or space ID * `--bedrock-region TEXT`: AWS region for Bedrock (e.g. us-east-1) * `--bedrock-access-key TEXT`: AWS Access Key ID for Bedrock * `--bedrock-secret-key TEXT`: AWS Secret Access Key for Bedrock * `--model TEXT`: Default model ID to use after adding the provider * `-y, --yes`: Skip confirmation prompts and auto-select recommended model * `--help`: Show this message and exit. ### `agentstack model remove | rm | delete` **Usage**: ```console theme={null} $ agentstack model remove | rm | delete [OPTIONS] [SEARCH_PATH] ``` **Arguments**: * `[SEARCH_PATH]`: Provider type or part of the provider base url **Options**: * `-y, --yes`: Skip confirmation prompts. * `--help`: Show this message and exit. ### `agentstack model provider` **Usage**: ```console theme={null} $ agentstack model provider [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--help`: Show this message and exit. **Commands**: * `list`: List all available model providers. * `add`: Add a new model provider \[Admin only] * `remove | rm | delete`: Remove a model provider \[Admin only] #### `agentstack model provider list` List all available model providers. **Usage**: ```console theme={null} $ agentstack model provider list [OPTIONS] ``` **Options**: * `--help`: Show this message and exit. #### `agentstack model provider add` Add a new model provider \[Admin only] **Usage**: ```console theme={null} $ agentstack model provider add [OPTIONS] [CAPABILITY]:[llm|embedding] ``` **Arguments**: * `[CAPABILITY]:[llm|embedding]`: Which default model to change (llm/embedding) **Options**: * `--provider TEXT`: Provider type (e.g. openai, anthropic, watsonx) * `--api-key TEXT`: Provider API key * `--base-url TEXT`: Base URL for 'other' provider * `--watsonx-region TEXT`: IBM watsonx region * `--watsonx-project-or-space TEXT`: IBM watsonx: 'project' or 'space' * `--watsonx-project-or-space-id TEXT`: IBM watsonx project or space ID * `--bedrock-region TEXT`: AWS region for Bedrock (e.g. us-east-1) * `--bedrock-access-key TEXT`: AWS Access Key ID for Bedrock * `--bedrock-secret-key TEXT`: AWS Secret Access Key for Bedrock * `--model TEXT`: Default model ID to use after adding the provider * `-y, --yes`: Skip confirmation prompts and auto-select recommended model * `--help`: Show this message and exit. #### `agentstack model provider remove | rm | delete` Remove a model provider \[Admin only] **Usage**: ```console theme={null} $ agentstack model provider remove | rm | delete [OPTIONS] [SEARCH_PATH] ``` **Arguments**: * `[SEARCH_PATH]`: Provider type or part of the provider base url **Options**: * `-y, --yes`: Skip confirmation prompts. * `--help`: Show this message and exit. ## `agentstack agent` Manage agents. Some commands are \[Admin only]. **Usage**: ```console theme={null} $ agentstack agent [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--help`: Show this message and exit. **Commands**: * `add`: Add a docker image or GitHub repository. * `update`: Upgrade agent to a newer docker image or... * `remove | uninstall | rm | delete`: Remove agent. * `logs`: Stream agent provider logs. * `run`: Run an agent. * `list`: List agents. * `info`: Show agent details. * `env` * `feedback`: Manage user feedback for your agents ### `agentstack agent add` Add a docker image or GitHub repository. \[Admin only] This command supports a variety of GitHub URL formats for deploying agents: * **Basic URL**: `https://github.com/myorg/myrepo` * **Git Protocol URL**: `git+https://github.com/myorg/myrepo` * **URL with .git suffix**: `https://github.com/myorg/myrepo.git` * **URL with Version Tag**: `https://github.com/myorg/myrepo@v1.0.0` * **URL with Branch Name**: `https://github.com/myorg/myrepo@my-branch` * **URL with Subfolder Path**: `https://github.com/myorg/myrepo#path=/path/to/agent` * **Combined Formats**: `https://github.com/myorg/myrepo.git@v1.0.0#path=/path/to/agent` * **Enterprise GitHub**: `https://github.mycompany.com/myorg/myrepo` * **With a custom Dockerfile location**: `agentstack add --dockerfile /my-agent/path/to/Dockerfile "https://github.com/my-org/my-awesome-agents@main#path=/my-agent"` **Usage**: ```console theme={null} $ agentstack agent add [OPTIONS] [LOCATION] ``` **Arguments**: * `[LOCATION]`: Agent location (public docker image or github url) **Options**: * `--dockerfile TEXT`: Use custom dockerfile path * `-v, --verbose`: Show verbose output * `-y, --yes`: Skip confirmation prompts. * `--help`: Show this message and exit. ### `agentstack agent update` Upgrade agent to a newer docker image or build from GitHub repository. \[Admin only] **Usage**: ```console theme={null} $ agentstack agent update [OPTIONS] [SEARCH_PATH] [LOCATION] ``` **Arguments**: * `[SEARCH_PATH]`: Short ID, agent name or part of the provider location of agent to replace * `[LOCATION]`: Agent location (public docker image or github url) **Options**: * `--dockerfile TEXT`: Use custom dockerfile path * `-v, --verbose`: Show verbose output * `-y, --yes`: Skip confirmation prompts. * `--help`: Show this message and exit. ### `agentstack agent remove | uninstall | rm | delete` Remove agent. \[Admin only] **Usage**: ```console theme={null} $ agentstack agent remove | uninstall | rm | delete [OPTIONS] [SEARCH_PATH] ``` **Arguments**: * `[SEARCH_PATH]`: Short ID, agent name or part of the provider location **Options**: * `-y, --yes`: Skip confirmation prompts. * `-a, --all`: Remove all agents without selection. * `--help`: Show this message and exit. ### `agentstack agent logs` Stream agent provider logs. \[Admin only] **Usage**: ```console theme={null} $ agentstack agent logs [OPTIONS] SEARCH_PATH ``` **Arguments**: * `SEARCH_PATH`: Short ID, agent name or part of the provider location \[required] **Options**: * `--help`: Show this message and exit. ### `agentstack agent run` Run an agent. **Usage**: ```console theme={null} $ agentstack agent run [OPTIONS] [SEARCH_PATH] [INPUT] ``` **Arguments**: * `[SEARCH_PATH]`: Short ID, agent name or part of the provider location * `[INPUT]`: Agent input as text or JSON **Options**: * `--dump-files PATH`: Folder path to save any files returned by the agent * `--help`: Show this message and exit. ### `agentstack agent list` List agents. **Usage**: ```console theme={null} $ agentstack agent list [OPTIONS] ``` **Options**: * `--help`: Show this message and exit. ### `agentstack agent info` Show agent details. **Usage**: ```console theme={null} $ agentstack agent info [OPTIONS] SEARCH_PATH ``` **Arguments**: * `SEARCH_PATH`: Short ID, agent name or part of the provider location \[required] **Options**: * `--help`: Show this message and exit. ### `agentstack agent env` **Usage**: ```console theme={null} $ agentstack agent env [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--help`: Show this message and exit. **Commands**: * `add`: Store environment variables. * `list`: List stored environment variables. * `remove`: Remove environment variable(s). #### `agentstack agent env add` Store environment variables. \[Admin only] **Usage**: ```console theme={null} $ agentstack agent env add [OPTIONS] SEARCH_PATH ENV... ``` **Arguments**: * `SEARCH_PATH`: Short ID, agent name or part of the provider location \[required] * `ENV...`: Environment variables to pass to agent \[required] **Options**: * `-y, --yes`: Skip confirmation prompts. * `--help`: Show this message and exit. #### `agentstack agent env list` List stored environment variables. \[Admin only] **Usage**: ```console theme={null} $ agentstack agent env list [OPTIONS] SEARCH_PATH ``` **Arguments**: * `SEARCH_PATH`: Short ID, agent name or part of the provider location \[required] **Options**: * `--help`: Show this message and exit. #### `agentstack agent env remove` Remove environment variable(s). \[Admin only] **Usage**: ```console theme={null} $ agentstack agent env remove [OPTIONS] SEARCH_PATH ENV... ``` **Arguments**: * `SEARCH_PATH`: Short ID, agent name or part of the provider location \[required] * `ENV...`: Environment variable(s) to remove \[required] **Options**: * `-y, --yes`: Skip confirmation prompts. * `--help`: Show this message and exit. ### `agentstack agent feedback` Manage user feedback for your agents **Usage**: ```console theme={null} $ agentstack agent feedback [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--help`: Show this message and exit. **Commands**: * `list`: List your agent feedback. #### `agentstack agent feedback list` List your agent feedback. \[Admin only] **Usage**: ```console theme={null} $ agentstack agent feedback list [OPTIONS] [SEARCH_PATH] ``` **Arguments**: * `[SEARCH_PATH]`: Short ID, agent name or part of the provider location **Options**: * `--limit INTEGER`: Number of results per page \[default: 50] * `--after TEXT`: Cursor for pagination * `--help`: Show this message and exit. ## `agentstack connector` Manage connectors to external services. **Usage**: ```console theme={null} $ agentstack connector [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--help`: Show this message and exit. **Commands**: * `create`: Create a connector to an external service. * `remove | rm | delete`: Remove connectors. * `list`: List all connectors. * `list-presets`: List connector presets. * `get`: Get connector details. * `connect`: Connect a connector (e.g., start OAuth flow). * `disconnect`: Disconnect one or more connectors. ### `agentstack connector create` Create a connector to an external service. **Usage**: ```console theme={null} $ agentstack connector create [OPTIONS] URL ``` **Arguments**: * `URL`: Agent location (public docker image or github url) \[required] **Options**: * `--client-id TEXT`: Client ID for authentication, acquired from env if not supplied * `--client-secret TEXT`: Client secret for authentication, acquired from env if not supplied * `--metadata TEXT`: Metadata as JSON string * `--match-preset`: Use preset configuration for given url if it exists \[default: True] * `--help`: Show this message and exit. ### `agentstack connector remove | rm | delete` Remove connectors. **Usage**: ```console theme={null} $ agentstack connector remove | rm | delete [OPTIONS] [SEARCH_PATH] ``` **Arguments**: * `[SEARCH_PATH]`: Short ID or connector url, supports partial matching **Options**: * `-y, --yes`: Skip confirmation prompts. * `-a, --all`: Remove all connectors without selection. * `--help`: Show this message and exit. ### `agentstack connector list` List all connectors. **Usage**: ```console theme={null} $ agentstack connector list [OPTIONS] ``` **Options**: * `--help`: Show this message and exit. ### `agentstack connector list-presets` List connector presets. **Usage**: ```console theme={null} $ agentstack connector list-presets [OPTIONS] ``` **Options**: * `--help`: Show this message and exit. ### `agentstack connector get` Get connector details. **Usage**: ```console theme={null} $ agentstack connector get [OPTIONS] SEARCH_PATH ``` **Arguments**: * `SEARCH_PATH`: Short ID or connector url, supports partial matching \[required] **Options**: * `--help`: Show this message and exit. ### `agentstack connector connect` Connect a connector (e.g., start OAuth flow). **Usage**: ```console theme={null} $ agentstack connector connect [OPTIONS] SEARCH_PATH ``` **Arguments**: * `SEARCH_PATH`: Short ID or connector url, supports partial matching \[required] **Options**: * `--help`: Show this message and exit. ### `agentstack connector disconnect` Disconnect one or more connectors. **Usage**: ```console theme={null} $ agentstack connector disconnect [OPTIONS] [SEARCH_PATH] ``` **Arguments**: * `[SEARCH_PATH]`: Short ID or connector url, supports partial matching **Options**: * `-y, --yes`: Skip confirmation prompts. * `-a, --all`: Deisconnect all connectors without selection. * `--help`: Show this message and exit. ## `agentstack platform` Manage Agent Stack platform. \[Local only] **Usage**: ```console theme={null} $ agentstack platform [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--help`: Show this message and exit. **Commands**: * `start`: Start Agent Stack platform. * `stop`: Stop Agent Stack platform. * `delete`: Delete Agent Stack platform. * `import`: Import a local docker image into the Agent... * `exec`: For debugging -- execute a command inside... ### `agentstack platform start` Start Agent Stack platform. \[Local only] **Usage**: ```console theme={null} $ agentstack platform start [OPTIONS] ``` **Options**: * `--set TEXT`: Set Helm chart values using \=\ syntax \[default: \] * `--image-pull-mode [guest|host|hybrid|skip]`: guest = pull all images inside VM host = pull unavailable images on host, then import all hybrid = import available images from host, pull the rest in VM skip = skip explicit pull step (Kubernetes will attempt to pull missing images) \[default: guest] * `-f PATH`: Set Helm chart values using yaml values file * `--lima-image TEXT`: Local path or URL to Lima image (.qcow2) * `--wsl-image TEXT`: Local path or URL to WSL distro image (.wsl) * `-v, --verbose`: Show verbose output * `--help`: Show this message and exit. ### `agentstack platform stop` Stop Agent Stack platform. \[Local only] **Usage**: ```console theme={null} $ agentstack platform stop [OPTIONS] ``` **Options**: * `-v, --verbose`: Show verbose output * `--help`: Show this message and exit. ### `agentstack platform delete` Delete Agent Stack platform. \[Local only] **Usage**: ```console theme={null} $ agentstack platform delete [OPTIONS] ``` **Options**: * `-v, --verbose`: Show verbose output * `--help`: Show this message and exit. ### `agentstack platform import` Import a local docker image into the Agent Stack platform. \[Local only] **Usage**: ```console theme={null} $ agentstack platform import [OPTIONS] TAG ``` **Arguments**: * `TAG`: Docker image tag to import \[required] **Options**: * `-v, --verbose`: Show verbose output * `--mode [daemon|registry]`: \[default: daemon] * `--help`: Show this message and exit. ### `agentstack platform exec` For debugging -- execute a command inside the Agent Stack platform VM. \[Local only] **Usage**: ```console theme={null} $ agentstack platform exec [OPTIONS] [COMMAND]... ``` **Arguments**: * `[COMMAND]...` **Options**: * `-v, --verbose`: Show verbose output * `--help`: Show this message and exit. ## `agentstack client-side-build` Build agent locally using Docker. \[Local only] **Usage**: ```console theme={null} $ agentstack client-side-build [OPTIONS] [CONTEXT] ``` **Arguments**: * `[CONTEXT]`: Docker context for the agent \[default: .] **Options**: * `--dockerfile TEXT`: Use custom dockerfile path * `--tag TEXT`: Docker tag for the agent * `--multi-platform / --no-multi-platform`: \[default: no-multi-platform] * `--push / --no-push`: Push the image to the target registry. \[default: no-push] * `--import / --no-import`: Import the image into Agent Stack platform \[default: import] * `--extract-agent-card / --no-extract-agent-card`: Extract agent card from running container \[default: extract-agent-card] * `-v, --verbose`: Show verbose output * `--help`: Show this message and exit. ## `agentstack build` Build agent from a GitHub repository in the platform. \[Admin only] **Usage**: ```console theme={null} $ agentstack build [OPTIONS] GITHUB_URL ``` **Arguments**: * `GITHUB_URL`: Github repository URL (public or private if supported by the platform instance) \[required] **Options**: * `--dockerfile TEXT`: Use custom dockerfile path, relative to github url sub-path * `-v, --verbose`: Show verbose output * `-y, --yes`: Skip confirmation prompts. * `--help`: Show this message and exit. ## `agentstack server` Manage Agent Stack servers and authentication. **Usage**: ```console theme={null} $ agentstack server [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--help`: Show this message and exit. **Commands**: * `login | change | select | default | switch`: Login to a server or switch between logged... * `logout | remove | rm | delete` * `show` * `list` ### `agentstack server login | change | select | default | switch` Login to a server or switch between logged in servers. **Usage**: ```console theme={null} $ agentstack server login | change | select | default | switch [OPTIONS] [SERVER] ``` **Arguments**: * `[SERVER]` **Options**: * `--client-id TEXT`: OAuth client ID (skips interactive prompt) * `--client-secret TEXT`: OAuth client secret (skips interactive prompt) * `--auth-server TEXT`: Authorization server URL (skips interactive selection) * `--help`: Show this message and exit. ### `agentstack server logout | remove | rm | delete` **Usage**: ```console theme={null} $ agentstack server logout | remove | rm | delete [OPTIONS] ``` **Options**: * `--all / --no-all`: \[default: no-all] * `--help`: Show this message and exit. ### `agentstack server show` **Usage**: ```console theme={null} $ agentstack server show [OPTIONS] ``` **Options**: * `--help`: Show this message and exit. ### `agentstack server list` **Usage**: ```console theme={null} $ agentstack server list [OPTIONS] ``` **Options**: * `--help`: Show this message and exit. ## `agentstack self` Manage Agent Stack installation. **Usage**: ```console theme={null} $ agentstack self [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--help`: Show this message and exit. **Commands**: * `version`: Print version of the Agent Stack CLI. * `install`: Install Agent Stack platform pre-requisites. * `upgrade`: Upgrade Agent Stack CLI and Platform to... * `uninstall`: Uninstall Agent Stack CLI and Platform. ### `agentstack self version` Print version of the Agent Stack CLI. **Usage**: ```console theme={null} $ agentstack self version [OPTIONS] ``` **Options**: * `-v, --verbose`: Show verbose output * `--help`: Show this message and exit. ### `agentstack self install` Install Agent Stack platform pre-requisites. **Usage**: ```console theme={null} $ agentstack self install [OPTIONS] ``` **Options**: * `-v, --verbose`: Show verbose output * `-y, --yes`: Skip confirmation prompts * `--help`: Show this message and exit. ### `agentstack self upgrade` Upgrade Agent Stack CLI and Platform to the latest version. **Usage**: ```console theme={null} $ agentstack self upgrade [OPTIONS] ``` **Options**: * `-v, --verbose`: Show verbose output * `--help`: Show this message and exit. ### `agentstack self uninstall` Uninstall Agent Stack CLI and Platform. **Usage**: ```console theme={null} $ agentstack self uninstall [OPTIONS] ``` **Options**: * `-v, --verbose`: Show verbose output * `--help`: Show this message and exit. ## `agentstack add` Add a docker image or GitHub repository. \[Admin only] This command supports a variety of GitHub URL formats for deploying agents: * **Basic URL**: `https://github.com/myorg/myrepo` * **Git Protocol URL**: `git+https://github.com/myorg/myrepo` * **URL with .git suffix**: `https://github.com/myorg/myrepo.git` * **URL with Version Tag**: `https://github.com/myorg/myrepo@v1.0.0` * **URL with Branch Name**: `https://github.com/myorg/myrepo@my-branch` * **URL with Subfolder Path**: `https://github.com/myorg/myrepo#path=/path/to/agent` * **Combined Formats**: `https://github.com/myorg/myrepo.git@v1.0.0#path=/path/to/agent` * **Enterprise GitHub**: `https://github.mycompany.com/myorg/myrepo` * **With a custom Dockerfile location**: `agentstack add --dockerfile /my-agent/path/to/Dockerfile "https://github.com/my-org/my-awesome-agents@main#path=/my-agent"` **Usage**: ```console theme={null} $ agentstack add [OPTIONS] [LOCATION] ``` **Arguments**: * `[LOCATION]`: Agent location (public docker image or github url) **Options**: * `--dockerfile TEXT`: Use custom dockerfile path * `-v, --verbose`: Show verbose output * `-y, --yes`: Skip confirmation prompts. * `--help`: Show this message and exit. ## `agentstack update` Upgrade agent to a newer docker image or build from GitHub repository. \[Admin only] **Usage**: ```console theme={null} $ agentstack update [OPTIONS] [SEARCH_PATH] [LOCATION] ``` **Arguments**: * `[SEARCH_PATH]`: Short ID, agent name or part of the provider location of agent to replace * `[LOCATION]`: Agent location (public docker image or github url) **Options**: * `--dockerfile TEXT`: Use custom dockerfile path * `-v, --verbose`: Show verbose output * `-y, --yes`: Skip confirmation prompts. * `--help`: Show this message and exit. ## `agentstack remove | uninstall | rm | delete` Remove agent. \[Admin only] **Usage**: ```console theme={null} $ agentstack remove | uninstall | rm | delete [OPTIONS] [SEARCH_PATH] ``` **Arguments**: * `[SEARCH_PATH]`: Short ID, agent name or part of the provider location **Options**: * `-y, --yes`: Skip confirmation prompts. * `-a, --all`: Remove all agents without selection. * `--help`: Show this message and exit. ## `agentstack logs` Stream agent provider logs. \[Admin only] **Usage**: ```console theme={null} $ agentstack logs [OPTIONS] SEARCH_PATH ``` **Arguments**: * `SEARCH_PATH`: Short ID, agent name or part of the provider location \[required] **Options**: * `--help`: Show this message and exit. ## `agentstack run` Run an agent. **Usage**: ```console theme={null} $ agentstack run [OPTIONS] [SEARCH_PATH] [INPUT] ``` **Arguments**: * `[SEARCH_PATH]`: Short ID, agent name or part of the provider location * `[INPUT]`: Agent input as text or JSON **Options**: * `--dump-files PATH`: Folder path to save any files returned by the agent * `--help`: Show this message and exit. ## `agentstack list` List agents. **Usage**: ```console theme={null} $ agentstack list [OPTIONS] ``` **Options**: * `--help`: Show this message and exit. ## `agentstack info` Show agent details. **Usage**: ```console theme={null} $ agentstack info [OPTIONS] SEARCH_PATH ``` **Arguments**: * `SEARCH_PATH`: Short ID, agent name or part of the provider location \[required] **Options**: * `--help`: Show this message and exit. ## `agentstack env` **Usage**: ```console theme={null} $ agentstack env [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--help`: Show this message and exit. **Commands**: * `add`: Store environment variables. * `list`: List stored environment variables. * `remove`: Remove environment variable(s). ### `agentstack env add` Store environment variables. \[Admin only] **Usage**: ```console theme={null} $ agentstack env add [OPTIONS] SEARCH_PATH ENV... ``` **Arguments**: * `SEARCH_PATH`: Short ID, agent name or part of the provider location \[required] * `ENV...`: Environment variables to pass to agent \[required] **Options**: * `-y, --yes`: Skip confirmation prompts. * `--help`: Show this message and exit. ### `agentstack env list` List stored environment variables. \[Admin only] **Usage**: ```console theme={null} $ agentstack env list [OPTIONS] SEARCH_PATH ``` **Arguments**: * `SEARCH_PATH`: Short ID, agent name or part of the provider location \[required] **Options**: * `--help`: Show this message and exit. ### `agentstack env remove` Remove environment variable(s). \[Admin only] **Usage**: ```console theme={null} $ agentstack env remove [OPTIONS] SEARCH_PATH ENV... ``` **Arguments**: * `SEARCH_PATH`: Short ID, agent name or part of the provider location \[required] * `ENV...`: Environment variable(s) to remove \[required] **Options**: * `-y, --yes`: Skip confirmation prompts. * `--help`: Show this message and exit. ## `agentstack feedback` Manage user feedback for your agents **Usage**: ```console theme={null} $ agentstack feedback [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--help`: Show this message and exit. **Commands**: * `list`: List your agent feedback. ### `agentstack feedback list` List your agent feedback. \[Admin only] **Usage**: ```console theme={null} $ agentstack feedback list [OPTIONS] [SEARCH_PATH] ``` **Arguments**: * `[SEARCH_PATH]`: Short ID, agent name or part of the provider location **Options**: * `--limit INTEGER`: Number of results per page \[default: 50] * `--after TEXT`: Cursor for pagination * `--help`: Show this message and exit. # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport /development/api-reference/openapi.json get /api/v1/a2a/{provider_id}/http/{path} # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-1 /development/api-reference/openapi.json put /api/v1/a2a/{provider_id}/http/{path} # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-10 /development/api-reference/openapi.json delete /api/v1/a2a/{provider_id}/http # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-11 /development/api-reference/openapi.json options /api/v1/a2a/{provider_id}/http # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-12 /development/api-reference/openapi.json head /api/v1/a2a/{provider_id}/http # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-13 /development/api-reference/openapi.json patch /api/v1/a2a/{provider_id}/http # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-2 /development/api-reference/openapi.json post /api/v1/a2a/{provider_id}/http/{path} # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-3 /development/api-reference/openapi.json delete /api/v1/a2a/{provider_id}/http/{path} # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-4 /development/api-reference/openapi.json options /api/v1/a2a/{provider_id}/http/{path} # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-5 /development/api-reference/openapi.json head /api/v1/a2a/{provider_id}/http/{path} # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-6 /development/api-reference/openapi.json patch /api/v1/a2a/{provider_id}/http/{path} # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-7 /development/api-reference/openapi.json get /api/v1/a2a/{provider_id}/http # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-8 /development/api-reference/openapi.json put /api/v1/a2a/{provider_id}/http # A2A Proxy Http Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-http-transport-9 /development/api-reference/openapi.json post /api/v1/a2a/{provider_id}/http # A2A Proxy Jsonrpc Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-jsonrpc-transport /development/api-reference/openapi.json post /api/v1/a2a/{provider_id}/ # A2A Proxy Jsonrpc Transport Source: https://agentstack.beeai.dev/api-reference/a2a/a2a-proxy-jsonrpc-transport-1 /development/api-reference/openapi.json post /api/v1/a2a/{provider_id} # Get Agent Card Source: https://agentstack.beeai.dev/api-reference/a2a/get-agent-card /development/api-reference/openapi.json get /api/v1/a2a/{provider_id}/.well-known/agent-card.json # Jwks Source: https://agentstack.beeai.dev/api-reference/auth/jwks /development/api-reference/openapi.json get /.well-known/jwks # Protected Resource Metadata Source: https://agentstack.beeai.dev/api-reference/auth/protected-resource-metadata /development/api-reference/openapi.json get /.well-known/oauth-protected-resource/{resource} # Get Configuration Source: https://agentstack.beeai.dev/api-reference/configurations/get-configuration /development/api-reference/openapi.json get /api/v1/configurations/system # Update Configuration Source: https://agentstack.beeai.dev/api-reference/configurations/update-configuration /development/api-reference/openapi.json put /api/v1/configurations/system # Connect Connector Source: https://agentstack.beeai.dev/api-reference/connectors/connect-connector /development/api-reference/openapi.json post /api/v1/connectors/{connector_id}/connect # Create Connector Source: https://agentstack.beeai.dev/api-reference/connectors/create-connector /development/api-reference/openapi.json post /api/v1/connectors # Delete Connector Source: https://agentstack.beeai.dev/api-reference/connectors/delete-connector /development/api-reference/openapi.json delete /api/v1/connectors/{connector_id} # Disconnect Connector Source: https://agentstack.beeai.dev/api-reference/connectors/disconnect-connector /development/api-reference/openapi.json post /api/v1/connectors/{connector_id}/disconnect # List Connectors Source: https://agentstack.beeai.dev/api-reference/connectors/list-connectors /development/api-reference/openapi.json get /api/v1/connectors # List Presets Source: https://agentstack.beeai.dev/api-reference/connectors/list-presets /development/api-reference/openapi.json get /api/v1/connectors/presets # Mcp Source: https://agentstack.beeai.dev/api-reference/connectors/mcp /development/api-reference/openapi.json get /api/v1/connectors/{connector_id}/mcp # Mcp Source: https://agentstack.beeai.dev/api-reference/connectors/mcp-1 /development/api-reference/openapi.json post /api/v1/connectors/{connector_id}/mcp # Oauth Callback Source: https://agentstack.beeai.dev/api-reference/connectors/oauth-callback /development/api-reference/openapi.json get /api/v1/connectors/oauth/callback # Read Connector Source: https://agentstack.beeai.dev/api-reference/connectors/read-connector /development/api-reference/openapi.json get /api/v1/connectors/{connector_id} # List Files Source: https://agentstack.beeai.dev/api-reference/files/list-files /development/api-reference/openapi.json get /api/v1/files # Upload File Source: https://agentstack.beeai.dev/api-reference/files/upload-file /development/api-reference/openapi.json post /api/v1/files # Create Model Provider Source: https://agentstack.beeai.dev/api-reference/model_providers/create-model-provider /development/api-reference/openapi.json post /api/v1/model_providers # Delete Model Provider Source: https://agentstack.beeai.dev/api-reference/model_providers/delete-model-provider /development/api-reference/openapi.json delete /api/v1/model_providers/{model_provider_id} # Get Model Provider Source: https://agentstack.beeai.dev/api-reference/model_providers/get-model-provider /development/api-reference/openapi.json get /api/v1/model_providers/{model_provider_id} # List Model Providers Source: https://agentstack.beeai.dev/api-reference/model_providers/list-model-providers /development/api-reference/openapi.json get /api/v1/model_providers # Match Source: https://agentstack.beeai.dev/api-reference/model_providers/match /development/api-reference/openapi.json post /api/v1/model_providers/match # Create Provider Build Source: https://agentstack.beeai.dev/api-reference/provider_builds/create-provider-build /development/api-reference/openapi.json post /api/v1/provider_builds # Delete Source: https://agentstack.beeai.dev/api-reference/provider_builds/delete /development/api-reference/openapi.json delete /api/v1/provider_builds/{id} # Get Provider Build Source: https://agentstack.beeai.dev/api-reference/provider_builds/get-provider-build /development/api-reference/openapi.json get /api/v1/provider_builds/{id} # List Provider Builds Source: https://agentstack.beeai.dev/api-reference/provider_builds/list-provider-builds /development/api-reference/openapi.json get /api/v1/provider_builds # Preview Provider Build Source: https://agentstack.beeai.dev/api-reference/provider_builds/preview-provider-build /development/api-reference/openapi.json post /api/v1/provider_builds/preview # Stream Logs Source: https://agentstack.beeai.dev/api-reference/provider_builds/stream-logs /development/api-reference/openapi.json get /api/v1/provider_builds/{id}/logs # Create Provider Discovery Source: https://agentstack.beeai.dev/api-reference/provider_discovery/create-provider-discovery /development/api-reference/openapi.json post /api/v1/providers/discovery # Get Provider Discovery Source: https://agentstack.beeai.dev/api-reference/provider_discovery/get-provider-discovery /development/api-reference/openapi.json get /api/v1/providers/discovery/{id} # Create Provider Source: https://agentstack.beeai.dev/api-reference/providers/create-provider /development/api-reference/openapi.json post /api/v1/providers # Delete Provider Source: https://agentstack.beeai.dev/api-reference/providers/delete-provider /development/api-reference/openapi.json delete /api/v1/providers/{id} # Get Provider Source: https://agentstack.beeai.dev/api-reference/providers/get-provider /development/api-reference/openapi.json get /api/v1/providers/{id} # Get Provider By Location Source: https://agentstack.beeai.dev/api-reference/providers/get-provider-by-location /development/api-reference/openapi.json get /api/v1/providers/by-location/{location} # List Provider Variables Source: https://agentstack.beeai.dev/api-reference/providers/list-provider-variables /development/api-reference/openapi.json get /api/v1/providers/{id}/variables # List Providers Source: https://agentstack.beeai.dev/api-reference/providers/list-providers /development/api-reference/openapi.json get /api/v1/providers # Patch Provider Source: https://agentstack.beeai.dev/api-reference/providers/patch-provider /development/api-reference/openapi.json patch /api/v1/providers/{id} # Preview Provider Source: https://agentstack.beeai.dev/api-reference/providers/preview-provider /development/api-reference/openapi.json post /api/v1/providers/preview # Stream Logs Source: https://agentstack.beeai.dev/api-reference/providers/stream-logs /development/api-reference/openapi.json get /api/v1/providers/{id}/logs # Update Provider Variables Source: https://agentstack.beeai.dev/api-reference/providers/update-provider-variables /development/api-reference/openapi.json put /api/v1/providers/{id}/variables # Get User Source: https://agentstack.beeai.dev/api-reference/user/get-user /development/api-reference/openapi.json get /api/v1/user # List User Feedback Source: https://agentstack.beeai.dev/api-reference/user_feedback/list-user-feedback /development/api-reference/openapi.json get /api/v1/user_feedback # User Feedback Source: https://agentstack.beeai.dev/api-reference/user_feedback/user-feedback /development/api-reference/openapi.json post /api/v1/user_feedback # Get Me Source: https://agentstack.beeai.dev/api-reference/users/get-me /development/api-reference/openapi.json get /api/v1/users/me Get current user # Contribute Source: https://agentstack.beeai.dev/stable/community/contribute Learn how to contribute to Agent Stack Welcome! We’re so glad you’re here. Whether you're fixing a typo, filing a bug, or building something brand new, your contributions help shape Agent Stack into something better for everyone. ## What We Value & How We Work Agent Stack is a Linux Foundation project. This means we are committed to a neutral, sustainable, and truly open ecosystem. Because we are a community-led project, our growth and success depend entirely on the people who use, test, and build it. We care about: * Making real problems easier to solve * Sharing early and learning together * Designing tools that are easy for others to build on * Keeping interactions kind, intentional, and inclusive We are async-friendly and value thoughtful collaboration. When contributing, keep these principles in mind: * Share your progress early to get feedback before diving deep * Focus on the problem you’re solving, not just the code * Give thoughtful feedback and expect the same in return * Prioritize clarity, empathy, and user impact ## How to Get Involved You don’t need to be an AI expert to help. Here’s how you can make impact today: * Use Agent Stack and tell us what works (and what doesn’t) * Open an issue for bugs, questions, or new ideas * Share feedback on discussions or pull requests * Propose changes — small bug fixes are just as valuable as new features * Improve docs by clarifying examples, fixing typos, or writing tutorials ## Getting Started Not sure where to begin? * Start with our [Contributing Guide](https://github.com/i-am-bee/agentstack/blob/main/CONTRIBUTING.md) * Browse [Good first issues](https://github.com/i-am-bee/agentstack/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) to find approachable tasks to get your feet wet * Join the conversation on [Discord](https://discord.gg/NradeA6ZNF) - questions, ideas, and curiosity welcome Don't hesitate to reach out - we’re here to help you get started. ## Thanks for Being Here We’re grateful for your time, your ideas, and your effort to help make Agent Stack better. If you get stuck, feel free to [open an issue](https://github.com/i-am-bee/agentstack/issues) or reach out to the team on [Discord](https://discord.gg/NradeA6ZNF)! # A2A Client Integration Source: https://agentstack.beeai.dev/stable/custom-ui/a2a-client Create the A2A client, fulfill agent demands, and handle streaming events To communicate with agents, you use the A2A protocol client to send messages and receive streaming events. The Agent Stack SDK provides helpers that turn those events into UI updates. This guide shows how to integrate `@a2a-js/sdk` with the Agent Stack SDK helpers, mirroring the same flow used in `agentstack-ui`. If you only need the fast path, start with **[Getting Started](./getting-started)**. ## Prereqs * **Packages installed**: `agentstack-sdk` and `@a2a-js/sdk` * **Platform base URL** and **provider ID** * **CORS enabled on the server** for your frontend origin when running in a browser (or use a same-origin proxy). See **[Cross-Origin Resource Sharing Configuration](../deploy-agent-stack/deployment-guide#cross-origin-resource-sharing-configuration)**. * **User access token** (for platform API calls) * **Context token** (for A2A requests) ## Quick Start recap * Create a platform API client with the user access token. * List providers, pick a `providerId`, then create a `context` and `contextToken`. * Create the A2A client with the `contextToken` and start a message stream. The advanced sections assume you already have `client`, `context`, and `contextToken` from the quick start. If not, see **[Getting Started](./getting-started)**. ## Advanced guide In the next steps, you’ll wire up a full A2A message flow: resolve initial agent demands, start a streaming task, handle UI‑driven updates (like forms), and submit follow‑ups on the same task. The snippets are intentionally minimal but map directly to a real client implementation. ### 1. Read the agent card and resolve demands Fetch the agent card, inspect demands, and build the **initial** fulfillments. To keep the rest of this guide working without extra complexity, focus on the most common requirement: **LLM access** via the platform OpenAI‑compatible proxy. We map each LLM demand key to a model the user picked in your UI, then resolve the metadata that gets attached to your first message. ```typescript theme={null} import { handleAgentCard, type Fulfillments } from "agentstack-sdk"; const card = await client.getAgentCard(); const { resolveMetadata, demands } = handleAgentCard(card); // Example: model picker state keyed by demand id. const selectedLlmModels: Record = { default: "gpt-4o", }; const fulfillments: Fulfillments = { llm: demands.llmDemands ? async ({ llm_demands }) => ({ llm_fulfillments: Object.fromEntries( Object.keys(llm_demands).map((key) => [ key, { identifier: "llm_proxy", api_base: "{platform_url}/api/v1/openai/", api_key: contextToken.token, api_model: selectedLlmModels[key], }, ]), ), }) : undefined, }; const agentMetadata = await resolveMetadata(fulfillments); ``` See **[Agent Requirements](./agent-requirements)** for the available service and UI extension helpers. ### 2. Send the initial message stream Start a task by sending the user prompt that triggers the project brief flow. ```typescript theme={null} const stream = client.sendMessageStream({ message: { kind: "message", role: "user", messageId: crypto.randomUUID(), contextId: context.id, parts: [ { kind: "text", text: "Draft a short project brief for a new onboarding flow. Ask me for any details you need.", }, ], metadata: agentMetadata, }, }); ``` ### 3. Handle streaming updates and show the form Use `handleTaskStatusUpdate` to detect form requests and capture the task ID so you can continue the same task. Render streamed output from `status-update` events, and keep `message` as a fallback. ```typescript theme={null} import { handleTaskStatusUpdate, type TaskStatusUpdateType } from "agentstack-sdk"; let taskId: string | undefined; for await (const event of stream) { if (event.kind === "task") { taskId = event.id; } if (event.kind === "status-update") { taskId = event.taskId; // UI actions like forms, approvals, secrets, OAuth for (const update of handleTaskStatusUpdate(event)) { if (update.type === TaskStatusUpdateType.FormRequired) { renderForm(update.form); } } // Streaming agent output usually arrives here if (event.status.message) { renderMessage(event.status.message.parts, event.status.message.metadata); } } // Fallback for non-streaming agents or final messages if (event.kind === "message") { renderMessage(event.parts, event.metadata); } } ``` **Why task status updates for chat streaming** 1. A2A uses tasks to represent long running work so you can track progress and cancel. 2. Status updates provide the incremental channel for UI events and streaming output. 3. Each streamed token typically arrives as a `TaskStatusUpdateEvent` in Agent Stack, so you can append text as it arrives. For rendering message parts and metadata (such as citations), see **[Agent Responses](./agent-responses)**. ### 4. Submit the form and continue the task Convert the user responses into A2A metadata and send a follow-up message for the same task. ```typescript theme={null} import { resolveUserMetadata } from "agentstack-sdk"; // Form values follow the SDK schemas: each field is { type, value }. const formValues = { project_name: { type: "text", value: "New user onboarding" }, channels: { type: "multiselect", value: ["web", "mobile"] }, deadline: { type: "date", value: "2026-03-01" }, legal_review: { type: "checkbox", value: true }, }; const userMetadata = await resolveUserMetadata({ form: formValues, }); const responseStream = client.sendMessageStream({ message: { kind: "message", role: "user", messageId: crypto.randomUUID(), contextId: context.id, taskId, parts: [{ kind: "text", text: "Here are the project details." }], metadata: { ...agentMetadata, ...userMetadata }, }, }); ``` **User metadata** User metadata is the structured response payload that you attach to a message. It is separate from text parts and lets the agent consume form fields, approvals, and canvas actions in a typed way. Include `taskId` when responding to an in progress task. Omit it when starting a new task. For a focused look at composing messages, see **[User Messages](./user-messages)**. ### 5. Render the final output and artifacts Stream the response and render the final message and any artifacts. ```typescript theme={null} for await (const event of responseStream) { if (event.kind === "artifact-update") { renderArtifact(event.artifact); } if (event.kind === "message") { renderMessage(event.parts, event.metadata); } } ``` ### 6. Cancel a running task If a user clicks “Stop”, cancel the current task by ID. You’ll still receive a final status update with state `canceled`, so update your UI and stop streaming when you see it. ```typescript theme={null} const cancel = async () => { if (!taskId) return; await client.cancelTask({ id: taskId }); }; for await (const event of responseStream) { if (event.kind === "status-update" && event.status.state === "canceled") { renderCanceledState(); break; } // ...handle other events } ``` ## Basic concepts: tasks, status updates, and streaming output Agent Stack’s A2A streaming is task-based. A single `sendMessageStream` call can yield several event shapes, and you should handle all of them: * `task`: emits the initial Task object. Capture `task.id`, and use `task.status` (and optional `history`/`artifacts`) to seed your UI state. * `status-update`: emits a task status transition. When the agent is streaming, incremental output is typically delivered via `event.status.message` (a `Message` payload attached to the status). * `artifact-update`: emits artifacts as they are generated or updated (useful for streamed files, canvases, or structured outputs). * `message`: emits a standalone Message. This is common for non‑streaming agents and may also appear as a final response. Key detail for Agent Stack streaming: **incremental output usually arrives inside `status-update` events** (`event.status.message`). If you only render `message` events, you may miss streamed output. ## Handling failed states Tasks can fail at any point during streaming. Failed or rejected updates arrive as `status-update` events with `event.status.state` set to `failed` or `rejected`. In those cases: * Read structured error metadata from the status message (if present). * Fall back to a generic error message if no metadata is provided. * Update the UI to a terminal error state and stop streaming. ```typescript theme={null} import { errorExtension, extractUiExtensionData } from "agentstack-sdk"; const readError = extractUiExtensionData(errorExtension); for await (const event of responseStream) { if (event.kind === "status-update") { const state = event.status.state; if (state === "failed" || state === "rejected") { const errorMetadata = readError(event.status.message?.metadata); renderError(errorMetadata?.message ?? "Agent error"); break; } } } ``` For more about error handling, see **[Error Handling](./error-handling)**. ## Common pitfalls * **Wrong token in A2A requests**: use the *context token* for A2A fetches, not the user access token. * **Missing metadata merge**: merge agent card fulfillments with user metadata when you send responses. * **Streaming text handling**: status updates can include partial text; append incrementally. * **Ignoring status updates**: streamed agent output typically arrives via `status-update` events (in `event.status.message`), not `message`. * **Node fetch missing**: Node \< 18 requires a `fetch` polyfill or custom `fetch` passed to the API client. # Agent Requirements Source: https://agentstack.beeai.dev/stable/custom-ui/agent-requirements Fulfill agent service demands and render UI extensions in your custom interface Agent Stack extends the [A2A Protocol](https://a2a-protocol.org/) with [A2A extensions](https://a2a-protocol.org/latest/topics/extensions/) that enable agents to access platform services and enhance the user interface beyond what the base protocol provides. Extensions fall into two categories: 1. **Service extensions:** Demands in the agent card that your client fulfills (LLMs, embeddings, OAuth, secrets) 2. **UI extensions:** Message metadata your UI renders (forms, approvals, citations, trajectories) All extension definitions are exported from `agentstack-sdk/extensions` if you prefer a narrower import surface. ## Core Helpers The SDK includes helpers for reading and producing extension metadata: * `extractServiceExtensionDemands(extension)` reads demands from an agent card. * `fulfillServiceExtensionDemand(extension)` merges fulfillments into metadata. * `extractUiExtensionData(extension)` reads UI metadata from a message. * `resolveUserMetadata(inputs)` builds metadata for form, approval, and canvas responses. * `handleAgentCard(agentCard)` returns `demands` and a `resolveMetadata` function. * `handleTaskStatusUpdate(event)` maps status updates to actionable UI results. * `buildMessageBuilder(agentCard)` builds user messages with resolved metadata. Use these directly when you want fine grained control beyond `handleAgentCard`. `extractServiceExtensionDemands`, `fulfillServiceExtensionDemand`, and `extractUiExtensionData` are factories. You pass an extension definition once and reuse the returned function for that extension. For end to end usage, see **[A2A Client Integration](./a2a-client)** and **[User Messages](./user-messages)**. ## Service Extensions Service extensions use a dependency injection pattern. The agent declares demands and the client provides fulfillments. ### LLM Service Provides OpenAI compatible LLM access with `api_base`, `api_key`, and `api_model`. ```typescript theme={null} import { buildLLMExtensionFulfillmentResolver, handleAgentCard } from 'agentstack-sdk'; const { resolveMetadata } = handleAgentCard(agentCard); const llmResolver = buildLLMExtensionFulfillmentResolver(api, contextToken); const metadata = await resolveMetadata({ llm: llmResolver, }); ``` ### Embedding Service Provides OpenAI compatible embedding access with `api_base`, `api_key`, and `api_model`. ```typescript theme={null} import { handleAgentCard } from 'agentstack-sdk'; const { resolveMetadata } = handleAgentCard(agentCard); const metadata = await resolveMetadata({ embedding: async (demands) => ({ embedding_fulfillments: Object.fromEntries( Object.keys(demands.embedding_demands).map((key) => [ key, { identifier: 'openai/text-embedding-3-small', api_base: 'https://api.openai.com/v1', api_key: 'your-api-key', api_model: 'text-embedding-3-small', }, ]), ), }), }); ``` ### MCP Provides Model Context Protocol transports for tool and connector access. ```typescript theme={null} import { MCPTransportType, handleAgentCard } from 'agentstack-sdk'; const { resolveMetadata } = handleAgentCard(agentCard); const metadata = await resolveMetadata({ mcp: async (demands) => ({ mcp_fulfillments: Object.fromEntries( Object.keys(demands.mcp_demands).map((key) => [ key, { transport: { type: MCPTransportType.StreamableHttp, url: 'https://mcp.example.com', headers: { Authorization: 'Bearer token' }, }, }, ]), ), }), }); ``` ### OAuth Provides OAuth redirect metadata for authentication flows. ```typescript theme={null} import { handleAgentCard } from 'agentstack-sdk'; const { resolveMetadata } = handleAgentCard(agentCard); const metadata = await resolveMetadata({ oauth: async (demands) => ({ oauth_fulfillments: Object.fromEntries( Object.keys(demands.oauth_demands).map((key) => [ key, { redirect_uri: 'https://app.example.com/oauth/callback' }, ]), ), }), oauthRedirectUri: () => 'https://app.example.com/oauth/callback', }); ``` ### Secrets Provides secret values required by the agent. ```typescript theme={null} import { handleAgentCard } from 'agentstack-sdk'; const { resolveMetadata } = handleAgentCard(agentCard); const metadata = await resolveMetadata({ secrets: async (demands) => ({ secret_fulfillments: Object.fromEntries( Object.keys(demands.secret_demands).map((key) => [key, { secret: 'your-secret' }]), ), }), }); ``` ### Settings (deprecated) Deprecated. Planned for removal in the next release. Use [Settings Form](#settings-form) instead. This extension lives under `ui/settings` in the SDK, but it is treated as a service extension because it carries demands and fulfillments. ```typescript theme={null} import { handleAgentCard } from 'agentstack-sdk'; const { resolveMetadata } = handleAgentCard(agentCard); const metadata = await resolveMetadata({ settings: async (demands) => ({ values: Object.fromEntries( demands.fields.map((field) => { if (field.type === 'single_select') { return [field.id, { type: 'single_select', value: field.default_value }]; } return [ field.id, { type: 'checkbox_group', values: Object.fromEntries( field.fields.map((checkbox) => [checkbox.id, { value: checkbox.default_value }]), ), }, ]; }), ), }), }); ``` #### Migration guide * Agent cards: replace the `ui/settings` extension with the `services/form` extension and move settings fields to `form_demands.settings_form`. * Single select: change type `single_select` to `singleselect`, and change `options` items from `{ value }` to `{ id }`. * Checkbox group: `checkbox_group` now requires a non empty `label`. * Checkbox items: `checkbox` now requires `content` for the visible text. Keep `label` populated too and map the legacy checkbox label to `content` when migrating. * Fulfillments: change checkbox group values from `{ values: { [id]: { value } } }` to `{ value: { [id]: boolean | null } }`. ### Form Provides form responses when the agent requests structured input. ```typescript theme={null} import { handleAgentCard } from 'agentstack-sdk'; const { resolveMetadata } = handleAgentCard(agentCard); const metadata = await resolveMetadata({ form: async () => ({ form_fulfillments: { default: { values: { name: 'Ada' }, }, }, }), }); ``` #### Settings Form Settings UI is delivered through the Form service extension. Agents request settings with `form_demands.settings_form`, and clients respond with `form_fulfillments.settings_form`. ```typescript theme={null} import { handleAgentCard } from "agentstack-sdk"; const { resolveMetadata } = handleAgentCard(agentCard); const metadata = await resolveMetadata({ form: async (demands) => { const settingsForm = demands.form_demands.settings_form; if (!settingsForm) { return { form_fulfillments: {} }; } return { form_fulfillments: { settings_form: { values: Object.fromEntries( settingsForm.fields.map((field) => { if (field.type === "singleselect") { return [ field.id, { type: "singleselect", value: field.default_value } ]; } if (field.type === "checkbox_group") { return [ field.id, { type: "checkbox_group", value: Object.fromEntries( field.fields.map((checkbox) => [checkbox.id, checkbox.default_value]), ), }, ]; } throw new Error("Unsupported settings field type"); }), ), }, }, }; }, }); ``` Prefer `settings_form` when both `settings_form` and legacy settings demands are present. ### Platform API Adds context token metadata so the agent can call platform services. This is typically used only when you cannot pass the token through A2A client headers. `getContextToken` is deprecated and kept for backward compatibility. Prefer passing the context token via A2A client headers when possible. ```typescript theme={null} import { handleAgentCard } from 'agentstack-sdk'; const { resolveMetadata } = handleAgentCard(agentCard); const metadata = await resolveMetadata({ getContextToken: () => contextToken, }); ``` `getContextToken` is deprecated. Prefer sending the context token via A2A client headers when possible. ## UI Extensions UI extensions are message metadata your UI can render. The SDK includes typed schemas for extracting these payloads. ### Form Request Requests a form render payload. ```typescript theme={null} import { extractUiExtensionData, formRequestExtension } from 'agentstack-sdk'; const readForm = extractUiExtensionData(formRequestExtension); const form = readForm(message.metadata); ``` ### Approval Requests user approval for an action or tool call. ```typescript theme={null} import { approvalExtension, extractUiExtensionData } from 'agentstack-sdk'; const readApproval = extractUiExtensionData(approvalExtension); const approval = readApproval(message.metadata); ``` ### Canvas Requests a canvas edit with indices and description. ```typescript theme={null} import { canvasExtension, extractUiExtensionData } from 'agentstack-sdk'; const readCanvas = extractUiExtensionData(canvasExtension); const request = readCanvas(message.metadata); ``` ### Citation Provides citation ranges and URLs for inline references. ```typescript theme={null} import { citationExtension, extractUiExtensionData } from 'agentstack-sdk'; const readCitations = extractUiExtensionData(citationExtension); const citations = readCitations(message.metadata); ``` ### Trajectory Provides trace entries for reasoning or execution steps. ```typescript theme={null} import { extractUiExtensionData, trajectoryExtension } from 'agentstack-sdk'; const readTrajectory = extractUiExtensionData(trajectoryExtension); const trajectory = readTrajectory(message.metadata); ``` ### Agent Detail Provides agent metadata to display in the UI. This extension is sent in the agent card capabilities, not in message metadata. ```typescript theme={null} import { agentDetailExtension, extractUiExtensionData } from 'agentstack-sdk'; const readAgentDetail = extractUiExtensionData(agentDetailExtension); const uri = agentDetailExtension.getUri(); const params = agentCard.capabilities.extensions?.find((extension) => extension.uri === uri)?.params; const agentDetail = params ? readAgentDetail({ [uri]: params }) : null; ``` ### Error Provides structured error information. ```typescript theme={null} import { errorExtension, extractUiExtensionData } from 'agentstack-sdk'; const readError = extractUiExtensionData(errorExtension); const error = readError(message.metadata); ``` ### OAuth Request Provides an OAuth authorization endpoint to redirect the user. ```typescript theme={null} import { extractUiExtensionData, oauthRequestExtension } from 'agentstack-sdk'; const readOAuth = extractUiExtensionData(oauthRequestExtension); const oauthRequest = readOAuth(message.metadata); ``` ### Secrets Request Provides secret demand prompts. ```typescript theme={null} import { extractUiExtensionData, secretsRequestExtension } from 'agentstack-sdk'; const readSecrets = extractUiExtensionData(secretsRequestExtension); const secretDemands = readSecrets(message.metadata); ``` ### Handling task status updates Use `handleTaskStatusUpdate` to parse status updates into UI actions. This covers OAuth, secrets, forms, and approval flows. ```typescript theme={null} import { handleTaskStatusUpdate, TaskStatusUpdateType } from 'agentstack-sdk'; for await (const event of stream) { if (event.kind === 'status-update') { handleTaskStatusUpdate(event).forEach((result) => { switch (result.type) { case TaskStatusUpdateType.FormRequired: // Render result.form break; case TaskStatusUpdateType.OAuthRequired: // Redirect to result.url break; case TaskStatusUpdateType.SecretRequired: // Prompt for result.demands break; case TaskStatusUpdateType.ApprovalRequired: // Ask the user to approve result.request break; case TaskStatusUpdateType.TextInputRequired: // Prompt for text input } }); } } ``` ### Sending user metadata When the user responds to a form, approval, or canvas request, use `resolveUserMetadata` to build message metadata: ```typescript theme={null} import { resolveUserMetadata } from 'agentstack-sdk'; const metadata = await resolveUserMetadata({ form: formValues, approvalResponse: { decision: 'approve' }, canvasEditRequest: { start_index: 0, end_index: 12, artifact_id: 'artifact-id', description: 'Replace the title', }, }); ``` ## Type Safety All extension schemas are typed in TypeScript and validated with Zod at runtime. This helps catch malformed extension payloads early and keeps your UI logic aligned with the protocol. # Agent Responses Source: https://agentstack.beeai.dev/stable/custom-ui/agent-responses Extract and render message parts, citations, trajectories, and artifacts Agent responses arrive as A2A messages with parts (text, files) and metadata (citations, trajectories). This guide shows how to extract and render these components in your UI. ## Process message metadata Trajectory and citation metadata are stored in message metadata, not in parts. Use the UI extensions to extract them and render UI sections. ```typescript theme={null} import { citationExtension, extractUiExtensionData, trajectoryExtension, type Message } from "agentstack-sdk"; const extractCitation = extractUiExtensionData(citationExtension); const extractTrajectory = extractUiExtensionData(trajectoryExtension); function processMessageMetadata(message: Message) { const parts = []; const trajectory = extractTrajectory(message.metadata); const citations = extractCitation(message.metadata)?.citations; if (trajectory) { parts.push({ kind: "trajectory", ...trajectory }); } if (citations) { parts.push( ...citations.map((citation) => ({ kind: "source", url: citation.url, startIndex: citation.start_index ?? undefined, endIndex: citation.end_index ?? undefined, title: citation.title ?? undefined, description: citation.description ?? undefined, })), ); } return parts; } ``` ## Process message parts Text and file parts are in the `message.parts` array. Map them to your UI components. ```typescript theme={null} import { type Part } from "agentstack-sdk"; function processParts(parts: Part[]) { return parts.flatMap((part) => { if (part.kind === "text") { return [{ kind: "text", text: part.text, }]; } if (part.kind === "file") { return [ { kind: "file", filename: part.file.name ?? "file", mimeType: part.file.mimeType ?? "application/octet-stream", url: resolveFileUrl(part.file), }, ]; } if (part.kind === "data") { return [{ kind: "data", data: part.data, }]; } return []; }); } ``` Data parts are structured payloads. Render them as JSON or map them to specialized UI components in your app. ## Resolve file URLs Files can arrive as a platform URL or inline base64 bytes. Convert them to something the UI can render. ```typescript theme={null} import { type FilePart } from "agentstack-sdk"; function resolveFileUrl(file: FilePart["file"]) { if ("uri" in file) { return file.uri; } const mimeType = file.mimeType ?? "text/plain"; return `data:${mimeType};base64,${file.bytes}`; } ``` ## Artifacts Artifact updates arrive as `artifact-update` events and contain their own parts. Process them the same way as message parts. # Custom UI Architecture Guide Source: https://agentstack.beeai.dev/stable/custom-ui/architecture-guide Practical approach to building a custom Agent Stack UI using the chat-ui reference example If you are building your own user interface, start with the reference implementation: * [chat-ui example source](https://github.com/i-am-bee/agentstack/tree/main/apps/agentstack-sdk-ts/examples/chat-ui) * Local path in this monorepo: `apps/agentstack-sdk-ts/examples/chat-ui` This page explains the approach used in that example and how to extend it into a full custom UI. The example is intentionally minimal: it runs against one provider/agent at a time, with `VITE_AGENTSTACK_PROVIDER_ID` set manually in `.env`. This keeps the core SDK flow easy to follow before you add provider discovery, routing, and richer UI state. It uses React, TypeScript, and Vite for a clean demonstration and fast local iteration. The architecture itself is not tied to this stack. You can apply the same API, context token, and A2A streaming flow in other frontend frameworks. This example assumes the following runtime setup: * Your Agent Stack server is running and reachable. * The server has at least one provider/agent available. * `VITE_AGENTSTACK_PROVIDER_ID` matches an existing provider in that server. * When running from the Vite dev server (`http://localhost:5173`), CORS on the Agent Stack server allows that origin. If your server cannot allow the frontend origin directly, you can route requests through a frontend proxy, but proxy configuration is intentionally out of scope for this basic example. If you still need the SDK setup basics, start with **[Getting Started](./getting-started)** and then return here. ## What a custom UI needs Most implementations follow the same core flow: 1. Create a platform API client 2. Create a context and context token 3. Create an authenticated A2A client 4. Resolve agent card demands into metadata 5. Send messages and stream task events 6. Add UI flows for forms, approvals, OAuth, errors, and other extension-driven interactions ## 1. Configure environment and target provider The example keeps runtime configuration in environment variables and injects the target provider ID directly. `.env` values: ```bash theme={null} VITE_AGENTSTACK_BASE_URL="http://localhost:8333" VITE_AGENTSTACK_PROVIDER_ID="your-provider-id" ``` * `VITE_AGENTSTACK_BASE_URL`: URL of your actual Agent Stack server instance * `VITE_AGENTSTACK_PROVIDER_ID`: provider/agent ID the example will call In the example code, these values are read as: ```typescript theme={null} const BASE_URL = import.meta.env.VITE_AGENTSTACK_BASE_URL; const PROVIDER_ID = import.meta.env.VITE_AGENTSTACK_PROVIDER_ID; ``` ## 2. Create a context and context token Before sending messages, create a conversation context and a token scoped for agent access: ```typescript theme={null} import { buildApiClient, unwrapResult } from "agentstack-sdk"; const api = buildApiClient({ baseUrl: BASE_URL }); const context = unwrapResult(await api.createContext({ provider_id: PROVIDER_ID })); const contextToken = unwrapResult( await api.createContextToken({ context_id: context.id, grant_global_permissions: { a2a_proxy: [PROVIDER_ID], llm: ["*"], }, grant_context_permissions: { context_data: ["*"], }, }), ); ``` If your Agent Stack server requires user authentication, initialize `buildApiClient` with an authenticated fetch (for example, `createAuthenticatedFetch(accessToken)`), as shown in **[Getting Started](./getting-started)**. Keep permissions minimal for your use case. See **[Permissions and Tokens](./permissions-and-tokens)** for scope details. ## 3. Create an authenticated A2A client Use the context token with `createAuthenticatedFetch`, then pass it to both the transport and card resolver: ```typescript theme={null} import { ClientFactory, ClientFactoryOptions, DefaultAgentCardResolver, JsonRpcTransportFactory, } from "@a2a-js/sdk/client"; import { createAuthenticatedFetch, getAgentCardPath } from "agentstack-sdk"; const fetchImpl = createAuthenticatedFetch(contextToken.token); const factory = new ClientFactory( ClientFactoryOptions.createFrom(ClientFactoryOptions.default, { transports: [new JsonRpcTransportFactory({ fetchImpl })], cardResolver: new DefaultAgentCardResolver({ fetchImpl }), }), ); const agentCardPath = getAgentCardPath(PROVIDER_ID); const client = await factory.createFromUrl(BASE_URL, agentCardPath); ``` ## 4. Resolve agent requirements once per session Read the agent card and resolve demand fulfillments before the first message: ```typescript theme={null} import { buildLLMExtensionFulfillmentResolver, handleAgentCard } from "agentstack-sdk"; const agentCard = await client.getAgentCard(); const { resolveMetadata } = handleAgentCard(agentCard); const llmResolver = buildLLMExtensionFulfillmentResolver(api, contextToken); const metadata = await resolveMetadata({ llm: llmResolver }); ``` This keeps extension fulfillment logic centralized and reusable. See **[Agent Requirements](./agent-requirements)**. ## 5. Send messages and process stream events The example sends a user message and reads streamed output from both `status-update` and `message` events: ```typescript theme={null} const stream = client.sendMessageStream({ message: { kind: "message", role: "user", messageId: crypto.randomUUID(), contextId, parts: [{ kind: "text", text }], metadata, }, }); let agentText = ""; for await (const event of stream) { if (event.kind === "status-update" || event.kind === "message") { const message = event.kind === "message" ? event : event.status.message; const text = extractTextFromMessage(message); if (text) { agentText += text; } } } ``` This aggregation is intentionally minimal and text-only for readability. For full handling of `task`, `artifact-update`, cancellation, and failure states, use the patterns in **[A2A Client Integration](./a2a-client)** and **[Error Handling](./error-handling)**. ## 6. Extend the basic chat loop for production The example intentionally keeps UI logic minimal. Production apps usually add: * `handleTaskStatusUpdate` to drive form, approval, OAuth, and secret prompts * `resolveUserMetadata` to submit structured user responses * Citation and trajectory rendering from message metadata * Artifact rendering for files and non-text outputs * Retry and cancellation controls for long-running tasks Related guides: * **[User Messages](./user-messages)** * **[Agent Responses](./agent-responses)** * **[Agent Requirements](./agent-requirements)** ## Implementation checklist 1. Configure `VITE_AGENTSTACK_BASE_URL` and `VITE_AGENTSTACK_PROVIDER_ID` 2. Create `context` and `contextToken` 3. Build authenticated A2A client 4. Resolve agent card demands to metadata 5. Send message stream and render updates 6. Handle structured UI interactions and errors ## Run the reference example ```bash theme={null} cd apps/agentstack-sdk-ts/examples/chat-ui cp .env.example .env pnpm install pnpm dev ``` Update `.env` with a valid `VITE_AGENTSTACK_BASE_URL` and `VITE_AGENTSTACK_PROVIDER_ID` before starting. ## Troubleshooting * **`Missing required environment variables.` on startup** `VITE_AGENTSTACK_BASE_URL` or `VITE_AGENTSTACK_PROVIDER_ID` is missing. Check your `.env` file and restart `pnpm dev`. * **Network errors when creating context/token** `VITE_AGENTSTACK_BASE_URL` is wrong, unreachable, or points to a different environment. Verify the server URL and that the Agent Stack API is running. * **CORS errors in the browser console** The Agent Stack server must allow the frontend origin (for Vite dev, `http://localhost:5173`). Update server CORS settings or use a proxy. * **401/403 responses from platform API endpoints** Your server likely requires user auth. Use `buildApiClient` with authenticated fetch (for example, `createAuthenticatedFetch(accessToken)`), as shown in **[Getting Started](./getting-started)**. * **Context token created, but agent run fails with permission-related errors** The token grants may be too narrow for the provider/agent. Recheck `grant_global_permissions` and `grant_context_permissions` in Step 2. * **Provider not found / invalid provider ID errors** `VITE_AGENTSTACK_PROVIDER_ID` must match an existing provider on the target server. Confirm the ID in your Agent Stack instance. * **UI shows little or no useful output even though requests succeed** This example intentionally aggregates only text parts. Agents that return files, data parts, citations, forms, or artifacts need additional rendering logic (see **[Agent Responses](./agent-responses)** and **[A2A Client Integration](./a2a-client)**). # Error Handling Source: https://agentstack.beeai.dev/stable/custom-ui/error-handling Handle platform API errors and agent task failures The SDK handles errors at two levels: 1. **Platform API errors** occur when communicating with the Agent Stack server (authentication, network issues, invalid responses). 2. **Agent task errors** occur when agents fail or reject tasks during execution. This guide shows how to catch and handle both error types in your UI. ## Platform API errors All API methods return `ApiResult`. You can branch on `result.ok` or use `unwrapResult` and catch errors. ```typescript theme={null} import { buildApiClient, unwrapResult } from "agentstack-sdk"; const api = buildApiClient({ baseUrl: "https://your-agentstack-instance.com" }); const result = await api.readUser(); if (!result.ok) { console.error(result.error.type, result.error.message); } const user = unwrapResult(await api.readUser()); ``` ### Error helpers Use the guard helpers to match specific error types. ```typescript theme={null} import { isHttpError, isNetworkError, isParseError, isValidationError } from "agentstack-sdk"; try { unwrapResult(await api.readUser()); } catch (error) { if (isHttpError(error, 401)) { console.error("Unauthorized"); } if (isNetworkError(error)) { console.error("Network issue"); } if (isParseError(error)) { console.error("Invalid JSON response"); } if (isValidationError(error)) { console.error(error.apiError.details.issues); } } ``` ## A2A extension errors When tasks fail or are rejected, agents can emit an error extension payload. Read it with `extractUiExtensionData` and display a user facing message. ```typescript theme={null} import { errorExtension, extractUiExtensionData } from "agentstack-sdk"; const readError = extractUiExtensionData(errorExtension); const errorMetadata = readError(event.status.message?.metadata); if (errorMetadata) { console.error(errorMetadata.message ?? "Agent error"); } ``` # Getting Started Source: https://agentstack.beeai.dev/stable/custom-ui/getting-started SDK overview and key workflows for building custom agent interfaces The Agent Stack TypeScript Client SDK simplifies building custom UIs for A2A agents. It handles agent service demands, maps task status updates to UI actions, and provides a typed platform API client. Use it to fulfill agent requirements, render interactive elements like forms and approvals, and manage communication between your UI and Agent Stack. It builds on the [Agent2Agent Protocol (A2A)](https://a2a-protocol.org/) and provides two layers: 1. A2A extensions and helpers that translate agent card demands such as LLM access, embeddings, OAuth, and secrets into client fulfillments like API keys, model choices, redirect URIs, and secret values, plus UI metadata. 2. A platform API client that talks to the AgentStack server. The SDK also exports A2A protocol types and Zod schemas so your UI can stay in sync with the protocol. ## Quick Start ### 1. Install ```bash theme={null} npm i agentstack-sdk @a2a-js/sdk ``` ### 2. Create the platform API client ```typescript theme={null} import { buildApiClient, createAuthenticatedFetch } from "agentstack-sdk"; const baseUrl = "https://your-agentstack-instance.com"; const api = buildApiClient({ baseUrl, fetch: createAuthenticatedFetch(accessToken), }); ``` `buildApiClient` exposes the platform API with typed responses and runtime validation. Use the user access token from your identity provider (OIDC/OAuth) for UI side API calls. **Browser clients require CORS** If your custom UI runs on a different origin than your Agent Stack server, enable CORS on the server for your frontend origin. See **[Cross-Origin Resource Sharing Configuration](../deploy-agent-stack/deployment-guide#cross-origin-resource-sharing-configuration)**. If allowing origins directly is not possible, route calls through a same-origin frontend proxy. ### 3. Create context and context token ```typescript theme={null} const providers = unwrapResult(await api.listProviders()); const providerId = providers[0]?.id; const context = unwrapResult( await api.createContext({ provider_id: providerId }), ); const contextToken = unwrapResult( await api.createContextToken({ context_id: context.id, grant_global_permissions: { llm: ["*"], embeddings: ["*"], a2a_proxy: ["*"], }, grant_context_permissions: { files: ["*"], vector_stores: ["*"], context_data: ["*"], }, }), ); ``` **Finding `providerId`** Use `listProviders` or `readProviderByLocation` from the platform API to identify the agent you want. See **[Platform API Client](./platform-api-client)** for the provider endpoints. Context tokens are scoped for agent use and can also authenticate to the platform API when running inside the agent. See **[Permissions and Tokens](./permissions-and-tokens)** for token types and scopes. **Token usage summary** * Use **access token** for platform API calls from the UI. * Use **context token** for A2A client calls and agent card fetches. All API calls return `ApiResult`. Use `unwrapResult` if you want exceptions, and then handle errors with `isHttpError`, `isNetworkError`, `isParseError`, and `isValidationError`. ### 4. Create an A2A client and send a message Pass the **context token** to the A2A client fetch. Then fetch the agent card, resolve demands, and stream a message. For a full walkthrough (status updates, user responses, and cancellation), see **[A2A Client Integration](./a2a-client)**. ```typescript theme={null} import { ClientFactory, ClientFactoryOptions, DefaultAgentCardResolver, JsonRpcTransportFactory, } from "@a2a-js/sdk/client"; import { createAuthenticatedFetch, getAgentCardPath } from "agentstack-sdk"; const fetchImpl = createAuthenticatedFetch(contextToken.token); const factory = new ClientFactory( ClientFactoryOptions.createFrom(ClientFactoryOptions.default, { transports: [new JsonRpcTransportFactory({ fetchImpl })], cardResolver: new DefaultAgentCardResolver({ fetchImpl }), }), ); const agentCardPath = getAgentCardPath(providerId); const client = await factory.createFromUrl(baseUrl, agentCardPath); const stream = client.sendMessageStream({ message: { kind: "message", role: "user", messageId: crypto.randomUUID(), contextId: context.id, parts: [{ kind: "text", text: "Hello" }], }, }); ``` `createAuthenticatedFetch` attaches the `Authorization` header to every request so the A2A client can load the agent card and open JSON RPC streams with authentication. The agent card URL follows this convention: `{baseUrl}/api/v1/a2a/{providerId}/.well-known/agent-card.json` * `baseUrl` is your Agent Stack server root * `providerId` is the agent provider identifier `getAgentCardPath(providerId)` returns the **path portion** of that URL. Use it with `ClientFactory.createFromUrl(baseUrl, agentCardPath)` so the client can resolve the full agent card endpoint. **Need UI events and follow up messages?** The **[A2A Client Integration](./a2a-client)** shows how to handle status updates, render forms and approvals, and respond with user metadata. ## Architecture overview ```mermaid theme={null} graph TB subgraph CustomUI [Custom UI Application] SDK[AgentStack SDK] A2AClient[A2A Client] end subgraph AgentStack [Agent Stack Platform] Agent[Agent] API[Platform API] end SDK -->|"handleAgentCard
resolveMetadata"| A2AClient A2AClient -->|"A2A Protocol Messages"| Agent Agent -->|"Task Status Updates"| A2AClient A2AClient -->|"handleTaskStatusUpdate"| SDK SDK -->|"buildApiClient"| API style SDK fill:#e1f5ff,color:#000 style A2AClient fill:#fff4e1,color:#000 style Agent fill:#e8f5e9,color:#000 style API fill:#f3e5f5,color:#000 ``` ## SDK entrypoints The SDK is split into focused entrypoints: 1. `agentstack-sdk` for everything 2. `agentstack-sdk/api` for platform API client, schemas, and types 3. `agentstack-sdk/core` for extension helpers and A2A interaction utilities 4. `agentstack-sdk/extensions` for A2A extension definitions and types ## Protocol Types and Schemas The SDK exports A2A protocol types and Zod schemas that match the AgentStack UI usage, including: * `Message`, `Part`, and `Task` types * `TaskStatusUpdateEvent` and `TaskArtifactUpdateEvent` * UI and service extension schemas These exports are useful when building strongly typed UI layers or validating inbound messages. ## Next Steps * **[Custom UI Architecture Guide](./architecture-guide)** for a practical implementation approach using the `chat-ui` reference example * **[A2A Client Integration](./a2a-client)** for the full streaming, UI events, and response flow * **[Agent Requirements](./agent-requirements)** for service and UI extension handling * **[Agent Responses](./agent-responses)** for rendering message parts and citations * **[User Messages](./user-messages)** for composing user messages with metadata * **[Error Handling](./error-handling)** for platform and extension error patterns * **[Platform API Client](./platform-api-client)** for endpoint reference and error helpers # Permissions and Tokens Source: https://agentstack.beeai.dev/stable/custom-ui/permissions-and-tokens User roles, context tokens, and permission scopes for platform access Agent Stack uses two types of authentication tokens to control access to platform resources: 1. **User Tokens** - Issued by your identity provider (OIDC/OAuth) for human users accessing the platform 2. **Context Tokens** - Generated programmatically for agents to access resources with limited permissions during a conversation Both token types can be used to authenticate API requests, but serve different purposes and have different permission scopes. ## Understanding the Permission System Agent Stack's permission system has two independent dimensions that work together: 1. **Privacy and Ownership** - Controls who can access resources (user roles and entity visibility) 2. **Context Scoping** - Controls where resources are associated and how agents access them ### Privacy and Ownership This determines **who can access and manage resources** based on entity type and user role. ```mermaid theme={null} graph TB Start["Privacy & Ownership"] Start --> Entities["Entity Types"] Entities --> Platform["Platform Entities"] Entities --> UserPrivate["User-Private Entities"] Entities --> SemiPrivate["Semi-Private Entities"] Start --> Roles["User Roles"] Roles --> User["USER"] Roles --> Developer["DEVELOPER"] Roles --> Admin["ADMIN"] style Platform fill:#fff3e0,stroke:#ff6f00 style UserPrivate fill:#e8f5e9,stroke:#2e7d32 style SemiPrivate fill:#fce4ec,stroke:#c2185b style User fill:#e3f2fd,stroke:#1976d2 style Developer fill:#e1bee7,stroke:#7b1fa2 style Admin fill:#ffcdd2,stroke:#c62828 ``` #### Entity Types **Platform Entities** - Shared across all users in the platform: | Entity | Description | | ------------------------- | --------------------------------------- | | **system\_configuration** | Platform-wide configuration | | **model\_providers** | Available LLM/embedding model providers | **User-Private Entities** - Scoped to individual users (users can access their own): | Entity | Description | | ------------------ | ------------------------------- | | **files** | Uploaded files and documents | | **vector\_stores** | Vector databases for embeddings | | **variables** | User-specific variables | | **contexts** | Conversation contexts | | **context\_data** | Context history and data | | **feedback** | User feedback submissions | **Semi-Private Entities** - Visible to all users, but management is restricted by role: | Entity | Description | | -------------------- | --------------------- | | **providers** | Agent providers | | **provider\_builds** | Agent provider builds | #### User Roles and Access Agent Stack uses three role-based access levels: **USER** - Standard user role: * **Platform Entities**: Read-only access * **User-Private Entities**: Full read/write access to their own resources * **Semi-Private Entities**: Read-only access (can view all providers/builds but cannot manage them). **DEVELOPER** - Extends USER with additional management capabilities: * All USER permissions * **Semi-Private Entities**: Can create and manage their own providers and provider builds **ADMIN** - Full system access: * Full access to all resources across the platform ### Context Scoping and Agent Access This dimension controls **where resources are associated** and **how agents access them** through context tokens. A **context** represents a single conversation with an agent. Resources can be associated with either: * **User level** (not tied to any specific conversation) * **Context level** (linked to a specific conversation) This association, combined with token permissions, determines what an agent can access. #### Context-Scopeable Resources These resources can be **associated with** either the user level or a specific context: * **files**: Can be user-level (not associated with any context) or context-associated (linked to a specific context) * **vector\_stores**: Can be user-level or context-associated * **context\_data**: Conversation history (always associated with a context) **Context association does NOT restrict access** Association with a context is just metadata. A user token or a context token with **global permissions** can access ALL user files, including files associated with other contexts. Only a context token with **local-only permissions** is restricted to files associated with that specific context. #### Context Permissions for Agents When agents interact with resources, they use **context tokens** with limited permissions. You can grant specific permissions for each resource type: ```mermaid theme={null} graph TB Start["Context Scoping"] Start --> Resources["Resource Types"] Resources --> Scopeable["Context-Scopeable"] Resources --> GlobalOnly["Global-Only"] Start --> Tokens["Token Types"] Tokens --> UserToken["User Token (OIDC/OAuth)
Full user access"] Tokens --> ContextToken["Context Token (Generated)
Limited permissions"] ContextToken --> GlobalPerm["Global Permissions
Access ALL user resources"] ContextToken --> LocalPerm["Local Permissions
Only this context's resources"] style Scopeable fill:#e0f2f1,stroke:#00796b style GlobalOnly fill:#fff9c4,stroke:#f57f17 style UserToken fill:#e8eaf6,stroke:#3f51b5 style ContextToken fill:#f3e5f5,stroke:#8e24aa style GlobalPerm fill:#e1f5fe,stroke:#0277bd style LocalPerm fill:#fce4ec,stroke:#ad1457 ``` #### Available Permissions | Resource | Available Operations | Context Scopeable | Description | | -------------------- | ------------------------------- | ----------------- | ------------------------------- | | **files** | `read`, `write`, `extract`, `*` | ✔︎ | User files and documents | | **vector\_stores** | `read`, `write`, `*` | ✔︎ | Vector databases for embeddings | | **context\_data** | `read`, `write`, `*` | ✔︎ | Conversation history | | **llm** | `*` | | LLM inference services | | **embeddings** | `*` | | Embedding generation services | | **a2a\_proxy** | `*` | | Agent-to-agent communication | | **model\_providers** | `read`, `write`, `*` | | Model provider management | | **variables** | `read`, `write`, `*` | | User variables | | **providers** | `read`, `write`, `*` | | Agent providers | | **contexts** | `read`, `write`, `*` | | Context management | | **connectors** | `read`, `write`, `proxy`, `*` | | External service connectors | | **feedback** | `write` | | User feedback submissions | ### Global vs. Local Permission Grants When generating a context token, you specify **two independent permission sets** that control what the token can access: #### Global Permissions Grants the token access to **ALL user resources**, regardless of which context they're associated with. **What the token can access:** * All user-level files and vector stores (not associated with any context) * Files and vector stores associated with ANY context (including other contexts) **Example use cases:** * Agent needs to search across all user files from previous conversations * Agent needs to access a shared knowledge base (user-level vector store) * Agent creates persistent resources that should be accessible to other contexts #### Context Permissions (Local) Grants the token access **ONLY to resources associated with this specific context**. **What the token can access:** * Files and vector stores associated with this specific context * Context data (conversation history) for this context * A restricted surface area - only resources linked to this conversation **Example use cases:** * Sandboxed agent that should only see files uploaded in this conversation * Limiting agent access to prevent data leakage across conversations * Working with conversation-specific temporary data ## Creating Context Tokens Context tokens allow agents to authenticate with limited permissions. Here's the flow: ```mermaid theme={null} sequenceDiagram participant User participant Platform participant Agent User->>Platform: 1. Create Context Platform-->>User: Context created (context_id) User->>Platform: 2. Generate Context Token Note over Platform: Specify global & local permissions Platform-->>User: Context Token User->>Agent: 3. Start Agent Run Note over User,Agent: Context token passed via
A2A PlatformApiExtension Agent->>Platform: 4. Access Platform Resources Note over Agent,Platform: Using context token
with limited permissions Platform-->>Agent: Resources (within token scope) ``` ### Generating a Context Token Here is an example how you can create a context and generate a custom token with specific permissions: ```python Python SDK expandable theme={null} from agentstack_sdk.platform.context import Context, Permissions, ContextPermissions # Create a context providers = await Provider.list() context = await Context.create() # Generate a token with specific permissions token = await context.generate_token( providers=[providers[0]], grant_global_permissions=Permissions( files={"read", "write"}, llm={"*"}, embeddings={"*"}, ), grant_context_permissions=ContextPermissions( files={"read", "write"}, context_data={"read", "write"}, ), ) # Use the token print(f"Token: {token.token.get_secret_value()}") print(f"Expires at: {token.expires_at}") ``` ```bash REST API theme={null} # 1. Create a context curl -X POST "https://api.agentstack.example.com/api/v1/contexts" \ -H "Authorization: Bearer YOUR_USER_TOKEN" \ -H "Content-Type: application/json" # Response { "id": "123abc", "created_at": "2025-01-15T10:00:00Z" } # 2. Generate a context token curl -X POST "https://api.agentstack.example.com/api/v1/contexts/{context_id}/token" \ -H "Authorization: Bearer YOUR_USER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "grant_global_permissions": { "files": ["read", "write"], "llm": ["*"], "embeddings": ["*"], "a2a_proxy": [""] }, "grant_context_permissions": { "files": ["read", "write"], "context_data": ["read", "write"] } }' # Response { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expires_at": "2025-01-15T12:00:00Z" } ``` **Important Notes** * Context tokens **cannot** be used to generate other tokens * The permissions you grant must be a subset of your own permissions * Context token expiration is 20 minutes. If an agent runs longer it must request a new token using `AuthRequired` a2a message. ### Using Context Tokens in Agent Runs When calling an agent using the Agent Stack SDK, you pass the context token through extension configurations. Here's a complete example: ```python Full Agent Run Client Example expandable theme={null} from datetime import timedelta import httpx from a2a.client import ClientConfig, ClientFactory from a2a.types import Message, Part, Role, TextPart from agentstack_sdk.platform import ModelProvider, Provider, ModelCapability from agentstack_sdk.platform.context import Permissions, ContextPermissions, Context from agentstack_sdk.a2a.extensions import ( LLMServiceExtensionClient, LLMServiceExtensionSpec, EmbeddingServiceExtensionClient, EmbeddingServiceExtensionSpec, PlatformApiExtensionClient, PlatformApiExtensionSpec, LLMFulfillment, EmbeddingFulfillment, ) from uuid import uuid4 async def call_agent( provider: Provider, # any agent from Provider.list() call ): # 1. Create context and generate token context = await Context.create(provider_id="your_provider_id") context_token = await context.generate_token( grant_global_permissions=Permissions( llm={"*"}, embeddings={"*"}, a2a_proxy={"*"} ), grant_context_permissions=ContextPermissions( files={"*"}, vector_stores={"*"}, context_data={"*"} ), ) # 2. Get agent card and prepare extension specs agent_card = provider.agent_card llm_spec = LLMServiceExtensionSpec.from_agent_card(agent_card) embedding_spec = EmbeddingServiceExtensionSpec.from_agent_card(agent_card) platform_spec = PlatformApiExtensionSpec.from_agent_card(agent_card) # 3. Build extension metadata with context token metadata = {} # LLM extension - token passed as api_key if llm_spec: metadata |= LLMServiceExtensionClient(llm_spec).fulfillment_metadata( llm_fulfillments={ key: LLMFulfillment( api_base="{platform_url}/api/v1/openai/", api_key=context_token.token.get_secret_value(), api_model=( await ModelProvider.match( suggested_models=demand.suggested, capability=ModelCapability.LLM, ) )[0].model_id, ) for key, demand in llm_spec.params.llm_demands.items() } ) # Embedding extension - token passed as api_key if embedding_spec: metadata |= EmbeddingServiceExtensionClient( embedding_spec ).fulfillment_metadata( embedding_fulfillments={ key: EmbeddingFulfillment( api_base="{platform_url}/api/v1/openai/", api_key=context_token.token.get_secret_value(), api_model=( await ModelProvider.match( suggested_models=demand.suggested, capability=ModelCapability.EMBEDDING, ) )[0].model_id, ) for key, demand in embedding_spec.params.embedding_demands.items() } ) # 4. Create message and run agent message = Message( message_id=str(uuid4()), parts=[Part(root=TextPart(text="Hello, agent!"))], role=Role.user, context_id=context.id, # Use the Context ID we just created metadata=metadata, ) # 5. Execute agent run async with httpx.AsyncClient( # Add context token from before headers={"Authorization": f"Bearer {context_token.token.get_secret_value()}"}, follow_redirects=True, timeout=timedelta(hours=1).total_seconds(), ) as httpx_client: conf = ClientConfig(httpx_client=httpx_client, use_client_preference=True) a2a_client = ClientFactory(conf).create(card=agent_card) async for event in a2a_client.send_message(message): # Handle agent responses print(event) ``` ### Inside Your Agent When you declare the `PlatformApiExtensionServer` in your agent function, the Agent Stack SDK automatically authenticates your platform API calls using the context token that was passed through the extension metadata. ```python expandable theme={null} from typing import Annotated from a2a.types import Message from agentstack_sdk.a2a.extensions import PlatformApiExtensionServer, PlatformApiExtensionSpec from agentstack_sdk.platform import File from agentstack_sdk.server import Server from agentstack_sdk.server.context import RunContext server = Server() @server.agent() async def my_agent( input: Message, context: RunContext, _: Annotated[PlatformApiExtensionServer, PlatformApiExtensionSpec()], ): """Your agent function with platform access.""" # The platform extension automatically authenticates these calls # using the context token passed in the extension metadata # Create a file - automatically scoped to the current context file = await File.create( filename="agent_output.txt", content=b"Generated by the agent", # context_id="auto" is the default - uses the context from the token # context_id=None will attempt to upload the file globally (outside the context) ) yield "Done! Uploaded a file to the context!" ``` **How it works** 1. The client passes the context token via the `Authorization` header 2. The `PlatformApiExtensionServer` receives the token and sets up the authentication context 3. All `File`, `VectorStore`, and other platform API calls automatically use this token 4. By default, context-scopeable resources are associated with the context (via `context_id="auto"`) ## Resource Scoping with `context_id` When creating context-scopeable resources (files, vector stores) using the SDK, the `context_id` parameter determines whether they are scoped globally (user-level) or locally (context-level). ### The `auto` Parameter Both `File.create()` and `VectorStore.create()` include a `context_id` parameter with special `"auto"` behavior. * `context_id="auto"` (default) - Automatically scopes the resource to the current context if used inside an agent with active `PlatformApiExtension` * `context_id=None` - Scopes the resource globally (outside the context) * `context_id=specific_context_id` - Scopes the resource explicitly to the context ID passed in **Best Practices** 1. **Use context tokens for agents**: When building agents, always use context tokens to limit access scope 2. **Default to `"auto"`**: Let the SDK determine the appropriate scope based on the client type 3. **Be explicit when needed**: Override `context_id` only when you need specific scoping behavior 4. **Consider data lifecycle**: Use local scoping for temporary data, global scoping for persistent resources ## Advanced: API Authentication This is an advanced section aimed for agent application developers who want to integrate their custom a2a clients with the Agent Stack. The Agent Stack platform API accepts two types of tokens for authentication: 1. **Access Token** - Issued by your identity provider (OIDC/OAuth) when a user logs in 2. **Context Token** - Generated programmatically for agents with limited permissions Technically, both token types are used identically: passed in the `Authorization: Bearer {token}` header for all API endpoints. However, as you may have noticed in the example above, you would use A2A extensions to send context token to the agent. The agent will then use the context token in the Bearer authorization header to authenticate itself with the platform API, this is abstracted away in the SDK. To make this crystal clear, let's break down the full low-level interaction with an agent that wants to upload a file. We will use the square brackets `[]` to denote the `Authorization: Bearer` header and placeholders `ACCESS_TOKEN` and `CONTEXT_TOKEN` for the user access token and context token, respectively. **Part 1: Client code**: Execute the Authorization code flow or similar to obtain an **ACCESS\_TOKEN** from the identity provider (in this flow, the user will log in to the system with their credentials). Create a **context token** with specific permissions, these are 2 API requests: 1. Create a context: `POST /api/v1/contexts` **\[ACCESS\_TOKEN]** 2. Create context token: `POST /api/v1/contexts/{context_id}/token` **\[ACCESS\_TOKEN]**. The body contains a request for `files.write` context permission grant. 1. Fulfill extension demands and configure message metadata, add **CONTEXT\_TOKEN** to: * LLM and embedding fulfillments * PlatformApiExtensionClient 2. Set `context_id` to associate the message with the context and token created in previous step Send message to the agent: `POST /api/v1/a2a/{provider_id}` **\[ACCESS\_TOKEN]**. **Proxy request authorization** The *request header* contains **ACCESS\_TOKEN** to authorize user. This token is consumed by the platform API (a2a proxy endpoint) and not forwarded to the agent. **Extension payload** The *request body* contains the **CONTEXT\_TOKEN** that the agent receives and can use later to call the Platform API. **Part 2: Agent code**: The agent requests the platform API extension using dependency injection: ```python theme={null} @server.agent() async def my_agent( ..., _: Annotated[PlatformApiExtensionServer, PlatformApiExtensionSpec()], ): ... ``` When the agent is invoked through the A2A protocol, the `PlatformApiExtensionServer` will consume the **CONTEXT\_TOKEN** from the extension metadata and set up the authentication context. In your agent code you will use the `File.create()` method to upload a file to the context. The method will call `POST /api/v1/files?context_id={context_id}` **\[CONTEXT\_TOKEN]**. Using the "auto" behavior of `File.create`, the file will be uploaded to the context by specifying the `context_id` query parameter. This ID is taken from the user message payload. The SDK will automatically attach the CONTEXT\_TOKEN to the request header. The token must contain the `files.write` context permission grant for this to work. At no point did Agent receive or use `ACCESS_TOKEN`. This is on purpose, because based on user role this token can possess destructive permissions - to delete other agents, read all user data (if the user is ADMIN, etc.). To prevent API misuse by untrusted agents, avoid passing the access token to the agent. # Platform API Client Source: https://agentstack.beeai.dev/stable/custom-ui/platform-api-client Complete reference for platform endpoints and API authentication The platform API client provides typed access to Agent Stack server endpoints for managing contexts, files, providers, and other platform resources. This guide covers client initialization, authentication, response handling, and the complete endpoint reference. The `buildApiClient` function creates a type safe HTTP client for interacting with the AgentStack platform API. It returns an object that groups all platform endpoints into a single client. Every method returns an `ApiResult` object so you can handle errors without throwing, or use `unwrapResult` to throw an `ApiErrorException` on failure. ## Initialization Create an API client by calling `buildApiClient` with configuration options: ```typescript theme={null} import { buildApiClient } from "agentstack-sdk"; const api = buildApiClient({ baseUrl: "https://your-agentstack-instance.com", fetch: customFetch, // Optional: provide custom fetch implementation }); ``` ### Configuration Options * `baseUrl` (required): The base URL of your AgentStack server instance. * `fetch` (optional): Custom fetch implementation. Required in Node.js \< 18 or environments without global fetch support. Useful for adding authentication headers or custom request handling. **Browser clients require CORS** If you call the Agent Stack API from a browser app running on a different origin, enable server-side CORS for your frontend origin. See **[Cross-Origin Resource Sharing Configuration](../deploy-agent-stack/deployment-guide#cross-origin-resource-sharing-configuration)**. If allowing origins directly is not possible, route calls through a same-origin frontend proxy. ### Example: Authenticated Client In many applications, you will want to add authentication headers to requests: ```typescript theme={null} import { buildApiClient, createAuthenticatedFetch } from "agentstack-sdk"; const authFetch = createAuthenticatedFetch("token-value"); const api = buildApiClient({ baseUrl: "https://your-agentstack-instance.com", fetch: authFetch, }); ``` ## Response Handling All API methods return a `Promise>`: ```typescript theme={null} import { unwrapResult } from "agentstack-sdk"; const result = await api.readUser(); if (result.ok) { console.log(result.data.email); } else { console.error(result.error.type, result.error.message); } // Or throw on error const user = unwrapResult(await api.readUser()); ``` `unwrapResult` also accepts an optional Zod schema if you want to validate against a more specific or stricter response shape: ```typescript theme={null} import { unwrapResult } from "agentstack-sdk"; const connectors = unwrapResult(await api.listConnectors(), stricterListConnectorsResponseSchema); ``` ### Error Helpers When you use `unwrapResult`, API errors throw `ApiErrorException`. Use the helper guards to handle specific cases: ```typescript theme={null} import { isHttpError, isValidationError } from "agentstack-sdk"; try { unwrapResult(await api.readUser()); } catch (error) { if (isHttpError(error, 401)) { console.error("Unauthorized"); } if (isValidationError(error)) { console.error(error.apiError.details.issues); } } ``` For more error handling patterns, see **[Error Handling](./error-handling)**. ## Type Safety All API methods are fully typed with TypeScript and validated with Zod schemas at runtime. This ensures response data matches the SDK types and catches API contract changes early. ## Endpoint Reference The `buildApiClient` return value includes the following methods. Each method returns a `Promise>`. In the list below, the return type shows the `T` value for quick reference. ### Contexts * `listContexts({ query }: ListContextsRequest): ListContextsResponse` * `createContext({ provider_id, metadata }: CreateContextRequest): CreateContextResponse` * `readContext({ context_id }: ReadContextRequest): ReadContextResponse` * `updateContext({ context_id, metadata }: UpdateContextRequest): UpdateContextResponse` * `deleteContext({ context_id }: DeleteContextRequest): DeleteContextResponse` * `listContextHistory({ context_id, query }: ListContextHistoryRequest): ListContextHistoryResponse` * `createContextHistory({ context_id, data }: CreateContextHistoryRequest): CreateContextHistoryResponse` * `patchContextMetadata({ context_id, metadata }: PatchContextMetadataRequest): PatchContextMetadataResponse` * `createContextToken({ context_id, grant_context_permissions, grant_global_permissions }: CreateContextTokenRequest): CreateContextTokenResponse` #### Example: Create a Context and Token ```typescript theme={null} const context = unwrapResult( await api.createContext({ provider_id: "my-provider-id", metadata: { project: "alpha" }, }), ); const token = unwrapResult( await api.createContextToken({ context_id: context.id, grant_global_permissions: { llm: ["*"], embeddings: ["*"], a2a_proxy: ["*"], contexts: ["read", "write"], }, grant_context_permissions: { files: ["read", "write", "extract"], vector_stores: ["read", "write"], context_data: ["read", "write"], }, }), ); ``` ### Files * `createFile({ context_id, file }: CreateFileRequest): CreateFileResponse` * `readFile({ context_id, file_id }: ReadFileRequest): ReadFileResponse` * `readFileContent({ context_id, file_id }: ReadFileContentRequest): ReadFileContentResponse` * `deleteFile({ context_id, file_id }: DeleteFileRequest): DeleteFileResponse` #### Example: Upload a File ```typescript theme={null} const upload = unwrapResult( await api.createFile({ context_id: context.id, file: new File(["Hello"], "hello.txt", { type: "text/plain" }), }), ); const content = unwrapResult( await api.readFileContent({ context_id: context.id, file_id: upload.id, }), ); ``` ### Providers * `listProviders({ query }: ListProvidersRequest): ListProvidersResponse` * `createProvider({ location, agent_card, auto_stop_timeout_sec, origin, variables }: CreateProviderRequest): CreateProviderResponse` * `readProvider({ id }: ReadProviderRequest): ReadProviderResponse` * `deleteProvider({ id }: DeleteProviderRequest): DeleteProviderResponse` * `patchProvider({ id, location, agent_card, auto_stop_timeout_sec, origin, variables }: PatchProviderRequest): PatchProviderResponse` * `readProviderLogs({ id }: ReadProviderLogsRequest): ReadProviderLogsResponse` * `listProviderVariables({ id }: ListProviderVariablesRequest): ListProviderVariablesResponse` * `updateProviderVariables({ id, variables }: UpdateProviderVariablesRequest): UpdateProviderVariablesResponse` * `readProviderByLocation({ location }: ReadProviderByLocationRequest): ReadProviderByLocationResponse` * `previewProvider({ location, agent_card, auto_stop_timeout_sec, origin, variables }: PreviewProviderRequest): PreviewProviderResponse` #### Example: Update Provider Variables ```typescript theme={null} await api.updateProviderVariables({ id: "provider-id", variables: { OPENAI_API_KEY: "secret", LEGACY_TOKEN: null, // set null to unset }, }); ``` #### Example: Streaming Provider Logs `readProviderLogs` returns a `ReadableStream` in `data`: ```typescript theme={null} const result = await api.readProviderLogs({ id: "provider-id" }); if (result.ok && result.data) { const reader = result.data.getReader(); await reader.read(); } ``` ### Provider Builds * `listProviderBuilds({ query }: ListProviderBuildsRequest): ListProviderBuildsResponse` * `createProviderBuild({ location, build_configuration, on_complete }: CreateProviderBuildRequest): CreateProviderBuildResponse` * `readProviderBuild({ id }: ReadProviderBuildRequest): ReadProviderBuildResponse` * `deleteProviderBuild({ id }: DeleteProviderBuildRequest): DeleteProviderBuildResponse` * `readProviderBuildLogs({ id }: ReadProviderBuildLogsRequest): ReadProviderBuildLogsResponse` * `previewProviderBuild({ location, build_configuration, on_complete }: PreviewProviderBuildRequest): PreviewProviderBuildResponse` #### Example: Create a Provider Build ```typescript theme={null} const build = unwrapResult( await api.createProviderBuild({ location: "github.com/org/repo", build_configuration: { dockerfile_path: "Dockerfile" }, on_complete: { type: "add_provider", auto_stop_timeout_sec: 900, variables: { OPENAI_API_KEY: "secret", }, }, }), ); console.log(build.status); ``` #### Example: Streaming Provider Build Logs `readProviderBuildLogs` returns a `ReadableStream` in `data`: ```typescript theme={null} const result = await api.readProviderBuildLogs({ id: build.id }); if (result.ok && result.data) { const reader = result.data.getReader(); await reader.read(); } ``` ### Model Providers * `listModelProviders(): ListModelProvidersResponse` * `createModelProvider({ api_key, base_url, type, name, description, watsonx_project_id, watsonx_space_id }: CreateModelProviderRequest): CreateModelProviderResponse` * `readModelProvider({ model_provider_id }: ReadModelProviderRequest): ReadModelProviderResponse` * `deleteModelProvider({ model_provider_id }: DeleteModelProviderRequest): DeleteModelProviderResponse` * `matchModelProviders({ suggested_models, capability, score_cutoff }: MatchModelProvidersRequest): MatchModelProvidersResponse` #### Example: Match Model Providers ```typescript theme={null} import { ModelCapability } from "agentstack-sdk"; const matches = unwrapResult( await api.matchModelProviders({ suggested_models: ["gpt-4", "gpt-4o"], capability: ModelCapability.Llm, score_cutoff: 0.4, }), ); ``` ### Connectors * `listConnectors(): ListConnectorsResponse` * `createConnector({ match_preset, url, client_id, client_secret, metadata }: CreateConnectorRequest): CreateConnectorResponse` * `readConnector({ connector_id }: ReadConnectorRequest): ReadConnectorResponse` * `deleteConnector({ connector_id }: DeleteConnectorRequest): DeleteConnectorResponse` * `connectConnector({ connector_id, redirect_url }: ConnectConnectorRequest): ConnectConnectorResponse` * `disconnectConnector({ connector_id }: DisconnectConnectorRequest): DisconnectConnectorResponse` * `listConnectorPresets(): ListConnectorPresetsResponse` #### Example: Start Connector OAuth ```typescript theme={null} const connector = unwrapResult(await api.connectConnector({ connector_id: "connector-id" })); if (connector.auth_request) { window.location.href = connector.auth_request.authorization_endpoint; } ``` ### Variables * `listVariables(): ListVariablesResponse` * `updateVariables({ variables }: UpdateVariablesRequest): UpdateVariablesResponse` #### Example: Update Variables ```typescript theme={null} await api.updateVariables({ variables: { GLOBAL_FLAG: "true", LEGACY_TOKEN: null, // set null to unset }, }); ``` ### Configuration * `readSystemConfiguration(): ReadSystemConfigurationResponse` * `updateSystemConfiguration({ default_embedding_model, default_llm_model }: UpdateSystemConfigurationRequest): UpdateSystemConfigurationResponse` #### Example: Update System Configuration ```typescript theme={null} const config = unwrapResult(await api.readSystemConfiguration()); const updated = unwrapResult( await api.updateSystemConfiguration({ default_llm_model: config.default_llm_model ?? "gpt-4o", default_embedding_model: config.default_embedding_model ?? "text-embedding-3-small", }), ); ``` ### Users * `readUser(): ReadUserResponse` #### Example: Read Current User ```typescript theme={null} const user = unwrapResult(await api.readUser()); console.log(user.email); ``` ### User Feedback * `createUserFeedback({ provider_id, context_id, task_id, message, rating, comment, comment_tags }: CreateUserFeedbackRequest): CreateUserFeedbackResponse` #### Example: Submit Feedback ```typescript theme={null} await api.createUserFeedback({ provider_id: "provider-id", context_id: "context-id", task_id: "task-id", message: "Agent replied with incorrect citations.", rating: -1, comment: "Sources were unrelated to the answer.", comment_tags: ["citations", "accuracy"], }); ``` ## Next Steps * **[Agent Requirements](./agent-requirements)** for service and UI extension handling # User Messages Source: https://agentstack.beeai.dev/stable/custom-ui/user-messages Compose user messages with agent fulfillments and user response metadata User messages need metadata that fulfills agent service demands and includes user responses to forms, approvals, and canvas requests. This guide shows how to build properly structured A2A messages with the correct metadata attached. See **[Agent Requirements](./agent-requirements)** for the fulfillment and UI metadata helpers used in this flow. ## Build a message builder `buildMessageBuilder` returns a function that resolves agent demands and builds a user message with metadata. ```typescript theme={null} import { buildMessageBuilder } from "agentstack-sdk"; const buildMessage = buildMessageBuilder(agentCard); const message = await buildMessage( "context-id", { llm: async (demands) => ({ llm_fulfillments: { default: { identifier: "llm_proxy", api_base: "{platform_url}/api/v1/openai/", api_key: contextToken.token, api_model: "gpt-4o", }, }, }), }, { messageId: "message-id", parts: [{ kind: "text", text: "Hello" }], }, ); ``` ## Add user responses Use `resolveUserMetadata` when the user responds to forms, approvals, or canvas requests. ```typescript theme={null} import { resolveUserMetadata } from "agentstack-sdk"; const metadata = await resolveUserMetadata({ form: { name: "Ada" }, approvalResponse: { decision: "approve" }, }); ``` You can merge the user metadata with agent card metadata if you are constructing messages manually. For end to end streaming usage, see **[A2A Client Integration](./a2a-client)**. For rendering responses, see **[Agent Responses](./agent-responses)**. # Connect CLI to Remote Server Source: https://agentstack.beeai.dev/stable/deploy-agent-stack/authenticate-cli-to-server Connect the Agent Stack CLI to your deployed server Use the Agent Stack CLI to configure and manage remote Agent Stack deployments. Agent Stack follows a client-server architecture, where the CLI acts as a client connecting to a deployed Agent Stack server. This allows you to manage agents remotely from your local machine. ## Prerequisites * Agent Stack CLI installed locally ([Quickstart](../introduction/quickstart)) * URL of your deployed Agent Stack server * Authentication credentials for the server ## Connect to a server Run this command to connect to a remote server: ```bash theme={null} agentstack server login ``` The CLI will prompt you for: * Server URL * Authentication credentials Once authenticated, the CLI connects to your specified server and saves the connection for future commands. ## View connected servers List all servers you've connected to: ```bash theme={null} agentstack server list ``` This shows all saved server connections. The active server is marked in the output. ## Switch between servers To switch to a different connected server: ```bash theme={null} agentstack server select ``` Select from your saved connections. ## Disconnect from a server Remove the currently active server connection: ```bash theme={null} agentstack server logout ``` This removes the server from your saved connections. To disconnect from a different server, switch to it first, then logout. # Agent Stack Deployment Guide Source: https://agentstack.beeai.dev/stable/deploy-agent-stack/deployment-guide Deploying Agent Stack This guide walks you through running Agent Stack in your own self-hosted environment so you can evaluate, develop, and integrate agents as backend services. Agent Stack provides a consistent runtime for agents—handling model access, storage, authentication, and service wiring—so teams can move from agent code to a running service with minimal setup. Agent Stack deploys with Kubernetes via our provided Helm chart, ensuring a standardized setup within your infrastructure. Agent Stack is designed for trusted environments and early-stage integration work. It’s well suited for local development, shared team environments, and internal evaluation. It is not intended to be directly exposed to untrusted public traffic or used as a hardened, multi-tenant production platform today. If you’re evaluating Agent Stack for broader organizational use, deploy it behind your existing network and security boundaries. If you’re an application or agent developer, this guide helps you get agents running in your deployment environment and callable from your application. If you’re part of a platform or IT team, this guide provides a concrete runtime to assess how agent services run inside your infrastructure using our Helm-based deployment, how they integrate with your security and observability tooling, and how they fit your deployment standards. Proceed to the installation guide below to configure your environment and deploy the Agent Stack cluster. ## Requirements Before deployment, ensure your environment meets these specifications: | Category | Requirement | | ------------------- | ------------------------------------------------ | | **Kubernetes** | Version 1.24+ with admin access | | **Tooling** | Helm 3.8+ and kubectl configured | | **Storage** | 20GB+ available for PostgreSQL | | **Providers** | API access for LLMs (OpenAI, Anthropic, etc.) | | **Agent Stack CLI** | Agent Stack CLI installed for post-deploy config | **OpenShift Users**: Use the [OpenShift CLI](https://docs.redhat.com/en/documentation/openshift_container_platform/4.20/html/cli_tools/openshift-cli-oc) for login and administration. ## Deployment Steps ### 1. Generate Encryption Key Agent Stack requires an encryption key for secure data handling. Generate this using Python before creating your configuration file. ```shell theme={null} # Option A: Using UV uv run --with cryptography python3 -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())' # Option B: Using python3 directly python3 -m pip install cryptography python3 -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())' # Option C: use your preferred way to install the cryptography package ``` ### 2. Create Configuration File Create a `config.yaml` file to define your registry, agents, and security keys. This is a minimal example, more advanced options are explained in the [Configuration Options](#configuration-options) section. ```yaml theme={null} # External registries for default catalogs (change release/tag accordingly): externalRegistries: public_github: "https://github.com/i-am-bee/agentstack@v0.4.3#path=agent-registry.yaml" # Custom agents as docker images providers: - location: # e.g. location: ghcr.io/i-am-bee/agentstack-starter/my-agent:latest # Use the key generated in Step 1 encryptionKey: "encryption-key-from-command" # Basic authentication settings requires passing an admin password to certain endpoints, you can disable auth for insecure deployments auth: enabled: true basic: enabled: true adminPassword: "my-secret-password" ``` ### 3. Install the Chart Deploy the stack to your cluster using Helm. ```shell theme={null} helm upgrade --install agentstack -f config.yaml oci://ghcr.io/i-am-bee/agentstack/chart/agentstack:0.4.3 ``` It may take a few minutes for the pods to start. ### 4. Port-Forwarding By default, ingress is not configured. You can port-forward the service to access the platform. In a separate terminal, run: ```shell theme={null} kubectl port-forward svc/agentstack-svc 8333:8333 & ``` ### 5. Setup the LLM After the platform becomes ready, set up your model provider (and login if auth is enabled): ```shell theme={null} # agentstack server login (if auth is enabled) agentstack model setup ``` ### 6. Test the Deployment Verify the installation by listing available agents and running a test command: ```shell theme={null} agentstack list agentstack run chat hi ``` ## Configuration Options ### Authentication & Security Settings | Option | Description | | -------------------- | ---------------------------------------------------------- | | **Disabled** | No security. Simplest for isolated testing. | | **Basic Auth** | Simple username/password (adminPassword). | | **Bundled Keycloak** | OIDC using an internal instance of Keycloak. | | **External OIDC** | Integration with existing Auth0, Okta, or Enterprise IDPs. | The current authentication model is basic and intended for development use. For any deployment beyond local testing, carefully consider your security requirements and network access controls. #### Disable Authentication For local testing environments without authentication. ```yaml theme={null} # CAUTION: INSECURE, for testing only auth: enabled: false ``` #### Basic Authentication A straightforward method requiring an admin password for certain endpoints. ```yaml theme={null} auth: enabled: true basic: enabled: true adminPassword: "my-secret-password" ``` #### Bundled Keycloak (Default OIDC) Use an internal Keycloak instance to handle OIDC and bridge to GitHub, Google, or SAML. Keycloak is deployed automatically when `auth.enabled` is `true`. ```yaml theme={null} trustProxyHeaders: true # This is important if validate_audience is enabled auth: enabled: true # Next.js settings nextauthUrl: "https://example.com" # Public URL of the UI apiUrl: "https://api.example.com" # Public URL of the API keycloak: enabled: true # Internal URL used by the server for token validation (default shown) internalIssuerUrl: "http://keycloak:8336/realms/agentstack" # Public URL used by the browser publicIssuerUrl: "https://keycloak.example.com/realms/agentstack" ``` #### External OIDC Provider Connect your own OIDC provider (e.g., Auth0, Okta, or your own Keycloak) by disabling the bundled Keycloak. ```yaml theme={null} auth: enabled: true # Next.js settings nextauthUrl: "https://example.com" # Public URL of the UI apiUrl: "https://api.example.com" # Public URL of the API keycloak: enabled: false externalOidcProvider: issuerUrl: "https://your-oidc-provider.com" name: "My IDP" # Optional display name used in the UI id: "my-idp" # Optional ID used internally rolesPath: "realm_access.roles" # JSON path to roles in JWT claims (can be an array or a semicolon separated string) uiClientId: "agentstack-ui" serverClientId: "agentstack-server" # Secrets can be provided directly or via existing secrets uiClientSecret: "..." serverClientSecret: "..." # existingSecret: "my-oidc-secret" # uiClientSecretKey: "ui-secret-key" # Key in my-oidc-secret # serverClientSecretKey: "server-secret-key" # Key in my-oidc-secret ``` **Critical: Endpoint Exposure** If using OIDC (Bundled or External), you must expose the following endpoints publicly via [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/), [Gateway API](https://kubernetes.io/docs/concepts/services-networking/gateway/), [OpenShift Routes](https://docs.redhat.com/en/documentation/openshift_container_platform/4.20/html/ingress_and_load_balancing/routes), or other tools: * UI: `https://example.com` * API: `https://api.example.com` used by the CLI to authenticate * Keycloak: `https://keycloak.example.com` used by the UI and CLI to authenticate ⚠️ Failure to configure these endpoints with proper https may leave your application vulnerable. ### Accessing & Configuring Keycloak * **Local Access**: kubectl port-forward `svc/agentstack-keycloak 8336:8336` -> Access at `http://localhost:8336` * **Production Access:** Create an [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) for the `agentstack-keycloak` service. * **Setup:** Log in (default credentials: `admin` / retrieve from secret). -> navigate to Identity Providers -> add your provider (GitHub, Google, etc.). **Email Verification Required:** Agent Stack requires the `email_verified` claim to be `true` in JWT tokens. Ensure: * Users verify emails before authenticating * The `email_verified` claim is mapped to the token (included by default in the `email` scope) * External providers pass the email verification status correctly Without verified emails, authentication will fail with "Verified email not found" error. The Agent Stack Keycloak integration is automatically provisioned with the following architecture: **Realm:** `agentstack` **Clients:** * `agentstack-server`: Backend service client (Confidential, Service Accounts enabled). * `agentstack-ui`: Frontend UI client (Confidential, Standard Flow). * `agentstack-cli`: Command Line Interface client (Public, Standard Flow). **Roles:** * `agentstack-admin`: Full system access. * `agentstack-developer`: Access to agent management. **Audiences:** * Audience mappers are dynamically configured to ensure tokens are valid for the UI and API URLs defined in your config. ## Exposing the Platform To move beyond local port-forwarding, you must expose the following services using [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) or [OpenShift Routes](https://docs.redhat.com/en/documentation/openshift_container_platform/4.20/html/ingress_and_load_balancing/configuring-routes). | Service | Internal Target | Purpose | | ------------------------------- | ----------------------- | --------------------------------------------------------- | | UI Endpoint | `agentstack-ui-svc` | Access to the web interface (includes API proxy). | | API Endpoint (optional) | `agentstack-server-svc` | Direct server API access; Required for CLI usage. | | Auth Endpoint (optional) | `agentstack-keycloak` | Required for UI and CLI authentication if using Keycloak. | ## Configuring External Services & Storage By default, Agent Stack includes PostgreSQL and SeaweedFS (S3-compatible storage). For production environments, it is highly recommended to use external, managed services. ### External PostgreSQL To use an external PostgreSQL instance, disable the built-in subchart and provide connection details. This is required if you prefer a managed database like AWS RDS or Google Cloud SQL. Example configuration: ```yaml theme={null} postgresql: enabled: false # disable builtin subchart externalDatabase: host: "" port: 5432 user: "" database: "" password: "" # Required when initContainers.createVectorDbExtension is enabled adminUser: "" adminPassword: "" ssl: true sslRootCert: "" # SSL certificate for the external database to ensure ssl connection, for example: # sslRootCert: | # -----BEGIN CERTIFICATE----- # ... # -----END CERTIFICATE----- ``` If the migration fails while installing the vector extension, you can disable automated creation and run the SQL manually. ```yaml theme={null} initContainers.createVectorDbExtension=false ``` Then make sure the following SQL statements were executed in your database: ```sql theme={null} -- Manual SQL setup for External DB CREATE EXTENSION IF NOT EXISTS vector; SET maintenance_work_mem = '512MB'; SET hnsw.ef_search = 1000; SET hnsw.iterative_scan = strict_order; SET hnsw.max_scan_tuples = 1000000; ``` ### External S3 support You may want to have Agent Stack connect to an external object storage rather than installing seaweedfs inside your cluster. This chart allows you to specify credentials for an external storage streaming with the `externalS3`. You should also disable the seaweedfs installation with the `seaweedfs.enabled` option. Example configuration: ```yaml theme={null} seaweedfs: enabled: false externalS3: host: accessKeyID: accessKeySecret: ``` ### Redis Redis is used for rate limiting and caching. While disabled by default, it is required if replicaCount > 1 to ensure counters are synchronized across replicas. **Enable Built-in Redis:** ```yaml theme={null} redis: enabled: true ``` **External Redis:** To use an external Redis instance, ensure the built-in subchart is disabled (default) and provide connection details: ```yaml theme={null} redis: enabled: false externalRedis: host: "" port: 6379 password: "" # Optional: use existing secret for password # existingSecret: "my-redis-secret" # existingSecretPasswordKey: "redis-password" ``` ## Security and Traffic Control ### Rate Limiting Rate limiting is recommended for production environments as a protection against overloading the platform. ```yaml theme={null} rateLimit: enabled: true globalLimits: - "20/second" - "100/minute" roleBasedLimits: user: openai_chat_completion_tokens: [] openai_chat_completion_requests: [] openai_embedding_inputs: [] developer: openai_chat_completion_tokens: [] openai_chat_completion_requests: [] openai_embedding_inputs: [] admin: openai_chat_completion_tokens: [] openai_chat_completion_requests: [] openai_embedding_inputs: [] strategy: "sliding-window-counter" # Options: fixed-window, moving-window, sliding-window-counter ``` If you are running multiple replicas (`replicaCount` > 1), Redis is **required** for rate limiting to work correctly across all instances. Without Redis, each replica maintains its own rate limit counters, allowing users to exceed limits by distributing requests across replicas. ### Cross-Origin Resource Sharing Configuration Agent Stack supports Cross-Origin Resource Sharing (CORS) to allow web applications hosted on different domains to interact with the Agent Stack API. ```yaml theme={null} cors: enabled: true allowOrigins: - http://localhost:3000 - https://my-ui.example.com allowMethods: - GET - POST - PUT - DELETE - OPTIONS allowHeaders: - Content-Type - Authorization allowCredentials: true ``` * `cors.enabled`: Set to `true` to enable CORS. Default is `false`. * `cors.allowOrigins`: A list of origins (e.g., `http://localhost:3000`) that are allowed to make cross-site requests to the Agent Stack API. * `cors.allowMethods`: A list of HTTP methods (e.g., `GET`, `POST`) that are allowed for cross-origin requests. Default is `["*"]`. * `cors.allowHeaders`: A list of HTTP request headers (e.g., `Content-Type`, `Authorization`) that can be used when making the actual cross-origin request. Default is `["*"]`. * `cors.allowCredentials`: Set to `true` to indicate that the client can send cookies and HTTP authentication credentials with the cross-origin request. Note that `allowOrigins` cannot be `["*"]` when `allowCredentials` is `true`. Default is `false`. ## Agent Management To manage agents within Agent Stack, you must configure how the platform accesses existing images, how it registers them for use, and how it builds new agents from source code. ### Configuring Private Access If your agent images or source code are hosted in private environments, you must provide authentication credentials to allow the cluster to retrieve them. #### Private Image Registries When using agents stored in private Docker registries, you must specify the name of a Kubernetes `custom-registry-secret`. This allows the cluster to pull the images during deployment. ```yaml theme={null} imagePullSecrets: - name: custom-registry-secret ``` #### Private GitHub Repositories For building agents from private or Enterprise GitHub repositories, Agent Stack supports both [Personal Access Token (PAT)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) and [GitHub Apps](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps). This configuration enables the build engine to clone your source code. ```yaml theme={null} github: auths: github.com: type: "pat" token: "ghp_xxxxxxxxxxxxxxxxxxxx" github.enterprise.com: type: "app" app_id: 123456 installation_id: 789012 private_key: | -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA... -----END RSA PRIVATE KEY----- ``` ### Agent Registration Once access is configured, you must define which agents are available to the platform. You can do this by listing specific images or by syncing with a remote catalog. #### Static Agent Registration Use this method to manually define specific agent images and their unique environment variables. ```yaml theme={null} providers: # Official agents - location: ghcr.io/i-am-bee/agentstack/agents/chat:0.4.3 - location: ghcr.io/i-am-bee/agentstack/agents/rag:0.4.3 - location: ghcr.io/i-am-bee/agentstack/agents/form:0.4.3 # Your custom agents - location: your-registry.com/your-team/custom-agent:v1.0.0 auto_stop_timeout_sec: 0 # disable agent downscaling # Variables should be strings (or they will be converted) variables: MY_API_KEY: "sk-..." MY_CONFIG_VAR: "42" ``` To upgrade an agent, change its version tag and redeploy using `helm upgrade`. #### External Agent Registry Instead of manual listing, you can point to a remote registry file. This is ideal for teams managing a large, dynamic catalog of agents. ```yaml theme={null} externalRegistries: public_github: "https://github.com/i-am-bee/agentstack@v0.4.3#path=agent-registry.yaml" ``` To upgrade an agent, change its version in the registry and wait for automatic synchronization (up to 10 minutes). ### Agent Builds Agents can be built from a GitHub repository directly in the cluster. To enable this feature, you will need to setup a few things: * Docker image registry credentials with write permissions, see [Private image registries](#private-image-registries) * GitHub credentials to access private or enterprise GitHub repositories (optional) * External cluster for better security (optional) ```yaml theme={null} providerBuilds: enabled: true buildBackend: "kaniko" # valid options: [kaniko, buildkit] buildRegistry: registryPrefix: "ghcr.io/github-org-name" imageFormat: "{registry_prefix}/{org}/{repo}/{path}:{commit_hash}" # Registry credentials with write access (see section about Private image registries below) secretName: "custom-registry-secret" insecure: false kaniko: useSecurityContextCapabilities: true externalClusterExecutor: serviceAccountName: "" namespace: "" # Kubernetes namespace for provider builds (defaults to current namespace if empty) kubeconfig: "" # Kubeconfig YAML content for external cluster (optional) # Example: # kubeconfig: | # apiVersion: v1 # kind: Config # clusters: # - cluster: # server: https://kubernetes.example.com # ... ``` **OpenShift Users**: In-cluster builds require a service account with the appropriate [Security Context Constraints (SCC)](https://www.redhat.com/en/blog/managing-sccs-in-openshift) to allow the elevated permissions necessary for container image construction. ## Advanced Configuration The guide above covers the primary configuration paths for production environments. For a comprehensive list of every available parameter, including low-level resource limits, node selectors, and detailed sub-chart settings, refer to the source configuration file. All configuration options, including their default values and technical descriptions, are documented in the [values.yaml file](https://github.com/i-am-bee/agentstack/blob/v0.4.3/helm/values.yaml) within the official repository. If your infrastructure has specific requirements (such as custom sidecars, unique volume mounts, or specific network policies) that are not currently exposed via the Helm chart, please open a GitHub issue to request a new configuration toggle. ## Operations & Maintenance ### Management Commands ### Upgrading Once Agent Stack is running, use these Helm and `kubectl` commands to manage the lifecycle of your deployment. | Task | Command | | ------------------ | --------------------------------------------------------------------------------------------------------------- | | Upgrade Platform | `helm upgrade --install agentstack -f config.yaml oci://ghcr.io/i-am-bee/agentstack/chart/agentstack:` | | View Active Config | `helm get values agentstack` | | Deployment Status | `helm status agentstack` | | Resource Health | `kubectl get pods` , `kubectl logs deployment/agentstack-server` | | Uninstall | `helm uninstall agentstack` | ## Troubleshooting If you encounter issues during or after deployment, use the following commands and checklists to diagnose the problem. ### Common Issues **Platform not starting:** ```bash theme={null} # Check pod status kubectl get pod # Check server logs kubectl logs deployment/agentstack-server # If server is not starting, check specific init container logs (e.g. migrations) kubectl logs deployment/agentstack-server -c run-migrations # Check events kubectl get events --sort-by=.lastTimestamp ``` **Authentication issues:** * **Redirect URIs:** Your Identity Provider must whitelist `https:///api/auth/callback`. For CLI usage, you must also whitelist `http://localhost:9001/callback`. * **CLI Application:** Consider creating a separate public OIDC application for the CLI * **Audience Claim**: The `audience` claim in your JWT must match the public URL of your application exactly, with no trailing slash (e.g., `https://example.com`). * **Proxy Headers:** If you are behind a load balancer or ingress, `trustProxyHeaders`: true must be enabled in your config to correctly forward request URLs. # Webhooks Source: https://agentstack.beeai.dev/stable/deploy-agent-stack/webhooks Receive real-time notifications when resources change Agent Stack can send outgoing webhook notifications when resources are created, updated, or deleted. This allows external systems to react to changes without polling the API. Webhooks are configured statically via Helm chart values. Each endpoint receives a JSON payload via HTTP POST for matching events. Delivery is fire-and-forget — the server does not retry failed deliveries or block on slow endpoints. ## Configuration Add webhook endpoints to your `config.yaml`: ```yaml theme={null} webhook: endpoints: - url: "https://example.com/webhook" headers: Authorization: "Bearer my-secret-token" events: ["*"] ``` | Field | Description | | --------- | ---------------------------------------------------------------------------------------------------- | | `url` | The endpoint URL to receive POST requests. | | `headers` | HTTP headers sent with each request. Use this for authentication (e.g. `Authorization: Bearer ...`). | | `events` | List of event patterns to subscribe to. Defaults to `["*"]` (all events). | ## Event filtering Each endpoint can subscribe to specific events using pattern matching: | Pattern | Matches | | ------------------ | -------------------------------------------------------------------------------- | | `*` | All events | | `provider.*` | All provider events (`provider.created`, `provider.updated`, `provider.deleted`) | | `provider.created` | Only provider creation events | ## Payload Every webhook POST delivers a JSON body with the following structure: ```json theme={null} { "event": "provider.created", "resource_type": "provider", "resource_id": "550e8400-e29b-41d4-a716-446655440000", "resource_url": "/api/v1/providers/550e8400-e29b-41d4-a716-446655440000", "user_id": "660e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-03-26T12:00:00+00:00" } ``` | Field | Description | | --------------- | -------------------------------------------------------- | | `event` | The event type (e.g. `provider.created`). | | `resource_type` | The type of resource that changed. | | `resource_id` | UUID of the affected resource. | | `resource_url` | API path to fetch the resource. | | `user_id` | UUID of the user who triggered the change, if available. | | `timestamp` | ISO 8601 timestamp of when the event was dispatched. | Payloads intentionally do not include the resource data itself. Use the `resource_url` to fetch the current state of the resource from the API. ## Available events | Resource | Events | | -------------------- | ------------------------------- | | `context` | `created`, `updated`, `deleted` | | `connector` | `created`, `updated`, `deleted` | | `file` | `created`, `deleted` | | `file_extraction` | `created`, `deleted` | | `model_provider` | `created`, `updated`, `deleted` | | `provider` | `created`, `updated`, `deleted` | | `provider_build` | `created`, `updated`, `deleted` | | `provider_discovery` | `created` | | `user` | `created`, `updated`, `deleted` | | `user_feedback` | `created` | | `vector_store` | `created`, `updated`, `deleted` | ## Example: multiple endpoints with filtered events ```yaml theme={null} webhook: endpoints: - url: "https://ci.example.com/hooks/agentstack" headers: Authorization: "Bearer ci-token" events: ["provider_build.*"] - url: "https://monitoring.example.com/ingest" headers: X-API-Key: "monitoring-key" events: ["*"] ``` # MCP Connectors Source: https://agentstack.beeai.dev/stable/experimental/connectors Create and manage data connections to external services Connectors are remote MCP servers registered into the Agent Stack. Effectively, they take some responsibilities away from MCP Clients to improve user experience and reduce overhead within the Agent Stack. Once connector is created and connected, it can be used as any regular remote MCP server by both clients and agents. ## API Reference Core of connectors API consists of the following endpoints: | Endpoint | Purpose | | ----------------------------------------- | ---------------------- | | `POST /api/v1/connectors` | Create a connector | | `GET /api/v1/connectors` | List all connectors | | `GET /api/v1/connectors/{id}` | Read a connector | | `DELETE /api/v1/connectors/{id}` | Delete a connector | | `POST /api/v1/connectors/{id}/connect` | Connect a connector | | `POST /api/v1/connectors/{id}/disconnect` | Disconnect a connector | The connector can then be used by any MCP client at `/api/v1/connectors/{id}/mcp` endpoint. For example, to inspect a connector using MCP Inspector: ```bash theme={null} npx -y @modelcontextprotocol/inspector --transport http --server-url http://localhost:8333/api/v1/connectors/{id}/mcp ``` Additionally, the connectors API exposes a catalog of preconfigured presets for common connectors: | Endpoint | Purpose | | -------------------------------- | ---------------------- | | `GET /api/v1/connectors/presets` | List connector presets | ## Lifecycle Following diagram illustrates the lifecycle of a connector: ```mermaid theme={null} stateDiagram-v2 [*] --> created: Create created --> auth_required: Connect (auth) created --> connected: Connect (no auth) auth_required --> connected: oAuth redirect connected --> disconnected: Disconnect, auth expired, probe failed, ... disconnected --> [*]: Delete disconnected --> connected: Connect (no auth) disconnected --> auth_required: Connect (auth) ``` Usual flow works as follows: 1. **Create**: Client creates a connector by calling `POST /api/v1/connectors` with MCP server URL. 2. **Connect**: Client initiates connection by calling `POST /api/v1/connectors/{id}/connect`. * For OAuth-enabled servers: The response will contain an authorization URL for the user to complete authentication * For token-based authentication: Provide an `access_token` in the connect request body (see [Authentication](#authentication)) 3. **Authorize** (OAuth only): User visits the authorization URL, authenticates and grants access. Authorization server redirects the user back to the platform with an authorization code. 4. **Complete**: Platform exchanges the authorization code for access and refresh tokens (OAuth) or stores the provided access token. Once completed, the connector is in `connected` state and ready to be used. ## Authentication Connectors support two authentication methods: ### OAuth (External MCP Servers) For MCP servers that support OAuth, the platform handles the full authorization code flow. No additional configuration is needed in the connect request. ### Token-based Authentication For MCP servers that use simple token-based authentication, provide the token when connecting: ```json theme={null} POST /api/v1/connectors/{id}/connect { "access_token": "YOUR_API_TOKEN" } ``` The authentication token is used differently depending on the connector type: * **External HTTP/HTTPS MCP servers**: Token is sent as a Bearer token in the `Authorization` header for all requests * **Managed stdio MCP servers**: Token is injected as an environment variable in the container (requires `access_token_env_name` in preset configuration) ## Error handling The connectors API endpoints return standard HTTP status codes in responses. Apart from that, there are two additional mechanisms used by connectors API to relay errors to the client. ### Authorization Code Flow An error may happen during the authorization code flow as described in [RFC6749 Section 4.1](https://www.rfc-editor.org/rfc/rfc6749#section-4.1.2.1). When no `redirect_url` has been provided by the client during connection initiation, the server responds with a HTML page containing the error. Otherwise, the user will be redirected to `redirect_url` instead. The `error` and `error_description` query parameters will be included in the redirect. ### Disconnection Connector can be asynchronously disconnected at any time. This can happen for various reasons including intentional disconnect, refresh token expiration or an arbitrary error. The client MAY check for the `disconnected` state and read `disconnect_reason` to read the description of what happened. ## Connector Presets Connector presets provide pre-configured settings for common MCP servers, simplifying the connector creation process. Presets can include: * **URL**: The MCP server endpoint (supports `http://`, `https://`, and `mcp+stdio://` schemes) * **Client credentials**: Pre-configured OAuth `client_id` and `client_secret` for public clients * **Metadata**: Display information such as name and description * **Stdio configuration**: For `mcp+stdio://` URLs, container image and runtime settings for managed MCP servers ### Available Presets The platform comes with several built-in presets: | MCP Server | URL | Description | | ---------- | ----------------------------------- | ----------------------------------------------------------------------- | | Stripe | `https://mcp.stripe.com` | Payment processing and financial infrastructure tools | | Box | `https://mcp.box.com` | Search, access and get insights on your Box content | | GitHub | `https://api.githubcopilot.com/mcp` | Access and interact with your GitHub repositories and code intelligence | ### Using Presets When creating a connector with `POST /connectors`, the system automatically matches the provided URL against available presets (this behavior is controlled by the `match_preset` parameter, which defaults to `true`). If a match is found: 1. **Client credentials**: If no `client_id` is provided in the request, the preset's credentials are used automatically 2. **Metadata**: If no metadata is provided in the request, the preset's metadata (name, description) is used This allows for simplified connector creation. For example, to create a GitHub connector: ```json theme={null} POST /connectors { "url": "https://api.githubcopilot.com/mcp" } ``` The system will automatically apply the preset's client credentials and metadata. To disable preset matching and provide all credentials manually, set `match_preset: false` in the request. ### Configuring Presets Connector presets are configurable via Helm values when deploying Agent Stack. The presets are defined in the `connector.presets` section of `values.yaml`: #### Remote MCP Servers (HTTP/HTTPS) For MCP servers accessible over HTTP/HTTPS with Streamable HTTP transport: ```yaml theme={null} connector: presets: - url: "https://mcp.stripe.com" metadata: name: "Stripe" description: "Payment processing and financial infrastructure tools" - url: "https://mcp.box.com" client_id: "YOUR_CLIENT_ID" client_secret: "YOUR_CLIENT_SECRET" metadata: name: "Box" description: "Search, access and get insights on your Box content" ``` #### Managed MCP Servers (stdio) For MCP servers that use stdio transport, Agent Stack can manage them as Kubernetes deployments using the `mcp+stdio://` scheme. The platform automatically: * Deploys the MCP server as a Kubernetes pod with a sidecar container running [supergateway](https://github.com/supercorp-ai/supergateway) * Exposes the stdio MCP server over Streamable HTTP transport via supergateway * Manages the deployment lifecycle (creates on connect, deletes on disconnect) * Handles authentication tokens via environment variables ```yaml theme={null} connector: presets: - url: "mcp+stdio://example-mcp" metadata: name: "Example MCP" description: "Example stdio-based MCP server" stdio: image: "registry.example.com/mcp-server:latest" command: ["node"] # Optional: override container command args: ["dist/index.js"] # Optional: override container args env: # Optional: additional environment variables LOG_LEVEL: "info" access_token_env_name: "API_TOKEN" # Optional: env var name to inject access token ``` The managed MCP architecture uses a sidecar pattern: * **MCP Server Container**: Runs your stdio-based MCP server * **Supergateway Sidecar**: Wraps the stdio interface and exposes it as Streamable HTTP at port 8080 When a connector using a managed preset is connected, the platform: 1. Creates a Kubernetes Deployment with both containers 2. Creates a Kubernetes Service to expose the supergateway 3. Waits for the deployment to become ready (up to 60 seconds) 4. Proxies MCP requests to the managed service 5. If an `accessToken` was provided in the connect request and the preset defines `access_token_env_name`, injects the token as an environment variable When disconnected, the platform cleans up the Deployment and Service resources. #### Configuration Details The presets are injected into the platform via the `CONNECTOR__PRESETS` environment variable, which is populated from a Kubernetes Secret created by Helm. This allows administrators to: * Add custom MCP server presets for their organization (both remote and managed) * Modify or remove default presets * Configure client credentials for remote MCP servers with OAuth * Configure stdio container images and settings for managed MCP servers * Customize metadata (names, descriptions) for better user experience After modifying preset configuration in Helm values, redeploy the platform for changes to take effect. ## Python SDK The AgentStack Python SDK provides a convenient wrapper around the connectors API, handling common patterns and reducing boilerplate. While it maps directly to API endpoints, the SDK adds extra functionality for smoother interaction patterns such as methods to wait until a connector reaches a desired state or until it is deleted. ### Getting Started ```python theme={null} from agentstack_sdk.platform import Connector, ConnectorState ``` ### Core Operations #### Create a Connector ```python theme={null} connector = await Connector.create( url="https://api.githubcopilot.com/mcp", client_id="optional_id", client_secret="optional_secret", metadata={"custom": "data"}, match_preset=True ) ``` #### Delete a Connector ```python theme={null} await connector.delete() await connecrtor.wait_for_deletion(). # Wait until the connector is deleted from the platform ``` #### List Connectors ```python theme={null} result = await Connector.list() for connector in result.items: print(f"{connector.id}: {connector.url} ({connector.state})") ``` #### Get or Refresh a Connector ```python theme={null} # Get a specific connector connector = await Connector.get(connector_id) # Refresh an existing connector instance connector = await connector.refresh() ``` #### List Presets ```python theme={null} presets = await Connector.presets() for preset in presets.items: print(preset) ``` ### Authentication & Connection #### OAuth Authentication For OAuth-enabled servers, the SDK automatically opens the browser when `connect()` is called. Use `wait_for_state()` to poll until authorization completes. ```python theme={null} # Connect with OAuth (browser will open automatically) connector = await connector.connect( redirect_url="https://localhost:8080/callback" # Optional ) # Wait for authorization to complete connector = await connector.wait_for_state( state=ConnectorState.connected, poll_interval=1 # Optional, seconds between polls ) ``` #### Token-Based Authentication For services that use access tokens instead of OAuth: ```python theme={null} connector = await Connector.create(url="https://api.example.com/mcp") connector = await connector.connect(access_token="your_api_token") connector = await connector.wait_for_state(state=ConnectorState.connected) ``` #### Disconnect ```python theme={null} await connector.disconnect() connector = await connector.wait_for_state(state=ConnectorState.disconnected) ``` ### Advanced Features #### Stream MCP Proxy Requests ```python theme={null} response_chunks = [] async for response in connector.mcp_proxy( method="POST", headers={"Accept": "application/json, text/event-stream"}, content=json.dumps(mcp_request).encode() ): response_chunks.append(response.chunk) response_text = b"".join(response_chunks).decode() ``` ### Complete Workflow Example ```python theme={null} import asyncio from agentstack_sdk.platform import Connector, ConnectorState async def main(): # Create connector connector = await Connector.create(url="https://mcp.stripe.com") # Connect (browser opens for OAuth if needed) connector = await connector.connect() connector = await connector.wait_for_state(state=ConnectorState.connected) # Use the connector for k, v in connector.model_dump().items(): print(f"{k}: {v}") # Cleanup await connector.disconnect() await connector.delete() await connector.wait_for_deletion() asyncio.run(main()) ``` Check out the repository for more examples. # Jwks Source: https://agentstack.beeai.dev/api-reference/auth/jwks /development/api-reference/openapi.json get /.well-known/jwks # Protected Resource Metadata Source: https://agentstack.beeai.dev/api-reference/auth/protected-resource-metadata /development/api-reference/openapi.json get /.well-known/oauth-protected-resource/{resource} # Connect Connector Source: https://agentstack.beeai.dev/api-reference/connectors/connect-connector /development/api-reference/openapi.json post /api/v1/connectors/{connector_id}/connect # Create Connector Source: https://agentstack.beeai.dev/api-reference/connectors/create-connector /development/api-reference/openapi.json post /api/v1/connectors # Delete Connector Source: https://agentstack.beeai.dev/api-reference/connectors/delete-connector /development/api-reference/openapi.json delete /api/v1/connectors/{connector_id} # Disconnect Connector Source: https://agentstack.beeai.dev/api-reference/connectors/disconnect-connector /development/api-reference/openapi.json post /api/v1/connectors/{connector_id}/disconnect # List Connectors Source: https://agentstack.beeai.dev/api-reference/connectors/list-connectors /development/api-reference/openapi.json get /api/v1/connectors # List Presets Source: https://agentstack.beeai.dev/api-reference/connectors/list-presets /development/api-reference/openapi.json get /api/v1/connectors/presets # Mcp Source: https://agentstack.beeai.dev/api-reference/connectors/mcp /development/api-reference/openapi.json get /api/v1/connectors/{connector_id}/mcp # Mcp Source: https://agentstack.beeai.dev/api-reference/connectors/mcp-1 /development/api-reference/openapi.json post /api/v1/connectors/{connector_id}/mcp # Oauth Callback Source: https://agentstack.beeai.dev/api-reference/connectors/oauth-callback /development/api-reference/openapi.json get /api/v1/connectors/oauth/callback # Read Connector Source: https://agentstack.beeai.dev/api-reference/connectors/read-connector /development/api-reference/openapi.json get /api/v1/connectors/{connector_id} # Add Context History Item Source: https://agentstack.beeai.dev/api-reference/contexts/add-context-history-item /development/api-reference/openapi.json post /api/v1/contexts/{context_id}/history # Create Context Source: https://agentstack.beeai.dev/api-reference/contexts/create-context /development/api-reference/openapi.json post /api/v1/contexts # Delete Context Source: https://agentstack.beeai.dev/api-reference/contexts/delete-context /development/api-reference/openapi.json delete /api/v1/contexts/{context_id} # Delete Context History From Id Source: https://agentstack.beeai.dev/api-reference/contexts/delete-context-history-from-id /development/api-reference/openapi.json delete /api/v1/contexts/{context_id}/history # Generate Context Token Source: https://agentstack.beeai.dev/api-reference/contexts/generate-context-token /development/api-reference/openapi.json post /api/v1/contexts/{context_id}/token # Get Context Source: https://agentstack.beeai.dev/api-reference/contexts/get-context /development/api-reference/openapi.json get /api/v1/contexts/{context_id} # List Context Source: https://agentstack.beeai.dev/api-reference/contexts/list-context /development/api-reference/openapi.json get /api/v1/contexts # List Context History Source: https://agentstack.beeai.dev/api-reference/contexts/list-context-history /development/api-reference/openapi.json get /api/v1/contexts/{context_id}/history # Patch Context Metadata Source: https://agentstack.beeai.dev/api-reference/contexts/patch-context-metadata /development/api-reference/openapi.json patch /api/v1/contexts/{context_id}/metadata # Update Context Source: https://agentstack.beeai.dev/api-reference/contexts/update-context /development/api-reference/openapi.json put /api/v1/contexts/{context_id} # Create Text Extraction Source: https://agentstack.beeai.dev/api-reference/files/create-text-extraction /development/api-reference/openapi.json post /api/v1/files/{file_id}/extraction Create or return text extraction for a file. - If extraction is completed, returns existing result - If extraction failed, retries the extraction - If extraction is pending/in-progress, returns current status - If no extraction exists, creates a new one # Delete File Source: https://agentstack.beeai.dev/api-reference/files/delete-file /development/api-reference/openapi.json delete /api/v1/files/{file_id} # Delete Text Extraction Source: https://agentstack.beeai.dev/api-reference/files/delete-text-extraction /development/api-reference/openapi.json delete /api/v1/files/{file_id}/extraction # Get File Source: https://agentstack.beeai.dev/api-reference/files/get-file /development/api-reference/openapi.json get /api/v1/files/{file_id} # Get File Content Source: https://agentstack.beeai.dev/api-reference/files/get-file-content /development/api-reference/openapi.json get /api/v1/files/{file_id}/content # Get Text Extraction Source: https://agentstack.beeai.dev/api-reference/files/get-text-extraction /development/api-reference/openapi.json get /api/v1/files/{file_id}/extraction # Get Text File Content Source: https://agentstack.beeai.dev/api-reference/files/get-text-file-content /development/api-reference/openapi.json get /api/v1/files/{file_id}/text_content # Create Chat Completion Source: https://agentstack.beeai.dev/api-reference/openai/create-chat-completion /development/api-reference/openapi.json post /api/v1/openai/chat/completions # Create Embedding Source: https://agentstack.beeai.dev/api-reference/openai/create-embedding /development/api-reference/openapi.json post /api/v1/openai/embeddings # List Models Source: https://agentstack.beeai.dev/api-reference/openai/list-models /development/api-reference/openapi.json get /api/v1/openai/models # List User Feedback Source: https://agentstack.beeai.dev/api-reference/user_feedback/list-user-feedback /development/api-reference/openapi.json get /api/v1/user_feedback # User Feedback Source: https://agentstack.beeai.dev/api-reference/user_feedback/user-feedback /development/api-reference/openapi.json post /api/v1/user_feedback # List User Variables Source: https://agentstack.beeai.dev/api-reference/variables/list-user-variables /development/api-reference/openapi.json get /api/v1/variables # Update User Variables Source: https://agentstack.beeai.dev/api-reference/variables/update-user-variables /development/api-reference/openapi.json put /api/v1/variables # Add Items Source: https://agentstack.beeai.dev/api-reference/vector_stores/add-items /development/api-reference/openapi.json put /api/v1/vector_stores/{vector_store_id} # Create Vector Store Source: https://agentstack.beeai.dev/api-reference/vector_stores/create-vector-store /development/api-reference/openapi.json post /api/v1/vector_stores Create a new vector store. # Delete Document Source: https://agentstack.beeai.dev/api-reference/vector_stores/delete-document /development/api-reference/openapi.json delete /api/v1/vector_stores/{vector_store_id}/documents/{document_id} Delete a document by ID. # Delete Vector Store Source: https://agentstack.beeai.dev/api-reference/vector_stores/delete-vector-store /development/api-reference/openapi.json delete /api/v1/vector_stores/{vector_store_id} Delete a vector store by ID. # Get Vector Store Source: https://agentstack.beeai.dev/api-reference/vector_stores/get-vector-store /development/api-reference/openapi.json get /api/v1/vector_stores/{vector_store_id} Get a vector store by ID. # List Documents Source: https://agentstack.beeai.dev/api-reference/vector_stores/list-documents /development/api-reference/openapi.json get /api/v1/vector_stores/{vector_store_id}/documents List all documents in a vector store. # Search With Vector Source: https://agentstack.beeai.dev/api-reference/vector_stores/search-with-vector /development/api-reference/openapi.json post /api/v1/vector_stores/{vector_store_id}/search Search a vector store using either text or a vector.