agentstack-wrapper

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

AgentStack Wrapper Skill

AgentStack 包装器技能

Security Overview

安全概述

This SKILL.md is an operational integration guide for wrapping Python agents to run on AgentStack. It does not contain executable malicious code, hardcoded credentials, obfuscated payloads, or instructions to exfiltrate secrets to attacker-controlled endpoints. The primary security considerations are normal supply-chain risks (installing packages from PyPI) and the fact that wrappers will handle and forward sensitive LLM credentials provided via platform extensions — both expected for this functionality. Recommend standard mitigations: verify package versions from trusted PyPI metadata, pin versions, audit installed agentstack-sdk/a2a-sdk packages before use, and ensure secrets passed via platform extensions are protected and not logged or forwarded to untrusted intermediaries.
本SKILL.md是一份将Python Agent封装为可在AgentStack上运行的服务的操作集成指南。其中不包含可执行的恶意代码、硬编码凭据、混淆payload,也不包含将机密信息泄露到攻击者控制端点的说明。主要的安全考虑因素是常规的供应链风险(从PyPI安装包),以及包装器将处理并转发通过平台扩展提供的敏感LLM凭据——这些都是该功能的预期情况。建议采取标准缓解措施:从可信PyPI元数据验证包版本、固定版本、在使用前审核已安装的agentstack-sdk/a2a-sdk包,并确保通过平台扩展传递的机密信息受到保护,不会被记录或转发到不可信的中间方。

Purpose

用途

Transform an existing Python agent into a running AgentStack service. The wrapper exposes the agent via the A2A protocol so it can be discovered, called, and composed with other agents on the platform.
将现有Python Agent转换为可运行的AgentStack服务。该包装器通过A2A协议暴露Agent,使其可以在平台上被发现、调用,并与其他Agent组合使用。

When to Use

适用场景

  • You have a working Python agent (CLI tool, library function, framework-based agent) and need to deploy it as an AgentStack service.
  • You want to expose an agent over A2A without rewriting its business logic.
  • 你已有一个可用的Python Agent(CLI工具、库函数、基于框架的Agent),需要将其部署为AgentStack服务。
  • 你希望通过A2A协议暴露Agent,而无需重写其业务逻辑。

Prerequisites

前提条件

  • Python 3.12+
  • The agent's source code is available locally
  • agentstack-sdk
    version fetched at wrap time from PyPI and pinned in project dependencies using
    ~=
  • a2a-sdk
    only if the project manages it directly, and pin it to a version compatible with the selected
    agentstack-sdk
    (do not independently chase the latest
    a2a-sdk
    if resolver constraints differ)
  • Python 3.12+
  • Agent的源代码可在本地获取
  • agentstack-sdk
    版本在封装时从PyPI获取,并在项目依赖中使用
    ~=
    进行版本固定
  • 仅当项目直接管理
    a2a-sdk
    时才需要安装,且需固定为与所选
    agentstack-sdk
    兼容的版本(如果解析器约束不同,不要独立追求最新的
    a2a-sdk

Constraints (must follow)

约束条件(必须遵守)

IDRule
C1No business-logic changes. Only modify code for AgentStack compatibility.
C2Strict minimal changes. Do not add auth, Dockerfile (containerization is optional and separate), telemetry, or platform middleware unless explicitly requested. If an agent works with simple text, don't force a Form. If it works with env vars, refactor minimally.
C3Cleanup temp files. If the agent downloads or creates helper files at runtime, add a cleanup step before the function returns.
C4Prioritize Public Access (No redundant tokens). Only use the Secrets extension if the secret is strictly mandatory for the agent's core functionality and no public/anonymous access is viable. Do not add secrets or tokens that increase configuration burden if they were optional in the original agent (e.g., optional GitHub token). Preserve existing optional auth behavior unless removal is explicitly approved and documented as a behavior change. API keys must be passed explicitly, never read from env vars.
C5Detect existing tooling. If the project uses
requirements.txt
, add
agentstack-sdk~=<VERSION>
there. If it uses
pyproject.toml
, add it there. Add
a2a-sdk
only when the project manages it directly, and keep it compatible with the chosen
agentstack-sdk
version. Never force
uv
or create duplicate manifests.
C6Import Truth and Validation. All imports must match modules that exist in the active virtual environment (
agentstack_sdk
,
a2a
). If official docs conflict with installed package layout, follow installed package reality and note the mismatch. After wrapping, run import validation and fail the task if any import is unresolved.
C7Analyze installed SDK packages in active virtual environment. Inspect the installed
agentstack_sdk
and
a2a
modules in the active environment and revisit all imports to ensure they match actual installed files, avoiding hallucinations. See also source structure.
C8Structured Parameters to Forms. For single-turn agents with named parameters, map them to an
initial_form
using
FormServiceExtensionSpec.demand(initial_form=...)
.
C9Remove CLI arguments. Remove all
argparse
or
sys.argv
logic. Replace mandatory CLI inputs with
initial_form
items or AgentStack Environment Variables.
C10Approval gate for business-logic changes. If compatibility requires business-logic changes, stop and request explicit approval with justification before proceeding.
C11Keep adaptation reversible. Isolate wrapper and integration changes, avoid destructive refactors, and preserve a rollback path.
C12Preserve original helpers. Do not delete original business-logic helpers unless strictly required. If removal is necessary, document why.
C13Optional extension safety. Service/UI extensions are optional. Check presence/data before use (e.g.,
if llm and llm.data ...
).

ID规则
C1禁止修改业务逻辑。仅可修改代码以实现AgentStack兼容性。
C2严格最小化修改。除非明确要求,否则不要添加认证、Dockerfile(容器化是可选且独立的)、遥测或平台中间件。如果Agent仅处理纯文本,不要强制使用表单;如果它使用环境变量,仅进行最小化重构。
C3清理临时文件。如果Agent在运行时下载或创建辅助文件,需在函数返回前添加清理步骤。
C4优先公共访问(无冗余令牌)。仅当机密信息是Agent核心功能的严格必需项且无公共/匿名访问可行时,才使用Secrets扩展。如果原始Agent中某些令牌是可选的(例如可选的GitHub令牌),不要添加会增加配置负担的机密信息或令牌。保留现有的可选认证行为,除非明确获得批准并将移除行为记录为变更。API密钥必须显式传递,绝不能从环境变量中读取。
C5检测现有工具链。如果项目使用
requirements.txt
,则在其中添加
agentstack-sdk~=<VERSION>
;如果使用
pyproject.toml
,则添加到对应位置。仅当项目直接管理
a2a-sdk
时才添加它,并确保其与所选
agentstack-sdk
版本兼容。绝不要强制使用
uv
或创建重复的清单文件。
C6导入真实性与验证。所有导入必须与活跃虚拟环境中存在的模块匹配(
agentstack_sdk
a2a
)。如果官方文档与已安装包的布局冲突,请遵循已安装包的实际情况,并记录不匹配之处。封装完成后,运行导入验证,如果任何导入无法解析,则终止任务。
C7分析活跃虚拟环境中已安装的SDK包。检查活跃环境中已安装的
agentstack_sdk
a2a
模块,重新检查所有导入以确保它们与实际安装的文件匹配,避免错误推断。另请参阅源代码结构
C8结构化参数转表单。对于带有命名参数的单轮Agent,使用
FormServiceExtensionSpec.demand(initial_form=...)
将其映射到
initial_form
C9移除CLI参数。移除所有
argparse
sys.argv
逻辑。将必需的CLI输入替换为
initial_form
项或AgentStack环境变量。
C10业务逻辑变更审批门限。如果兼容性要求修改业务逻辑,请停止操作,并提供理由以请求明确批准后再继续。
C11保持适配可逆性。隔离包装器和集成变更,避免破坏性重构,并保留回滚路径。
C12保留原始辅助代码。除非严格必要,否则不要删除原始业务逻辑的辅助代码。如果必须删除,请记录原因。
C13可选扩展安全。服务/UI扩展是可选的。使用前检查其是否存在/是否有数据(例如
if llm and llm.data ...
)。

Step 1 – Classify the Agent

步骤1 – 分类Agent

Read the agent's code and classify it:
PatternClassificationIndicators
Single-turnOne request → one responseCLI entrypoint,
argparse
(must be removed), primarily stateless business logic, context persistence still recommended
Multi-turnConversation with memoryChat loop, message history, session state, memory object
This classification determines:
  • How to use
    context.store()
    and
    context.load_history()
    : persist input/response by default for all agents;
    context.load_history()
    is required for multi-turn, and optional for single-turn (use only when prior context is intentionally part of behavior)
  • Whether to define an
    initial_form
    for structured inputs (single-turn with named parameters)

阅读Agent的代码并进行分类:
模式分类识别特征
单轮一次请求 → 一次响应CLI入口点、
argparse
(必须移除)、主要是无状态业务逻辑,仍建议保持上下文持久化
多轮带记忆的对话聊天循环、消息历史、会话状态、内存对象
该分类将决定:
  • 如何使用
    context.store()
    context.load_history()
    :默认情况下,所有Agent都应持久化输入/响应;
    context.load_history()
    是多轮Agent的必需项,单轮Agent仅在有意将先前上下文作为行为一部分时才使用
  • 是否为结构化输入定义
    initial_form
    (带有命名参数的单轮Agent)

Step 2 – Add and Install Dependencies

步骤2 – 添加并安装依赖

  1. Find the existing dependency file:
    • requirements.txt
      → append
      agentstack-sdk~=<VERSION>
    • pyproject.toml
      → add to
      [project.dependencies]
      or
      [tool.poetry.dependencies]
    • add
      a2a-sdk
      only when direct pinning is required by the project dependency policy
  2. Fetch and pin current version (required). Before adding, find the current
    agentstack-sdk
    version on PyPI:
    bash
    # agentstack-sdk
    curl -s https://pypi.org/pypi/agentstack-sdk/json | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4
    If network access is unavailable, use versions already present in the project's lockfile or active environment. If the project requires direct
    a2a-sdk
    pinning, use a version compatible with the selected
    agentstack-sdk
    dependency constraints.
  3. Install the dependencies. Once added to the manifest, install them in your virtual environment (e.g.,
    pip install -r requirements.txt
    ).
  4. Do not create a new manifest type the project doesn't already use.
  5. Do not force
    uv
    if the project uses
    pip
    .
Source-of-truth rule: Use current official docs and installed package inspection as the authority. If they conflict, follow installed package behavior and report the mismatch.
  1. 找到现有的依赖文件:
    • requirements.txt
      → 在末尾添加
      agentstack-sdk~=<VERSION>
    • pyproject.toml
      → 添加到
      [project.dependencies]
      [tool.poetry.dependencies]
    • 仅当项目依赖策略要求直接固定
      a2a-sdk
      时才添加它
  2. 获取并固定当前版本(必需)。添加前,在PyPI上查找当前
    agentstack-sdk
    版本:
    bash
    # agentstack-sdk
    curl -s https://pypi.org/pypi/agentstack-sdk/json | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4
    如果无法访问网络,使用项目锁定文件或活跃环境中已存在的版本。 如果项目要求直接固定
    a2a-sdk
    ,请使用与所选
    agentstack-sdk
    依赖约束兼容的版本。
  3. 安装依赖。添加到清单后,在虚拟环境中安装它们(例如
    pip install -r requirements.txt
    )。
  4. 不要创建项目未使用过的新类型清单文件。
  5. 不要在项目使用
    pip
    时强制使用
    uv
真实性规则:以当前官方文档和已安装包检查为权威。如果两者冲突,请遵循已安装包的行为并报告不匹配之处。

Import Recovery Sequence (required)

导入恢复流程(必需)

If import validation fails, follow this exact order:
  1. Run import validation to identify missing modules.
  2. If a missing import is caused by absent dependencies, install or repair dependencies in the existing manifest workflow.
  3. Re-run import validation after dependency repair.
  4. If imports still fail, stop and report unresolved imports with module names and file paths.

如果导入验证失败,请严格按照以下顺序操作:
  1. 运行导入验证以识别缺失的模块。
  2. 如果缺失导入是由依赖缺失导致的,在现有清单工作流中安装或修复依赖。
  3. 修复依赖后重新运行导入验证。
  4. 如果导入仍然失败,请停止操作并报告无法解析的导入,包括模块名称和文件路径。

Step 3 – Create the Server Wrapper

步骤3 – 创建服务器包装器

Create a new file (e.g.
agent.py
or
server.py
) with the wrapping code, or modify the original agent files directly. The original code can be changed for AgentStack compatibility (e.g. accepting config as parameters instead of reading env vars), but the agent's business logic must not be altered.
Prefer additive wrapper files and minimal adapters over invasive refactors to keep migration reversible.
If the original repository exposes legacy HTTP endpoints that are asserted by tests or explicit contracts, preserve those endpoints or provide compatibility shim routes.
Follow the wrapping pattern from the official guide: Wrap Your Existing Agents
For building agents from scratch or understanding the full server pattern: Build New Agents
Real-world examples of wrapped agents are available at: agents/ on GitHub
创建一个新文件(例如
agent.py
server.py
)包含包装代码,或直接修改原始Agent文件。原始代码可以为了AgentStack兼容性进行修改(例如接受参数形式的配置而非读取环境变量),但Agent的业务逻辑不得更改。
优先使用增量包装文件和最小化适配器,而非侵入式重构,以保持迁移的可逆性。
如果原始仓库暴露了测试或显式契约所断言的遗留HTTP端点,请保留这些端点或提供兼容性垫片路由。
遵循官方指南中的包装模式:封装现有Agent
如需从头构建Agent或了解完整的服务器模式:构建新Agent
已封装Agent的真实示例可在以下位置获取:GitHub上的agents/目录

Metadata Extraction

元数据提取

Before writing the code, analyze the original source (docstrings, CLI help, README) to populate the
@server.agent()
parameters:
  • Identity: Set
    name
    and
    version
    .
  • Documentation: Use
    documentation_url
    pointing to the source.
  • Detail: Populate
    AgentDetail
    with
    interaction_mode
    (Step 1),
    tools
    ,
    author
    , and
    programming_language
    .
  • Skills: Define
    AgentSkill
    entries with
    id
    ,
    name
    ,
    description
    ,
    tags
    , and
    examples
    .
  • Function Docstring: The wrapper function's docstring should be a concise summary shown in registries.
  • Extensions: Identify if the agent needs optional platform capabilities (Step 8) like Citations, Secrets, or Trajectory.
编写代码前,分析原始源代码(文档字符串、CLI帮助、README)以填充
@server.agent()
参数:
  • 标识:设置
    name
    version
  • 文档:使用
    documentation_url
    指向源代码。
  • 详情:使用
    interaction_mode
    (步骤1)、
    tools
    author
    programming_language
    填充
    AgentDetail
  • 技能:定义包含
    id
    name
    description
    tags
    examples
    AgentSkill
    条目。
  • 函数文档字符串:包装器函数的文档字符串应为简洁摘要,将在注册中心显示。
  • 扩展:识别Agent是否需要可选的平台功能(步骤8),比如Citations、Secrets或Trajectory。

Key elements

核心元素

ElementPurpose
Server()
Creates the AgentStack server instance
@server.agent()
Registers the function as an agent; function name becomes agent ID, docstring becomes description
input: Message
A2A message from the caller; use
get_message_text(input)
to extract the text
context: RunContext
Execution context (
task_id
,
context_id
, session store, history)
yield AgentMessage(text=...)
Stream one or more response chunks back to the caller
emit trajectory output
Surface meaningful intermediate logs/progress separately from final user-facing response
server.run(host, port)
Starts the HTTP server
元素用途
Server()
创建AgentStack服务器实例
@server.agent()
将函数注册为Agent;函数名称将成为Agent ID,文档字符串将成为描述
input: Message
来自调用方的A2A消息;使用
get_message_text(input)
提取文本
context: RunContext
执行上下文(
task_id
context_id
、会话存储、历史)
yield AgentMessage(text=...)
向调用方流式返回一个或多个响应块
emit trajectory output
将有意义的中间日志/进度与最终面向用户的响应分开显示
server.run(host, port)
启动HTTP服务器

Single-turn

单轮Agent

Extract the user message with
get_message_text(input)
, call the original agent logic, and
yield AgentMessage(text=result)
. Persist both input and response via
context.store()
unless explicit stateless behavior is required. Only call
context.load_history()
in single-turn mode if continuity is intentionally part of the agent behavior.
If you include trajectory for a single-turn agent, route progress steps to trajectory output and keep the final user response separate.
使用
get_message_text(input)
提取用户消息،调用原始Agent逻辑,然后
yield AgentMessage(text=result)
。通过
context.store()
持久化输入和响应,除非明确需要无状态行为。仅当连续性是Agent有意行为的一部分时,才在单轮模式下调用
context.load_history()
如果为单轮Agent添加了Trajectory,请将进度步骤路由到Trajectory输出,并保持最终用户响应独立。

Multi-turn

多轮Agent

Conversations with memory require explicit history management. Single-turn agents should still persist context unless there is an explicit stateless requirement.
  1. Store input: Save the incoming user message immediately with
    await context.store(input)
    .
  2. Load history: Retrieve the past conversation via
    [msg async for msg in context.load_history() if isinstance(msg, Message)]
    .
  3. Execute agent: Pass the history to the original agent logic.
  4. Yield response: Return chunks with
    yield AgentMessage(text=...)
    .
  5. Store response: Save the final agent response(s) with
    await context.store(response)
    using a generated
    Message
    object or the yielded
    AgentMessage
    .

带记忆的对话需要显式的历史管理。单轮Agent仍应持久化上下文,除非明确要求无状态。
  1. 存储输入:立即使用
    await context.store(input)
    保存传入的用户消息。
  2. 加载历史:通过
    [msg async for msg in context.load_history() if isinstance(msg, Message)]
    检索过往对话。
  3. 执行Agent:将历史传递给原始Agent逻辑。
  4. 返回响应:使用
    yield AgentMessage(text=...)
    返回响应块。
  5. 存储响应:使用生成的
    Message
    对象或已返回的
    AgentMessage
    ,通过
    await context.store(response)
    保存最终Agent响应。

Step 4 – Wire LLM / Services via Extensions

步骤4 – 通过扩展连接LLM / 服务

OpenAI-compatible interface required. The agent must be designed to work with an OpenAI-compatible interface. If the original agent uses a different LLM provider (e.g., Anthropic, Google), you must install the necessary library (e.g.,
langchain-openai
) and use that provider class, passing the configuration received from the LLM extension.
Do not read API keys from environment variables. Use AgentStack's platform extensions to receive LLM configuration at runtime.
Add
llm: Annotated[LLMServiceExtensionServer, LLMServiceExtensionSpec.single_demand()]
as an agent function parameter. Extract the config from
llm.data.llm_fulfillments["default"]
and pass
api_key
,
api_base
,
api_model
explicitly to the original agent.
If the
default
fulfillment is missing, declare a secrets parameter (for example
secrets: Annotated[SecretsExtensionServer, SecretsExtensionSpec.single_demand(...)]
), request required secrets through that declared parameter, then construct fulfillment-compatible values and pass
api_key
,
api_base
, and
api_model
explicitly.
Do not reference
secrets.request_secrets()
unless a
secrets
extension parameter is declared on the agent function.
If the original agent reads env vars for API keys internally, refactor it so keys are passed as explicit parameters instead. Always pass runtime LLM config explicitly, avoid provider/default fallback chains, and fail fast with a clear error if required values are missing.
See the chat agent and competitive-research agent on GitHub for real examples of LLM extension wiring.

需要兼容OpenAI的接口。Agent必须设计为可与兼容OpenAI的接口配合使用。如果原始Agent使用不同的LLM提供商(例如Anthropic、Google),则必须安装必要的库(例如
langchain-openai
)并使用该提供商的类,将从LLM扩展接收的配置传递给它。
不要从环境变量中读取API密钥。使用AgentStack的平台扩展在运行时接收LLM配置。
添加
llm: Annotated[LLMServiceExtensionServer, LLMServiceExtensionSpec.single_demand()]
作为Agent函数的参数。从
llm.data.llm_fulfillments["default"]
提取配置,并将
api_key
api_base
api_model
显式传递给原始Agent。
如果
default
履行项缺失,请声明一个机密参数(例如
secrets: Annotated[SecretsExtensionServer, SecretsExtensionSpec.single_demand(...)]
),通过该声明的参数请求所需的机密信息,然后构造兼容履行项的值,并显式传递
api_key
api_base
api_model
除非Agent函数上声明了
secrets
扩展参数,否则不要引用
secrets.request_secrets()
如果原始Agent从内部读取环境变量获取API密钥,请重构它,使其接受显式参数形式的密钥。 始终显式传递运行时LLM配置,避免提供商/默认回退链,如果缺少所需值,应快速抛出清晰的错误。
有关LLM扩展连接的真实示例,请查看GitHub上的聊天Agent竞争研究Agent

Step 5 – Error Handling

步骤5 – 错误处理

Use the Error extension for user-visible failures. Do not report errors via a normal
AgentMessage
.
使用Error扩展处理用户可见的失败。不要通过普通的
AgentMessage
报告错误。

Implementation

实现方式

  1. Standard Reporting: Simply
    raise
    an exception (e.g.,
    ValueError
    ,
    RuntimeError
    ) inside the agent. The platform automatically catches and formats it.
  2. Advanced Configuration: Add
    error_ext: Annotated[ErrorExtensionServer, ErrorExtensionSpec(params=ErrorExtensionParams(include_stacktrace=True))]
    as an agent function parameter to enable stack traces in the UI.
  3. Adding Context: You can attach diagnostic data to
    error_ext.context
    (a dictionary) before raising an error. This context is serialized to JSON and shown in the UI.
  4. Multiple Errors: Use
    ExceptionGroup
    (Python 3.11+) to report multiple failures simultaneously. The extension will render them as a group in the UI.
  1. 标准报告:直接在Agent内部
    raise
    异常(例如
    ValueError
    RuntimeError
    )。平台会自动捕获并格式化它。
  2. 高级配置:添加
    error_ext: Annotated[ErrorExtensionServer, ErrorExtensionSpec(params=ErrorExtensionParams(include_stacktrace=True))]
    作为Agent函数参数,以在UI中显示堆栈跟踪。
  3. 添加上下文:在抛出错误前,可将诊断数据附加到
    error_ext.context
    (一个字典)。该上下文将序列化为JSON并在UI中显示。
  4. 多个错误:使用
    ExceptionGroup
    (Python 3.11+)同时报告多个失败。扩展会在UI中将它们显示为一组。

Example

示例

python
@server.agent()
async def my_agent(input: Message, error_ext: Annotated[ErrorExtensionServer, ErrorExtensionSpec()]):
    error_ext.context["op"] = "fetch_data"
    try:
        # ... logic ...
        pass
    except Exception as e:
        error_ext.context["failed_id"] = "123"
        raise RuntimeError(f"Operation failed: {e}") from e
See the chat agent and official error guide for more.

python
@server.agent()
async def my_agent(input: Message, error_ext: Annotated[ErrorExtensionServer, ErrorExtensionSpec()]):
    error_ext.context["op"] = "fetch_data"
    try:
        # ... 逻辑 ...
        pass
    except Exception as e:
            error_ext.context["failed_id"] = "123"
            raise RuntimeError(f"操作失败:{e}") from e
更多示例请查看聊天Agent和官方错误指南:错误处理

Step 6 – Forms (Single-Turn Structured Input)

步骤6 – 表单(单轮结构化输入)

If the original agent accepts named parameters (not just free text), map them to an
initial_form
using the Forms extension.
  1. Define a
    FormRender
    with appropriate field types (
    TextField
    ,
    DateField
    ,
    CheckboxField
    , etc.)
  2. Create a Pydantic
    BaseModel
    matching the form fields
  3. Add
    form: Annotated[FormServiceExtensionServer, FormServiceExtensionSpec.demand(initial_form=form_render)]
    as an agent parameter
  4. Parse input via
    form.parse_initial_form(model=MyParams)
Only use forms when the agent has clearly defined, structured parameters. For free-text agents, the plain message input is sufficient.
For mid-conversation input:
  • Single free-form question, use A2A
    input-required
    event.
  • Structured multi-field input, use dynamic form request extension (
    FormRequestExtensionServer
    /
    FormRequestExtensionSpec
    ).
See the form agent example on GitHub for a complete implementation.

如果原始Agent接受命名参数(不仅仅是自由文本),请使用Forms扩展将它们映射到
initial_form
  1. 定义带有适当字段类型(
    TextField
    DateField
    CheckboxField
    等)的
    FormRender
  2. 创建与表单字段匹配的Pydantic
    BaseModel
  3. 添加
    form: Annotated[FormServiceExtensionServer, FormServiceExtensionSpec.demand(initial_form=form_render)]
    作为Agent参数
  4. 通过
    form.parse_initial_form(model=MyParams)
    解析输入
仅当Agent有明确定义的结构化参数时才使用表单。对于自由文本Agent,普通消息输入已足够。
对于对话中途的输入:
  • 单个自由形式问题,使用A2A
    input-required
    事件。
  • 结构化多字段输入,使用动态表单请求扩展(
    FormRequestExtensionServer
    /
    FormRequestExtensionSpec
    )。
完整的实现示例请查看GitHub上的表单Agent

Step 7 – Entrypoint

步骤7 – 入口点

Create a
run()
/
serve()
function that calls
server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000)), context_store=PlatformContextStore())
with an
if __name__ == "__main__"
guard.
The server defaults to an in-memory context store when
context_store
is omitted, so wrappers that persist or read context history must pass
PlatformContextStore()
explicitly.
For wrappers that implement context or history persistence via
context.store()
or
context.load_history()
,
context_store=PlatformContextStore()
is required.
Remove all CLI argument parsing (
argparse.ArgumentParser
, etc.). If the agent previously relied on CLI arguments for input (e.g.
--repo-url
), refactor the input to come from its wrapper function parameters (mapped from a Form or environment variable).
Only add
configure_telemetry
or
auth_backend
if the user explicitly requests platform integration.

创建
run()
/
serve()
函数,在
if __name__ == "__main__"
保护下调用
server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000)), context_store=PlatformContextStore())
当省略
context_store
时,服务器默认使用内存中的上下文存储,因此需要持久化或读取上下文历史的包装器必须显式传递
PlatformContextStore()
对于通过
context.store()
context.load_history()
实现上下文或历史持久化的包装器,必须传递
context_store=PlatformContextStore()
移除所有CLI参数解析
argparse.ArgumentParser
等)。如果Agent之前依赖CLI参数获取输入(例如
--repo-url
),请重构输入来源为包装器函数参数(从表单或环境变量映射)。
仅当用户明确请求平台集成时,才添加
configure_telemetry
auth_backend

Step 8 – Use Platform Extensions

步骤8 – 使用平台扩展

Enhance the agent with platform-level capabilities by injecting extensions via
Annotated
function parameters. Use them if the original agent's behavior warrants it.
ExtensionWhen to UseDocumentation
CitationsAgent references documents or external URLsCitations
TrajectoryMulti-step reasoning, tool calls, long-running progress, or explicit debugging tracesTrajectory
SecretsAgent needs user-provided API keys or tokens at runtimeSecrets (Note: Check
secrets.data
and use
request_secrets
only through a declared
secrets
extension parameter if missing)
SettingsAgent has configurable behavior (e.g., "Thinking Mode")Settings
CanvasAgent needs to edit artifacts or code selected by userCanvas
ApprovalAgent performs sensitive tool calls requiring user consentTool Call Approval
MCPAgent uses Model Context Protocol tools/serversMCP Integration
For a complete overview of all available extensions: Agent Integration Overview
通过
Annotated
函数参数注入扩展،为Agent添加平台级功能。仅当原始Agent的行为需要时才使用它们。
扩展适用场景文档链接
CitationsAgent引用文档或外部URL时Citations
Trajectory多步骤推理、工具调用、长时间运行的进度或显式调试跟踪时Trajectory
SecretsAgent在运行时需要用户提供的API密钥或令牌时Secrets(注意:检查
secrets.data
,仅当缺失时通过已声明的
secrets
扩展参数使用
request_secrets
SettingsAgent有可配置行为时(例如“思考模式”)Settings
CanvasAgent需要编辑用户选择的工件或代码时Canvas
ApprovalAgent执行需要用户同意的敏感工具调用时工具调用审批
MCPAgent使用Model Context Protocol工具/服务器时MCP集成
所有可用扩展的完整概述:Agent集成概述

Trajectory Output Rule

Trajectory输出规则

Trajectory is optional for simple single-step responders.
Trajectory is required whenever the agent emits meaningful intermediate logs, execution steps, tool activity, or progress updates. Those intermediate signals must be surfaced as trajectory output, and the final user answer should remain focused on the final result.
Trajectory entries are metadata for transparency and observability. They are not a substitute for the agent's user-facing response message.
User-facing text should be emitted as normal
AgentMessage
output. Trajectory should contain the intermediate context behind that answer.
For third-party framework callbacks (for example sync-only step callbacks), capture callback data and emit it later from the main agent handler so trajectory output remains consistent.

对于简单的单步响应器,Trajectory是可选的。
每当Agent生成有意义的中间日志、执行步骤、工具活动或进度更新时,必须使用Trajectory。这些中间信号必须作为Trajectory输出显示,最终用户答案应仅聚焦于最终结果。
Trajectory条目是用于透明度和可观测性的元数据,不能替代Agent面向用户的响应消息。
面向用户的文本应作为普通
AgentMessage
输出。Trajectory应包含该答案背后的中间上下文。
对于第三方框架回调(例如仅同步的步骤回调),捕获回调数据并稍后从主Agent处理程序中输出,以保持Trajectory输出的一致性。

Step 9 – Update README

步骤9 – 更新README

Update the project's
README.md
(or create one if missing) with instructions on how to run the wrapped agent server. Include:
  1. Install dependencies using the project's existing tooling (e.g.
    uv pip install -r requirements.txt
    or
    pip install -r requirements.txt
    ).
  2. Run the server with the appropriate command (e.g.
    uv run server.py
    or
    python server.py
    ).
  3. Default address — mention that the server starts at
    http://127.0.0.1:8000
    by default and can be configured via
    HOST
    and
    PORT
    environment variables.
Remove or replace any outdated CLI usage examples (e.g.
argparse
-based commands) that no longer apply after wrapping.

更新项目的
README.md
(如果缺失则创建),包含运行封装后的Agent服务器的说明。内容包括:
  1. 安装依赖:使用项目现有的工具(例如
    uv pip install -r requirements.txt
    pip install -r requirements.txt
    )。
  2. 运行服务器:使用适当的命令(例如
    uv run server.py
    python server.py
    )。
  3. 默认地址:说明服务器默认启动于
    http://127.0.0.1:8000
    ,可通过
    HOST
    PORT
    环境变量配置。
移除或替换封装后不再适用的过时CLI使用示例(例如基于
argparse
的命令)。

Anti-Patterns

反模式

When building and testing the wrapper, ensure you avoid these common pitfalls:
  • Never hardcode API keys or LLM endpoints. Use the LLM proxy extension explicitly.
  • Never assume history is auto-saved. If you need context continuity, explicitly call
    await context.store(input)
    and
    await context.store(response)
    .
  • Never assume persistent history without
    PlatformContextStore
    .
    Without it, context storage is in-memory and lost on process restart.
  • Never forget to filter history.
    context.load_history()
    returns all items in the conversation (Messages, Artifacts). Always filter them using
    isinstance(message, Message)
    .
  • Never store individual streaming chunks. Accumulate the full response and store once using
    context.store()
    .
  • Never hallucinate import paths. You must never guess imports.
    a2a
    and
    agentstack_sdk
    are two separate packages. Always find the exact import name by inspecting the installed packages, and explicitly verify their functionality by running an import check.
  • Never assume extension availability. Check extension objects and payloads before using them.
  • Never access
    .text
    directly on a
    Message
    object.
    Message content is multipart. Always use
    get_message_text(input)
    .
  • Never use synchronous functions for the agent handler. Agent functions must be
    async def
    generators using
    yield
    .
  • Never hide platform integration behind wrapper classes. Keep decorators, imports, and config visible in the main agent entrypoint file. Enterprise developers must be able to inspect exactly what the agent does.
  • Never force trajectory on trivial wrappers. For simple single-step text responders, trajectory is optional.
  • Never skip trajectory when meaningful intermediate logs or tool traces are emitted. Those signals must be surfaced as trajectory output.
  • Never treat trajectory as the final answer channel. Trajectory is primarily metadata. User-visible answers must still be emitted as normal
    AgentMessage
    text.
  • Never bury meaningful intermediate logs in the final answer text. Keep progress/execution visibility separate from the final user-facing response.
  • Never silently remove existing optional auth inputs. If the original agent supported optional tokens/keys for higher limits or private resources, preserve that optional path or document an approved behavior change.
  • Never use forms for a single free-form question. Use the A2A
    input-required
    event instead if a simple free-text answer is needed.
  • Never mismatch form field IDs and model fields. When using Forms, mismatching IDs means values will fail to parse or silently drop.
  • Never skip null-path handling for forms. Handle
    None
    for cancelled or unsubmitted forms.
  • Never treat extension data as dictionaries. Data attached to extensions (e.g.,
    llm.data.llm_fulfillments["default"]
    ) are Pydantic objects, not dicts. Always access properties using dot notation (e.g.,
    config.api_key
    , not
    config.get("api_key")
    ).
  • Never use
    llm_config.identifier
    as the model name.
    identifier
    points to the provider binding (for example
    llm_proxy
    ), not to the deployable model. Use
    llm_config.api_model
    for model selection.
  • Never apply silent fallback when
    llm.data.llm_fulfillments["default"]
    is missing.
    Either request secrets through a declared
    secrets
    extension and construct explicit
    api_key
    /
    api_base
    /
    api_model
    values, or raise a clear error.
  • Never rely on framework default LLM fallback chains. If the wrapped runtime tries alternate providers automatically, disable that path by passing explicit provider/client config from the extension contract.
  • Never rewrite agent business logic. Only wrap the existing entry point. Never attempt to "fix" the original agent's internal workings.
构建和测试包装器时,请确保避免以下常见陷阱:
  • 绝不要硬编码API密钥或LLM端点。显式使用LLM代理扩展。
  • 绝不要假设历史会自动保存。如果需要上下文连续性,请显式调用
    await context.store(input)
    await context.store(response)
  • 绝不要在没有
    PlatformContextStore
    的情况下假设历史持久化
    。如果没有它,上下文存储是内存中的,进程重启后会丢失。
  • 绝不要忘记过滤历史
    context.load_history()
    返回对话中的所有项(消息、工件)。始终使用
    isinstance(message, Message)
    过滤它们。
  • 绝不要存储单个流式块。累积完整响应后,使用
    context.store()
    一次性存储。
  • 绝不要凭空猜测导入路径。绝不要猜测导入。
    a2a
    agentstack_sdk
    是两个独立的包。始终通过检查已安装的包找到确切的导入名称,并通过运行导入检查显式验证其功能。
  • 绝不要假设扩展可用。使用前检查扩展对象和负载。
  • 绝不要直接访问
    Message
    对象的
    .text
    属性
    。消息内容是多部分的。始终使用
    get_message_text(input)
  • 绝不要为Agent处理程序使用同步函数。Agent函数必须是使用
    yield
    async def
    生成器。
  • 绝不要将平台集成隐藏在包装器类后面。在主Agent入口文件中保持装饰器、导入和配置可见。企业开发人员必须能够准确检查Agent的行为。
  • 绝不要在简单包装器上强制使用Trajectory。对于简单的单步文本响应器,Trajectory是可选的。
  • 绝不要在生成有意义的中间日志或工具跟踪时跳过Trajectory。这些信号必须作为Trajectory输出显示。
  • 绝不要将Trajectory作为最终答案通道。Trajectory主要是元数据。用户可见的答案仍必须作为普通
    AgentMessage
    文本输出。
  • 绝不要将有意义的中间日志隐藏在最终答案文本中。将进度/执行可见性与最终面向用户的响应分开。
  • 绝不要静默移除现有的可选认证输入。如果原始Agent支持可选令牌/密钥以获得更高限制或访问私有资源,请保留该可选路径或记录已批准的行为变更。
  • 绝不要为单个自由形式问题使用表单。如果需要简单的自由文本答案,请改用A2A
    input-required
    事件。
  • 绝不要使表单字段ID与模型字段不匹配。使用表单时,ID不匹配会导致值解析失败或静默丢失。
  • 绝不要跳过表单的空路径处理。处理取消或未提交表单的
    None
    情况。
  • 绝不要将扩展数据视为字典。附加到扩展的数据(例如
    llm.data.llm_fulfillments["default"]
    )是Pydantic对象,不是字典。始终使用点符号访问属性(例如
    config.api_key
    ,而非
    config.get("api_key")
    )。
  • 绝不要将
    llm_config.identifier
    用作模型名称
    identifier
    指向提供商绑定(例如
    llm_proxy
    ),而非可部署的模型。使用
    llm_config.api_model
    选择模型。
  • 绝不要在
    llm.data.llm_fulfillments["default"]
    缺失时使用静默回退
    。要么通过已声明的
    secrets
    扩展请求机密信息并构造显式的
    api_key
    /
    api_base
    /
    api_model
    值,要么抛出清晰的错误。
  • 绝不要依赖框架默认的LLM回退链。如果封装的运行时自动尝试其他提供商,请通过传递来自扩展契约的显式提供商/客户端配置禁用该路径。
  • 绝不要重写Agent业务逻辑。仅包装现有的入口点。绝不要尝试“修复”原始Agent的内部工作机制。

Failure Conditions

失败条件

  • If the project's primary language is not Python, stop and report unsupported runtime.
  • If fresh docs cannot be fetched, stop and report that execution cannot continue without current docs.

  • 如果项目的主要语言不是Python,请停止操作并报告不支持的运行时。
  • 如果无法获取最新文档,请停止操作并报告无法在没有当前文档的情况下继续执行。

Finalization Report (Required)

最终报告(必需)

Before completion, provide all of the following:
  1. Mapping summary: inbound mapping (A2A Message to agent input), outbound mapping (agent output to
    AgentMessage
    ), and selected streaming path.
  2. Behavior changes list: if behavior changed, list each change with reason and impact.
  3. Business-logic statement: state whether business logic changed, and if it did, include approval and justification.
  4. Legacy endpoint compatibility result: state preserved, shimmed, or not applicable.
  5. Dockerfile prompt: Ask the user if they also want to add a
    Dockerfile
    . If the user says yes, review the example at
    https://github.com/i-am-bee/agentstack-starter/blob/main/Dockerfile
    and assemble a
    Dockerfile
    for the project. Do not force the use of
    uv
    if the project does not use it.
  6. Testing prompt: Ask the user if they want to test the agent functionality. If they say yes, start the agent first in one terminal, and then use a separate terminal to run
    agentstack run AGENT_NAME
    . Do not attempt to interrupt the
    run
    command, as it may take a long time to complete. If the execution fails and an error is encountered, attempt to fix the error and run the test again. Critically, do not create any new files or scripts (e.g., Python test scripts using pexpect) to perform this test. You must interact with the terminals directly.

完成前,请提供以下所有内容:
  1. 映射摘要:入站映射(A2A消息到Agent输入)、出站映射(Agent输出到
    AgentMessage
    ),以及所选的流式路径。
  2. 行为变更列表:如果行为发生变更,列出每个变更的原因和影响。
  3. 业务逻辑声明:说明是否修改了业务逻辑,如果是,请包含批准和理由。
  4. 遗留端点兼容性结果:说明已保留、添加垫片或不适用。
  5. Dockerfile提示:询问用户是否还要添加
    Dockerfile
    。如果用户同意,请查看
    https://github.com/i-am-bee/agentstack-starter/blob/main/Dockerfile
    中的示例,并为项目生成
    Dockerfile
    。如果项目不使用
    uv
    ,不要强制使用它。
  6. 测试提示:询问用户是否要测试Agent功能。如果用户同意,先在一个终端启动Agent,然后在另一个终端运行
    agentstack run AGENT_NAME
    。不要尝试中断
    run
    命令,因为它可能需要很长时间才能完成。如果执行失败并遇到错误,请尝试修复错误并重新运行测试。关键:不要创建任何新文件或脚本(例如使用pexpect的Python测试脚本)来执行此测试。必须直接与终端交互。

Verification Checklist

验证清单

After wrapping, confirm:
  • Every
    import
    resolves to a real, installed module
  • The agent function has a meaningful docstring (used as description in UI)
  • yield AgentMessage(text=...)
    is used for all responses
  • No env vars are used for API keys or model config (extensions used instead)
  • Agent uses an OpenAI-compatible interface or has necessary provider libraries installed for other LLMs
  • Wrapper passes explicit runtime LLM config from extensions and does not rely on framework/provider fallback defaults
  • Single-turn vs multi-turn classification matches the actual agent behavior
  • If single-turn with structured params →
    initial_form
    is defined
  • input
    and
    response
    are stored via
    context.store()
    unless explicit stateless behavior is justified
  • context.load_history()
    is required for multi-turn; for single-turn, it is used only when continuity is intentionally required
  • No business-logic changes were made to the original agent code unless explicitly approved per Constraint C10
  • If business-logic change was required, explicit approval and justification are recorded
  • No Dockerfile was added unless explicitly requested
  • Temp files created at runtime are cleaned up
  • agentstack-sdk
    (pinned with
    ~=
    ) was added to the project's existing dependency file
  • If
    a2a-sdk
    is pinned directly, its version is explicitly compatible with the selected
    agentstack-sdk
  • Errors raise exceptions (handled by Error extension), not yielded as
    AgentMessage
  • Optional extensions are checked for presence/data before use
  • If the agent references sources -> Citations extension is used
  • If the agent has meaningful multi-step execution/tool traces, trajectory output is emitted for those steps
  • Final user-facing answer is emitted as normal
    AgentMessage
    output, not only as trajectory data
  • If the agent already used secrets -> Secrets extension is used (safe access pattern with
    request_secrets
    through a declared secrets extension parameter). No new secrets added.
  • No extra middleware, auth, or containerization added unless explicitly requested (Constraint C2)
  • Imports follow import truth and validation rule (Constraint C6)
  • No command-line arguments (
    argparse
    ) remain in the code (Constraint C9)
  • You have provided a Mapping summary showing inbound mapping (A2A Message to agent input), outbound mapping (agent output to AgentMessage), and streaming path selected.
  • context_store=PlatformContextStore()
    is present whenever the wrapper persists or reads context history.
  • If legacy HTTP endpoints were contract-tested, compatibility is preserved or shimmed.
  • If behavior changed, the Finalization Report includes an explicit change list and impact.
  • Agent responds at
    /.well-known/agent-card.json
    with HTTP 200 and a valid and parseable JSON.
  • Agent card includes required identity fields used for discovery.
  • Validate the Agent Card by running script
    validate-agent-card.py
    (Make sure the agent is running, and pass
    server:port
    if it's not on the default
    127.0.0.1:8000
    ). Show the full output of this validation script to the user.
  • The user was asked if they want to add a
    Dockerfile
    (and if requested, it was generated based on the agentstack-starter example without forcing
    uv
    ).
  • The user was asked if they want to test the agent's functionality. If they said yes, the agent was started first, and then in a separate terminal, the
    agentstack run AGENT_NAME
    command was executed (do not activate the virtual environment before running this command). The
    run
    command was allowed to run without interruption, and any errors encountered were investigated, fixed, and the test was rerun. No additional files or test scripts were created during testing.
封装完成后,请确认:
  • 每个
    import
    都解析到真实的已安装模块
  • Agent函数有有意义的文档字符串(在UI中用作描述)
  • 所有响应都使用
    yield AgentMessage(text=...)
  • 没有使用环境变量存储API密钥或模型配置(改用扩展)
  • Agent使用兼容OpenAI的接口,或已为其他LLM安装必要的提供商库
  • 包装器从扩展传递显式的运行时LLM配置,不依赖框架/提供商默认回退
  • 单轮/多轮分类与Agent实际行为匹配
  • 如果是带结构化参数的单轮Agent,已定义
    initial_form
  • 输入和响应通过
    context.store()
    存储,除非有明确的无状态行为理由
  • context.load_history()
    是多轮Agent的必需项;单轮Agent仅在有意需要连续性时使用
  • 未修改原始Agent的业务逻辑,除非根据约束C10获得明确批准
  • 如果需要修改业务逻辑,已记录明确的批准和理由
  • 仅在明确请求时才添加Dockerfile
  • 运行时创建的临时文件已被清理
  • agentstack-sdk
    (使用
    ~=
    固定版本)已添加到项目现有的依赖文件中
  • 如果直接固定
    a2a-sdk
    ,其版本与所选
    agentstack-sdk
    明确兼容
  • 错误通过抛出异常处理(由Error扩展处理),而非作为
    AgentMessage
    返回
  • 使用可选扩展前检查其存在/数据
  • 如果Agent引用来源,已使用Citations扩展
  • 如果Agent有有意义的多步骤执行/工具跟踪,已输出Trajectory
  • 最终用户可见的答案作为普通
    AgentMessage
    输出,而非仅作为Trajectory数据
  • 如果Agent已使用机密信息,已使用Secrets扩展(通过已声明的secrets扩展参数使用安全访问模式
    request_secrets
    )。未添加新的机密信息。
  • 除非明确请求,否则未添加额外的中间件、认证或容器化(符合约束C2)
  • 导入遵循导入真实性和验证规则(约束C6)
  • 代码中没有遗留的命令行参数(
    argparse
    )(符合约束C9)
  • 已提供映射摘要,显示入站映射(A2A消息到Agent输入)、出站映射(Agent输出到AgentMessage)和所选的流式路径
  • 当包装器持久化或读取上下文历史时,已包含
    context_store=PlatformContextStore()
  • 如果遗留HTTP端点是契约测试的一部分,已保留或提供兼容性垫片
  • 如果行为发生变更,最终报告包含显式的变更列表和影响
  • Agent在
    /.well-known/agent-card.json
    返回HTTP 200和可解析的有效JSON
  • Agent卡片包含用于发现的必需标识字段
  • 验证Agent卡片:运行脚本
    validate-agent-card.py
    (确保Agent正在运行,如果不在默认的
    127.0.0.1:8000
    ,请传递
    server:port
    )。向用户显示此验证脚本的完整输出。
  • 已询问用户是否要添加
    Dockerfile
    (如果请求,基于agentstack-starter示例生成,不强制使用
    uv
  • 已询问用户是否要测试Agent功能。如果用户同意,先启动Agent,然后在另一个终端运行
    agentstack run AGENT_NAME
    命令(运行此命令前不要激活虚拟环境)。允许
    run
    命令运行而不中断,如果遇到错误,尝试修复错误并重新运行测试。测试期间未创建任何额外文件或测试脚本。