Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 27 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@

## 🆕 NEWS

* **[2026-02]** A major architectural refactor of `AgentApp`. By adopting direct inheritance from `FastAPI` and deprecating the previous factory pattern, `AgentApp` now offers seamless integration with the full FastAPI ecosystem, significantly boosting extensibility. Furthermore, we've introduced a **Distributed Interrupt Service**, enabling manual task preemption during agent execution and allowing developers to customize state persistence and recovery logic flexibly.
* **[2026-01]** Added **asynchronous sandbox** implementations (`BaseSandboxAsync`, `GuiSandboxAsync`, `BrowserSandboxAsync`, `FilesystemSandboxAsync`, `MobileSandboxAsync`) enabling non-blocking, concurrent tool execution in async program. Improved `run_ipython_cell` and `run_shell_command` methods with enhanced **concurrency and parallel execution** capabilities for more efficient sandbox operations.
* **[2025-12]** We have released **AgentScope Runtime v1.0**, introducing a unified “Agent as API” white-box development experience, with enhanced multi-agent collaboration, state persistence, and cross-framework integration. This release also streamlines abstractions and modules to ensure consistency between development and production environments. Please refer to the **[CHANGELOG](https://runtime.agentscope.io/en/CHANGELOG.html)** for full update details and migration guide.

Expand Down Expand Up @@ -141,14 +142,15 @@ pip install -e .

This example demonstrates how to create an agent API server using agentscope `ReActAgent` and `AgentApp`. To run a minimal `AgentScope` Agent with AgentScope Runtime, you generally need to implement:

1. **`@agent_app.init`** – Initialize services/resources at startup
1. **`Define lifespan`** – Use `contextlib.asynccontextmanager` to manage resource initialization (e.g., state services) at startup and cleanup on exit.
2. **`@agent_app.query(framework="agentscope")`** – Core logic for handling requests, **must use** `stream_printing_messages` to `yield msg, last` for streaming output
3. **`@agent_app.shutdown`** – Clean up services/resources on exit


```python
import os
from contextlib import asynccontextmanager

from fastapi import FastAPI
from agentscope.agent import ReActAgent
from agentscope.model import DashScopeChatModel
from agentscope.formatter import DashScopeChatFormatter
Expand All @@ -160,23 +162,32 @@ from agentscope.session import RedisSession
from agentscope_runtime.engine import AgentApp
from agentscope_runtime.engine.schemas.agent_schemas import AgentRequest

agent_app = AgentApp(
app_name="Friday",
app_description="A helpful assistant",
)


@agent_app.init
async def init_func(self):
# 1. Define lifespan manager
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Manage resources during service startup and shutdown"""
# Startup: Initialize Session manager
import fakeredis

fake_redis = fakeredis.aioredis.FakeRedis(decode_responses=True)
# NOTE: This FakeRedis instance is for development/testing only.
# In production, replace it with your own Redis client/connection
# (e.g., aioredis.Redis)
self.session = RedisSession(connection_pool=fake_redis.connection_pool)
app.state.session = RedisSession(connection_pool=fake_redis.connection_pool)

yield # Service is running

# Shutdown: Add cleanup logic here (e.g., closing database connections)
print("AgentApp is shutting down...")

# 2. Create AgentApp instance
agent_app = AgentApp(
app_name="Friday",
app_description="A helpful assistant",
lifespan=lifespan,
)

# 3. Define request handling logic
@agent_app.query(framework="agentscope")
async def query_func(
self,
Expand Down Expand Up @@ -204,7 +215,8 @@ async def query_func(
)
agent.set_console_output_enabled(enabled=False)

await self.session.load_session_state(
# Load state
await agent_app.state.session.load_session_state(
session_id=session_id,
user_id=user_id,
agent=agent,
Expand All @@ -216,13 +228,14 @@ async def query_func(
):
yield msg, last

await self.session.save_session_state(
# Save state
await agent_app.state.session.save_session_state(
session_id=session_id,
user_id=user_id,
agent=agent,
)


# 4. Run the application
agent_app.run(host="127.0.0.1", port=8090)
```

Expand Down
42 changes: 27 additions & 15 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@

## 🆕 新闻

* **[2026-02]** 我们对 `AgentApp` 进行了核心架构重构。新版本采用直接继承 `FastAPI` 的设计,废弃了原有的工厂类模式,使开发者能够直接利用完整的 FastAPI 生态,显著提升了应用的可扩展性。此外,新版本引入了分布式**任务中断管理服务**,支持在 Agent 推理过程中进行实时干预,并允许灵活自定义中断前后的状态保存与恢复逻辑。
* **[2026-01]** 新增 **异步沙箱** 实现(`BaseSandboxAsync`、`GuiSandboxAsync`、`BrowserSandboxAsync`、`FilesystemSandboxAsync`、`MobileSandboxAsync`),支持在异步编程中进行非阻塞的并发工具执行。
同时优化了 `run_ipython_cell` 和 `run_shell_command` 方法的 **并发与并行执行能力**,提升沙箱运行效率。
* **[2025-12]** 我们发布了 **AgentScope Runtime v1.0**,该版本引入统一的 “Agent 作为 API” 白盒化开发体验,并全面强化多智能体协作、状态持久化与跨框架组合能力,同时对抽象与模块进行了简化优化,确保开发与生产环境一致性。完整更新内容与迁移说明请参考 **[CHANGELOG](https://runtime.agentscope.io/zh/CHANGELOG.html)**。
Expand Down Expand Up @@ -143,14 +144,14 @@ pip install -e .
这个示例演示了如何使用 AgentScope 的 `ReActAgent` 和 `AgentApp` 创建一个代理 API 服务器。
要在 AgentScope Runtime 中运行一个最小化的 `AgentScope` Agent,通常需要实现以下内容:

1. **`@agent_app.init`** – 在启动时初始化服务或资源
1. **`定义生命周期 (lifespan) `** – 使用 contextlib.asynccontextmanager 管理服务启动时的资源初始化(如状态服务)和退出时的清理
2. **`@agent_app.query(framework="agentscope")`** – 处理请求的核心逻辑,**必须使用** `stream_printing_messages` 并 `yield msg, last` 来实现流式输出
3. **`@agent_app.shutdown`** – 在退出时清理服务或资源


```python
import os
from contextlib import asynccontextmanager

from fastapi import FastAPI
from agentscope.agent import ReActAgent
from agentscope.model import DashScopeChatModel
from agentscope.formatter import DashScopeChatFormatter
Expand All @@ -162,23 +163,32 @@ from agentscope.session import RedisSession
from agentscope_runtime.engine import AgentApp
from agentscope_runtime.engine.schemas.agent_schemas import AgentRequest

agent_app = AgentApp(
app_name="Friday",
app_description="A helpful assistant",
)


@agent_app.init
async def init_func(self):
# 1. 定义生命周期管理器
@asynccontextmanager
async def lifespan(app: FastAPI):
"""管理服务启动和关闭时的资源"""
# 启动时:初始化 Session 管理器
import fakeredis

fake_redis = fakeredis.aioredis.FakeRedis(decode_responses=True)
# 注意:这个 FakeRedis 实例仅用于开发/测试。
# 在生产环境中,请替换为你自己的 Redis 客户端/连接
#(例如 aioredis.Redis)。
self.session = RedisSession(connection_pool=fake_redis.connection_pool)
app.state.session = RedisSession(connection_pool=fake_redis.connection_pool)

yield # 服务运行中

# 关闭时:可以在此处添加清理逻辑(如关闭数据库连接)
print("AgentApp is shutting down...")

# 2. 创建 AgentApp 实例
agent_app = AgentApp(
app_name="Friday",
app_description="A helpful assistant",
lifespan=lifespan,
)

# 3. 定义请求处理逻辑
@agent_app.query(framework="agentscope")
async def query_func(
self,
Expand Down Expand Up @@ -206,7 +216,8 @@ async def query_func(
)
agent.set_console_output_enabled(enabled=False)

await self.session.load_session_state(
# 加载状态
await agent_app.state.session.load_session_state(
session_id=session_id,
user_id=user_id,
agent=agent,
Expand All @@ -218,13 +229,14 @@ async def query_func(
):
yield msg, last

await self.session.save_session_state(
# 保存状态
await agent_app.state.session.save_session_state(
session_id=session_id,
user_id=user_id,
agent=agent,
)


# 4. 启动应用
agent_app.run(host="127.0.0.1", port=8090)
```

Expand Down
106 changes: 82 additions & 24 deletions cookbook/en/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# CHANGELOG

## v1.1.0
AgentScope Runtime v1.1.0 **simplifies persistence and session continuity** by removing Runtime-side custom Memory/Session service abstractions and **standardizing on the Agent framework’s native persistence modules**. This reduces mental overhead, avoids duplicated concepts, and ensures the persistence behavior is consistent with the underlying agent framework.

AgentScope Runtime v1.1.0 focuses on **simplifying persistence and session continuity** by removing Runtime-side custom Memory/Session service abstractions and **standardizing on the Agent framework’s native persistence modules**. This reduces mental overhead, avoids duplicated concepts, and ensures the persistence behavior is consistent with the underlying agent framework.
Additionally, this version introduces a **major refactor of the `AgentApp` core architecture**: switching to direct inheritance from `FastAPI`, introducing **standard lifespan management**, and adding **task interruption** and **concurrency conflict control** for distributed scenarios.

**Background & Necessity of the Changes**

Expand All @@ -19,11 +20,28 @@ In v1.0, Runtime provided custom **Session History** and **Long-term Memory** se

To address this, v1.1.0 **deprecates and removes** these Runtime-side services/adapters, and recommends using the **Agent framework’s own persistence modules** (e.g., `JSONSession`, built-in memory implementations) directly in the `AgentApp` lifecycle.

**Furthermore, v1.1.0 refactors the core architecture of `AgentApp`, shifting from a "factory creation pattern" to "direct inheritance from `FastAPI`".** This change stems from the following considerations:

1. **Addressing limitations of the factory pattern**
In previous versions, `AgentApp` held a FastAPI instance internally through a factory class. This "black box" approach made it difficult for developers to leverage native FastAPI features (e.g., complex middleware, custom route decorators, and dependency injection), and custom lifecycle hooks like `@app.init` deviated from standard web development practices.

2. **Embracing native FastAPI ecosystem and extensibility**
By **directly inheriting from the `FastAPI` class**, `AgentApp` is now a standard FastAPI application. This grants developers full control and seamless integration with FastAPI's community plugins. The class-based architecture also allows us to elegantly introduce advanced features like **Task Interruption** and **State Race Condition Control** via `Mixin` classes, making `AgentApp` a core component that is both standard-compliant and deeply optimized for Agent-specific scenarios.

### Added

- **Distributed Task Interruption**: Introduced `InterruptMixin` with backend support (Local/Redis), allowing manual interruption via custom interfaces and supporting interrupt signal broadcasting in distributed clusters.
- **Distributed Race Condition Control**: Introduced atomic state-machine checks (Compare-and-Swap) during task startup to effectively prevent duplicate concurrent execution of the same Session ID in distributed environments.
- **Native FastAPI Extensibility**: Since `AgentApp` now inherits from `FastAPI`, developers can use native methods like `@app.get` and `app.add_middleware` with full compatibility.

### Changed

- Recommended persistence pattern:
- **Recommended persistence pattern**:
- Use the agent framework’s **Memory** modules directly (e.g., `InMemoryMemory`, Redis-backed memory if provided by the framework).
- Use the agent framework’s **Session** modules (e.g., `JSONSession`) to load/save agent session state during `query`.
- **Architectural Refactor**: `AgentApp` has shifted from a **factory class pattern** to **direct inheritance from `FastAPI`**.
- **Lifecycle Unification**: Unified the lifecycle management of internal framework resources and user-defined resources.


### Breaking Changes

Expand All @@ -35,17 +53,25 @@ To address this, v1.1.0 **deprecates and removes** these Runtime-side services/a
- Runtime long-term memory services/adapters
- `AgentScopeSessionHistoryMemory(...)`-style adapter usage
must be migrated to the Agent framework’s built-in persistence approach.
2. **Removal of the Factory Pattern**
- `FastAPIAppFactory` is deprecated. Users should now instantiate `AgentApp` objects directly.
3. **Deprecation of Custom Decorator Hooks**
- The `@app.init` and `@app.shutdown` decorators are deprecated and marked for removal.
- **Migration Advice**: Please use the standard FastAPI `lifespan` asynchronous context manager (see the example in the Migration Guide below).

#### Migration Guide (v1.0 → v1.1)

##### Recommended Pattern (Use Agent framework modules for persistence)
##### Recommended Pattern (Unified Lifecycle, Interruption Handling, and Native Persistence)

Use `JSONSession` or other submodule to persist/load the agent’s session state, and use `InMemoryMemory()` (or other framework-provided memory) directly in AgentScope:
In v1.1.0, we recommend using the **lifespan asynchronous context manager** instead of the old decorators to manage resources. Additionally, catch `asyncio.CancelledError` within the `query` logic to respond to interrupt signals, and use the Agent framework’s native **Session/Memory** modules for state persistence:

```python
# -*- coding: utf-8 -*-
import asyncio
import os
from contextlib import asynccontextmanager

from fastapi import FastAPI
from agentscope.agent import ReActAgent
from agentscope.model import DashScopeChatModel
from agentscope.formatter import DashScopeChatFormatter
Expand All @@ -57,23 +83,27 @@ from agentscope.session import JSONSession
from agentscope_runtime.engine.app import AgentApp
from agentscope_runtime.engine.schemas.agent_schemas import AgentRequest

# Standard FastAPI lifespan management
# (Replaces deprecated @app.init/@app.shutdown)
@asynccontextmanager
async def lifespan(app: FastAPI):
# Use JSONSession here
app.state.session = JSONSession(save_dir="./sessions")
try:
yield
finally:
# No Runtime state/session services to stop in v1.1
pass

# AgentApp now inherits directly from FastAPI
agent_app = AgentApp(
app_name="Friday",
app_description="A helpful assistant",
lifespan=lifespan,
# Optional: Enable distributed interrupt by providing interrupt_redis_url
# interrupt_redis_url="redis://localhost"
)


@agent_app.init
async def init_func(self):
self.session = JSONSession(save_dir="./sessions") # Use JSONSession here


@agent_app.shutdown
async def shutdown_func(self):
# No Runtime state/session services to stop in v1.1
pass


@agent_app.query(framework="agentscope")
async def query_func(
self,
Expand Down Expand Up @@ -102,15 +132,43 @@ async def query_func(
formatter=DashScopeChatFormatter(),
)

await self.session.load_session_state(session_id=session_id, agent=agent)

async for msg, last in stream_printing_messages(
agents=[agent],
coroutine_task=agent(msgs),
):
yield msg, last
await agent_app.state.session.load_session_state(
session_id=session_id,
agent=agent,
)

await self.session.save_session_state(session_id=session_id, agent=agent)
try:
async for msg, last in stream_printing_messages(
agents=[agent],
coroutine_task=agent(msgs),
):
yield msg, last

except asyncio.CancelledError:
# Handling Interruptions (New in v1.1.0)
await agent.interrupt() # Explicitly halt the underlying agent execution

# Re-raise to ensure AgentApp correctly updates the task state to STOPPED
raise

finally:
# Persistence: Save state regardless of normal completion or interruption
await agent_app.state.session.save_session_state(
session_id=session_id,
agent=agent
)

# Optional: Explicit endpoint to trigger task interruption
@agent_app.post("/stop")
async def stop_task(request: AgentRequest):
await agent_app.stop_chat(
user_id=request.user_id,
session_id=request.session_id,
)
return {
"status": "success",
"message": "Interrupt signal broadcasted.",
}


agent_app.run()
Expand Down
Loading