关于 Inception ICORE Server
Inception ICORE Server 是面向 Agent 系统开发者的 Rust 微服务,提供记忆与聊天能力。它组合易失和持久键值记忆、ChromaDB 向量检索、BaseAgent 及可选的 Sentience DSL,目前仍在开发中。
主要功能
- 01内存短期键值存储
- 02基于 SQLite 的持久长期记忆
- 03ChromaDB 向量存储与相似度检索
- 04支持记忆和回忆逻辑的 BaseAgent
- 05可运行时加载的 Sentience DSL 和聊天端点
适用场景
- 01在会话期间保存 Agent 临时上下文
- 02在服务器重启后保留键值记忆
- 03检索相关的向量记录
- 04按 DSL、BaseAgent、LLM 回退顺序处理聊天
MCP 相关
输入内容介绍了 HTTP API,未说明面向 MCP 的功能。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
Inception ICORE 服务
Inception 核心(ICORE)服务
基于 Rust 构建的模块化、可扩展服务,提供短期、长期和潜在记忆服务、一个带有 BaseAgent + Sentience DSL 的聊天端点,以及与 ChromaDB 和大模型服务的集成。
⚠️ 开发中:此项目正在积极开发。接口、API 和内部结构可能会频繁变更。
目录
概述
Inception ICORE 服务旨在打造高度灵活的微服务,提供以下功能:
- 短期记忆,用于临时键值存储(内存中)。
- 长期记忆,用于持久化键值存储(SQLite)。
- 潜在记忆(向量嵌入 + 相似度搜索),由 ChromaDB 提供支持。
- 能够执行“remember/recall/if context”操作的 BaseAgent,并可选择加载在运行时加载的 Sentience DSL 层。
- 聊天端点会优先将传入消息路由至 Sentience DSL(如果已加载),然后回退至标准 BaseAgent,最后回退至备用大模型。
- 简单且有完善文档的 HTTP API(基于 Axum 构建),用于与所有记忆层和 Agent 功能交互。
本 README 将介绍架构详情、设置说明、环境配置、API 用法和开发工作流。
请参阅 VISION.md,了解本项目背后的哲学动机和长期目标。
功能
-
模块化记忆层
- 短期记忆:快速的内存键值存储(易失)。
- 长期记忆:基于 SQLite 的持久化键值存储。
- 潜在记忆:通过 ChromaDB 提供向量嵌入和最近邻查询。
-
Agent 逻辑
- BaseAgent:简单的“记住”“回忆”和“如果上下文包含”逻辑。
- Sentience DSL:可加载的 DSL,可用于编写自定义响应逻辑,并支持无缝地为记忆层填充初始数据和刷新数据。
-
大模型回退
- 开箱即用地集成任意基于 HTTP 的大模型(例如本地 Llama 服务),用于回退生成。
-
HTTP API
- 提供用于管理记忆、嵌入/查询向量、聊天以及直接运行 DSL 代码的端点。
- 基于 JSON 的请求/响应格式。
-
Docker Compose 配置
- 预配置的
docker-compose.dev.yml,用于编排 ChromaDB、本地大模型服务和 ICORE Server。
- 预配置的
-
异步
- 基于 Rust 的 Tokio 运行时构建,支持高并发和高性能。
-
可扩展且可配置
- 可通过环境变量轻松替换 ChromaDB URL、大模型 URL、数据库路径和 DSL 脚本。
架构与组件
作为大脑的架构
inception-ICORE-server 充当人工智能系统的脑干,负责协调、记忆路由和协议层面的反射。
其结构灵感来自生物大脑:
-
Inception(此服务) — 脑干 管理记忆访问、Agent 执行和 DSL 处理。它不会“思考”,而是负责调节。
-
皮层(记忆层) — 认知皮层
存储长期知识(SQLite)、短期工作记忆(内存)和潜在语义空间(ChromaDB)。 -
Agents — 器官
使用结构化记忆和嵌入式逻辑进行感知、行动和反思的输入/输出实体。 -
ICORE Protocol — 神经信号
记忆、Agents 和逻辑评估器之间的标准化通信流程。
这种分离支持模块化开发、自主行为和语义反思——一次处理一个层。
以下是高级架构图,以及对每个核心组件及其交互方式的说明:

BaseAgent 与 Sentience DSL
-
BaseAgent(
agents/agent.rs):- 维护进程内上下文(
Context),用于实现简单的“记住/回忆/如果上下文包含”逻辑。 - 使用
sentiencecrate 直接加载并执行 Sentience DSL 代码。 - 在
handle期间,DSL 记忆会从全局存储中填充初始数据,并在执行后刷新回存储。
- 维护进程内上下文(
记忆层
-
短期记忆(
memory/short_term.rs):- 内存中的
HashMap<String, String>。 - 快速读写临时键值对。
- 易失(服务重启后会丢失)。
- 内存中的
-
长期记忆(
memory/long_term.rs):- 基于 SQLite 的键值存储(通过
sqlx)。 - 持久化存储在磁盘上(
memory.db)。 - 异步
get、set和all操作。
- 基于 SQLite 的键值存储(通过
-
潜在记忆(
memory/latent.rs):- 通过 ChromaDB REST API 提供向量嵌入和相似性搜索。
- 提供以下方法:
embed(id: &str, vector: Vec<f32>)→ 在id下存储虚拟或计算得到的嵌入。query(vector: Vec<f32>)→ 返回(id, score)对组成的列表。
- 在此原型中,嵌入为虚拟零向量(
vec![0.0; 1536]),但你可以替换为真实的大模型编码器输出。
大模型集成
- 服务需要一个环境变量
LLM_URL,指向本地大模型 HTTP 服务(例如 llama.cpp server)。 - 聊天回退逻辑(位于
handlers/chat)会调用:model::generate(&payload.message).await
通过向 LLM_URL 发送包含 JSON { "prompt": "..."} 的 HTTP 请求,并返回生成的文本。
HTTP API(Axum)
- 使用 Tokio 运行时和 Axum v0.7。
- 路由定义在
api/routes.rs中,处理程序定义在api/handlers.rs中。 - 所有端点均以 JSON 或
(StatusCode, String)组合响应(通过impl IntoResponse转换)。
入门指南
前置条件
-
Rust 工具链
-
安装 rustup,并确保使用较新的 stable 工具链:
rustup update stable rustup default stable
-
-
Docker 和 Docker Compose(用于完整的开发环境设置)
- Docker Desktop 或 Docker Engine v20+
- Docker Compose v1.29+(或集成在 Docker CLI 中的 v2)
-
端口
- ChromaDB:
8000 - LLM Server:
11434 - ICORE Server:
8080
- ChromaDB:
克隆并构建
git clone https://github.com/your-org/inception-ICORE-server.git
cd inception-ICORE-server
你可以验证项目能否在本地构建:
cargo build
克隆 Sentience 依赖项
本项目依赖一个名为 sentience 的本地 Rust crate。
将它克隆到与本项目相同的父目录中:
git clone https://github.com/nbursa/sentience.git
环境变量
在项目根目录创建 .env 文件,或在 shell 中导出以下变量:
# .env (for Docker Compose or local development)
CHROMADB_URL=http://localhost:8000
CHROMA_COLLECTION_ID=<your-collection-uuid>
LLM_URL=http://localhost:11434
ICORE_ENV=development # or "production"
RUST_LOG=info # e.g. "debug", "info", "warn", etc.
# For Docker Compose (network aliases):
# CHROMADB_URL=http://chromadb:8000
# LLM_URL=http://llm:11434
-
CHROMADB_URLChromaDB 的访问地址(例如,http://localhost:8000)。 -
CHROMA_COLLECTION_ID用于向量存储的 ChromaDB 集合 UUID。该集合必须已存在,或另行创建。 -
LLM_URLLLM 服务的基础 URL(例如,位于:11434的本地 llama.cpp 服务端)。 -
ICORE_ENV控制环境模式(development或production)。会影响日志级别等设置。 -
RUST_LOG标准的tracing_subscriber过滤器(例如,info、debug)。
使用 Docker Compose 运行
预先配置好的 docker-compose.dev.yml 会编排以下服务:
- chromadb 服务
- llm(本地 Llama 服务端)
- ICORE-server(此 Rust 应用程序)
-
确保你有有效的
CHROMA_COLLECTION_ID。如果没有,必须先在 ChromaDB 中创建集合(通过其 HTTP API 或 UI)。 -
在项目根目录(
docker-compose.dev.yml所在目录)运行:docker-compose -f docker-compose.dev.yml up -d --build这将会:
- 拉取或构建
chromadb/chroma:0.6.3镜像,并开放端口8000。 - 构建本地 LLM 容器(
llama.cpp/中的Dockerfile.server),并开放端口11434。 - 构建 ICORE Server 容器(项目根目录中的
Dockerfile),并开放端口8080。
- 拉取或构建
-
验证所有服务均已正常运行:
docker-compose -f docker-compose.dev.yml ps -
测试 ICORE 服务:
curl -i http://localhost:8080/api/ping # Expected: HTTP/1.1 200 OK, Body: "pong"
停止并移除所有容器:
docker-compose -f docker-compose.dev.yml down
不使用 Docker 在本地运行
如果你希望在本地直接运行所有服务:
-
启动 ChromaDB
-
下载并运行 ChromaDB:
docker run -d --name chroma-local \ -p 8000:8000 \ -e IS_PERSISTENT=FALSE \ -e ANONYMIZED_TELEMETRY=FALSE \ chromadb/chroma:0.6.3 -
创建一个集合(使用 Postman/cURL 或 ChromaDB UI),并记下其 UUID。
-
-
启动 LLM Server(例如,llama.cpp)
-
切换到
./llama.cpp/(确保已配置Dockerfile.server)。 -
构建并运行:
docker build -t llama-server -f Dockerfile.server . docker run -d --name llama-server -p 11434:11434 llama-server \ --model /models/mistral-7b-q4.gguf \ --port 11434 --ctx-size 256 --threads 1
-
-
配置
.env(位于项目根目录):CHROMADB_URL=http://localhost:8000 CHROMA_COLLECTION_ID=<your-collection-uuid> LLM_URL=http://localhost:11434 ICORE_ENV=development RUST_LOG=info -
运行 ICORE Server
cargo run服务将会:
- 初始化
ShortTermMemory(内存中)。 - 初始化
LongTermMemory(SQLite 文件memory.db)。 - 初始化
LatentMemory(连接到指定 URL 的 ChromaDB 客户端)。 - 尝试从项目根目录加载
agent.sent(如果存在)。 - 开始监听
0.0.0.0:8080。
- 初始化
测试服务
健康检查
curl -i http://localhost:8080/health
# 200 OK
# Body: ICORE server is healthy.
Ping
curl -i http://localhost:8080/api/ping
# 200 OK
# Body: pong
短期记忆
# Set a value
curl -i -X POST http://localhost:8080/api/mem/short/my_key \
-H "Content-Type: application/json" \
-d '{"value":"hello"}'
# 200 OK, Body: stored
# Get the value
curl -i http://localhost:8080/api/mem/short/my_key
# 200 OK, Body: hello
长期记忆
# Set (async behind scenes)
curl -i -X POST http://localhost:8080/api/mem/long/user123 \
-H "Content-Type: application/json" \
-d '{"value":"persistent data"}'
# Get value
curl -i http://localhost:8080/api/mem/long/user123
# 200 OK, Body: persistent data
潜在记忆(ChromaDB)
# Embed dummy vector under “doc1”
curl -i -X POST http://localhost:8080/api/mem/latent/embed \
-H "Content-Type: application/json" \
-d '{"id":"doc1","content":"some text to embed"}'
# 200 OK, Body: embedded
# Query with dummy vector
curl -i -X POST http://localhost:8080/api/mem/latent/query \
-H "Content-Type: application/json" \
-d '{"content":"any text"}'
# 200 OK, Body: ["doc1", ...] (list of nearest IDs)
聊天与 Sentience
# Chat endpoint (JSON)
curl -i -X POST http://localhost:8080/api/chat \
-H "Content-Type: application/json" \
-d '{"message":"remember color = blue"}'
# 200 OK, Body: "Okay, remembered color = blue"
curl -i -X POST http://localhost:8080/api/chat \
-H "Content-Type: application/json" \
-d '{"message":"recall color"}'
# 200 OK, Body: "color = blue"
curl -i -X POST http://localhost:8080/api/chat \
-H "Content-Type: application/json" \
-d '{"message":"What is the meaning of life?"}'
# 200 OK, Body: <LLM‐generated text> (fallback if no DSL match)
# Direct Sentience DSL execution (execute code snippet immediately)
curl -i -X POST http://localhost:8080/api/sentience/run \
-H "Content-Type: application/json" \
-d '{"code":"on input(hi) { mem.short[\\"response\\"] = \\"Hello from DSL!\\" }"}'
# 200 OK, Body: {"output":""} (empty unless DSL code sets a return)
配置与环境变量
| 变量 | 描述 | 默认值 / 示例 |
|---|---|---|
CHROMADB_URL | ChromaDB HTTP API 的 URL(例如,http://localhost:8000)。 | http://localhost:8000 |
CHROMA_COLLECTION_ID | 用于存储/查询嵌入的 ChromaDB 集合 UUID。必须预先存在。 | 1414cedf-3081-4235-ab29-656549bdff1a |
LLM_URL | 大模型服务的基础 URL(用于备用文本生成)。 | http://localhost:11434 |
ICORE_ENV | 环境模式(development 或 production)。控制日志记录/过滤和优化。 | development |
RUST_LOG | tracing_subscriber 的日志过滤器。(例如,info)。 | info |
DATABASE_URL | (可选)用于长期记忆的 SQLite 文件路径(由 sqlx 使用)。默认值为 memory.db。 | memory.db |
AGENT_SENT_FILE | (可选)Sentience DSL 脚本的路径(默认为项目根目录下的 agent.sent)。 | ./agent.sent |
注意:
- 如果缺少环境变量,服务将在启动时发生 panic。
- 请始终确保
CHROMA_COLLECTION_ID与现有的 ChromaDB 集合 ID 相匹配。
API 参考
所有端点都位于 /api 前缀下(/health 除外)。
1. 健康检查
GET /health
-
响应
200 OK- 正文:纯文本
"ICORE server is healthy."
2. 短期记忆端点
2.1. 获取短期值
GET /api/mem/short/:key
-
路径参数
:key(字符串)– 要获取的键。
-
响应
200 OK+ 正文包含已存储的字符串值。404 Not Found+ 正文"key not found",表示键不存在。
-
示例
curl -i http://localhost:8080/api/mem/short/foo
2.2. 设置短期值
POST /api/mem/short/:key
Content-Type: application/json
{
"value": "<string>"
}
-
路径参数
:key(字符串)– 要设置的键。
-
请求正文
{ "value": "some text" } -
响应
200 OK+ 正文"stored"(始终如此)。
-
示例
curl -i -X POST http://localhost:8080/api/mem/short/foo \ -H "Content-Type: application/json" \ -d '{"value":"bar"}'
3. 长期记忆端点
3.1. 获取长期值
GET /api/mem/long/:key
-
路径参数
:key(字符串)– 要获取的键。
-
响应
200 OK+ 正文包含已存储的字符串值。404 Not Found+ 正文"key not found",表示键不存在。
-
示例
curl -i http://localhost:8080/api/mem/long/user123
3.2. 设置长期值
POST /api/mem/long/:key
Content-Type: application/json
{
"value": "<string>"
}
-
路径参数
:key(字符串)– 要设置的键。
-
请求正文
{ "value": "persistent data" } -
响应
200 OK+ 正文"stored"(始终如此)。
-
示例
curl -i -X POST http://localhost:8080/api/mem/long/user123 \ -H "Content-Type: application/json" \ -d '{"value":"persistent data"}'
4. 潜在记忆(ChromaDB)端点
4.1. 嵌入向量
POST /api/mem/latent/embed
Content-Type: application/json
{
"id": "<string>",
"content": "<string>"
}
-
请求正文
id(字符串)– 文档/嵌入的唯一标识符。content(字符串)– 要存储其嵌入的原始文本。- 注意:在当前原型中,处理程序会构造一个长度为 1536 的虚拟零向量。你可以修改
embed_latent,使其调用真实的嵌入服务。
-
响应
200 OK+ 正文"embedded",表示成功。500 Internal Server Error+ 正文"error",表示失败。
-
示例
curl -i -X POST http://localhost:8080/api/mem/latent/embed \ -H "Content-Type: application/json" \ -d '{"id":"doc1","content":"something to embed"}'
4.2. 查询最近的 ID
POST /api/mem/latent/query
Content-Type: application/json
{
"content": "<string>"
}
-
请求正文
content(字符串)– 用于相似度搜索的原始文本,其嵌入目前会转换为虚拟零向量。
-
响应
200 OK+ JSON 匹配 ID 数组(Vec<String>),按相似度降序排列(最高分在前)。500 Internal Server Error+ 纯文本错误消息,表示 ChromaDB 查询失败。
-
示例
curl -i -X POST http://localhost:8080/api/mem/latent/query \ -H "Content-Type: application/json" \ -d '{"content":"search terms"}'
5. 聊天端点
POST /api/chat
Content-Type: application/json
{
"message": "<string>"
}
-
请求正文
message(字符串)——用户的聊天输入。
-
行为
-
如果已加载 Sentience DSL,则构建 DSL 代码片段:
on input(<message>) { … }– Agent 通过内置 DSL 引擎执行此代码片段。 – 执行后,检查 DSL 是否在内部短期记忆中写入了
"response"键。如果有,则将其作为响应返回。 -
如果 DSL 未生成响应(或未加载 DSL):
- 检查是否有用于 BaseAgent 逻辑的
remember、recall或if context includes命令。 - 否则,回退到大模型:调用
model::generate(message).await,并返回生成的文本。
- 检查是否有用于 BaseAgent 逻辑的
-
-
响应
200 OK+ 包含 Agent 回复的 JSON 字符串。
-
示例
# Remember example curl -i -X POST http://localhost:8080/api/chat \ -H "Content-Type: application/json" \ -d '{"message":"remember mood = happy"}' # Recall example curl -i -X POST http://localhost:8080/api/chat \ -H "Content-Type: application/json" \ -d '{"message":"recall mood"}' # Generic question (LLM fallback) curl -i -X POST http://localhost:8080/api/chat \ -H "Content-Type: application/json" \ -d '{"message":"Tell me a joke."}'
6. Sentience DSL 端点
POST /api/sentience/run
Content-Type: application/json
{
"code": "<Sentience DSL code>"
}
-
请求正文
code(字符串)——要立即执行的原始 Sentience DSL 代码片段(不含on input(...))。
-
行为
- Agent 使用内部 DSL 引擎执行提供的
code代码片段。 - 返回 DSL 代码返回的内容(如果有),如果没有返回任何内容,则返回空字符串。
- Agent 使用内部 DSL 引擎执行提供的
-
响应
-
200 OK+ JSON:{ "output": "<string>" }- 如果 DSL 脚本未显式设置值,
output可能为空("")。
- 如果 DSL 脚本未显式设置值,
-
-
示例
curl -i -X POST http://localhost:8080/api/sentience/run \ -H "Content-Type: application/json" \ -d '{"code":"on startup() { mem.short[\\"greeting\\"] = \\"Hello from DSL!\\" }"}'
Agent DSL(“Sentience”)详情
Sentience DSL 允许你使用一种简单的内部脚本语言定义事件驱动逻辑。常见模式:
-
启动时加载 DSL
-
将 DSL 文件放在项目根目录下,命名为
agent.sent,或通过环境变量指定。 -
服务启动时,
main.rs会执行:if let Ok(sent_code) = fs::read_to_string("agent.sent") { base_agent.load_sentience(&sent_code)?; println!("Loaded Sentience DSL from agent.sent"); }
-
-
DSL 语法(示例)
// Defines behavior on input event on input(user_msg) { if user_msg == "ping" { mem.short["response"] = "pong from DSL"; } else if user_msg startswith "set " { // parse "set key=value" let kv = split(user_msg[4..], "="); mem.short[kv[0]] = kv[1]; } } // Defines behavior on startup on startup() { mem.short["welcome"] = "Welcome, dear user!"; } -
在 DSL 中访问记忆
mem.short[<key>] = <value>读取/写入短期记忆。mem.long[<key>] = <value>读取/写入长期记忆(SQLite)。- Agent 处理代码时会为这两种记忆填充初始数据,并在执行后刷新更新。
-
发送响应
- 若要通过 DSL 返回聊天响应,请设置
mem.short["response"] = "<some text>"。 - DSL 运行后,处理程序会检查
guard.inner.get_short("response"),如果值非空,则返回该值。
- 若要通过 DSL 返回聊天响应,请设置
-
构建 DSL 文件
- 在项目根目录下创建
agent.sent。 - 遵循你的 DSL 语法(如需了解准确语法,请查阅
sentiencecrate 文档)。
- 在项目根目录下创建
目录结构
inception-ICORE-server/
├── Cargo.toml
├── Dockerfile
├── docker-compose.dev.yml
├── docker-compose.prod.yml
├── README.md
├── agent.sent # (optional) Sentience DSL script
├── memory.db # SQLite database (auto-created on first run)
├── src/
│ ├── main.rs # Entry point: initializes mem, agent, starts Axum server
│ ├── config/ # Configuration loading (env, Settings struct)
│ │ └── settings.rs
│ ├── agents/
│ │ ├── agent.rs # BaseAgent implementation & DSL integration
│ │ └── mod.rs # Re-exports AGENT, BaseAgent, SentienceAgent
│ ├── api/
│ │ ├── routes.rs # Defines Axum router & endpoints
│ │ ├── handlers.rs # Endpoint handlers (ping, memory, chat, etc.)
│ │ └── mod.rs
│ ├── memory/
│ │ ├── short_term.rs # In-memory key-value store
│ │ ├── long_term.rs # SQLite-backed key-value store
│ │ ├── latent.rs # ChromaDB client for embeddings & queries
│ │ └── mod.rs
│ ├── ICORE/
│ │ ├── model.rs # LLM HTTP client & `generate(prompt)` function
│ │ └── context.rs # Context for BaseAgent (“remember/recall/if context includes”)
│ │ └── protocol.rs # ICORE protocol definitions (errors, etc.)
│ │ └── mod.rs
│ ├── config.rs? # top-level config module for env/settings
│ └── ... # other modules (utilities, etc.)
└── llama.cpp/ # (submodule) Llama.cpp code & Dockerfile for local LLM server
├── Dockerfile.server
└── ...
日志记录与监控
- 使用
tracing和tracing_subscriber进行结构化日志记录。 - 日志级别由
RUST_LOG环境变量控制(例如,info、debug、warn)。 - 在开发环境中,将
RUST_LOG=debug设置为debug,即可查看详细的请求/响应和 SQL 查询。 - 默认将日志打印到 stdout(适用于 Docker)。
开发工作流
-
代码格式化与 lint 检查
rustup component add rustfmt clippy cargo fmt --all cargo clippy --all -- -D warnings -
运行测试 如果添加了单元/集成测试:
cargo test -
添加新端点
- 在
src/api/routes.rs中定义路由。 - 在
src/api/handlers.rs中实现处理程序。 - 更新任何相关的记忆/Agent 逻辑。
- 在 README 中添加 curl 示例作为文档。
- 在
-
扩展记忆层
- 短期:修改
memory/short_term.rs以更改内存存储。 - 长期:更改 SQLite 架构,或通过更新
LongTermMemory和DATABASE_URL切换到 Postgres。 - 潜在:更新
memory/latent.rs,改为调用真实的嵌入服务,而非模拟服务。
- 短期:修改
-
更新 DSL 脚本
- 编辑项目根目录下的
agent.sent。 - 重新加载服务,或直接发送
POST /api/sentience/run来测试新逻辑。
- 编辑项目根目录下的
贡献
欢迎贡献!请遵循以下指南:
-
Fork 此仓库。
-
创建一个功能分支:
git checkout -b feature/my-new-feature -
使用清晰、描述性强的消息提交更改。
-
运行格式化和 lint 检查:
cargo fmt --all cargo clippy --all -- -D warnings -
视情况添加或更新测试。
-
推送到你的 fork,并在 GitHub 上发起 Pull Request。
提交审核请求前,请确保所有 CI 检查(格式化、lint、测试)均已通过。
许可证
⚠️ 注意:本项目仍处于实验阶段,无法保证稳定性或可用于生产环境。
本项目采用专有非商业许可证。
本项目可免费用于研究、教育和实验,但未经书面许可,严禁用于商业用途。
完整条款请参阅 LICENSE。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。