# ReMe 代码框架 ## 1. 总览 ReMe 的运行时可以理解为:**配置驱动的 Application 把组件和 Job 装配起来,Service 把可服务的 Job 暴露给 CLI、HTTP 或 MCP,Job 再按顺序执行 Step**。

ReMe 代码框架结构:CLI、Service、Application、Job、Step 与 Component

如果只想先运行和使用 ReMe,见 [快速开始](./quick_start.md)。workspace 文件语义见 [Memory as File](./memory_as_file.md);检索、 自动记忆和主动读取的用户侧说明分别见 [Memory Search](./memory_search.md)、[Auto Memory](./auto_memory.md)、 [Auto Resource](./auto_resource.md)、[Auto Dream](./auto_dream.md) 和 [Proactive](./proactive.md)。 ### 能力边界 ReMe v4 聚焦长期记忆:把对话和资源沉淀为 `daily/`,再整理到 `digest/`,并通过 CLI、HTTP、MCP 暴露写入、检索和主动读取能力。 单会话上下文窗口管理不属于 ReMe v4 的职责范围,例如对当前会话做压缩、摘要注入、工具输出裁剪,或提供独立 `/compact` 接口。需要这类能力时,应在上层 Agent 框架中处理;ReMe 只接收已经发生的对话、资源或文件变更,并把其中有长期价值的信息持久化。 ```mermaid flowchart LR CLI["reme CLI
reme/reme.py"] --> Client["Client
http / mcp"] Client --> Service["Service
HTTP / MCP"] Service --> App["Application
reme/application.py"] App --> Jobs["Jobs
base / stream / background / cron"] Jobs --> Steps["Steps
reme/steps/**"] Steps --> Ctx["RuntimeContext
data + Response + stream queue"] Steps --> Components["Components
store / graph / index / llm / agent / catalog"] Components --> Workspace["Workspace
daily / digest / resource / metadata"] ``` 核心分层: | 层 | 主要目录 | 职责 | |-------------|----------------------------|---------------------------------------------------------------------------| | CLI | `reme/reme.py` | 解析命令;`start` 启动服务;其他 action 通过 client 调用服务 | | Service | `reme/components/service/` | 把 Job 注册成 HTTP endpoint 或 MCP tool | | Application | `reme/application.py` | 读取配置后的对象装配、依赖拓扑启动、关闭、Job 调用 | | Job | `reme/components/job/` | 编排一组 Step;决定同步、流式、后台、定时运行方式 | | Step | `reme/steps/` | 业务原子操作,例如读写文件、检索、索引、自进化 | | Component | `reme/components/` | 可复用基础设施,例如 file_store、file_graph、keyword_index、agent_wrapper | | Schema | `reme/schema/` | `Request`、`Response`、`FileChunk`、`FileNode`、配置模型等数据结构 | | Config | `reme/config/` | 默认 YAML 配置和命令行覆盖解析 | ## 2. 目录结构 ```text reme/ reme.py # CLI 入口 application.py # Application 装配与生命周期 plugin.py # 已安装插件契约与 entry-point loader config/ default.yaml # 默认 service / jobs / components config_parser.py # config=、dot notation、env 占位符解析 components/ component_registry.py # backend 注册表及 Application 局部副本 base_component.py # ComponentMixin / BaseComponent / bind 依赖声明 runtime_context.py # 单次 Job 执行上下文 job/ # BaseJob / StreamJob / BackgroundJob / CronJob service/ # HTTP / MCP 服务 client/ # HTTP / MCP 客户端 file_store/ # 文件索引协调层 file_graph/ # wikilink 图谱 keyword_index/ # BM25 等关键词索引 file_chunker/ # Markdown / JSON / JSONL / 通用文本分块 file_catalog/ # 变更 checkpoint as_llm/, as_embedding/ # 模型封装 agent_wrapper/ # AgentScope / Claude Code / Codex wrapper steps/ base_step.py # BaseStep、Ref、dispatch_steps common/ # version、help、health_check、status、chat benchmark/ # LongMemEval / BEAM 评测步骤 cookbook/ # 内置 cookbook 支持步骤 file_io/ # read/write/edit/delete/move/frontmatter/daily index/ # watch/init/update/search/traverse evolve/ # auto_memory、auto_resource、auto_dream、proactive transfer/ # upload/download plugins/ auto-fin/ # 独立发布的示例插件 daily_paper/ # 独立发布的论文研究插件 integrations/ claude_code/ # Claude Code 适配器及 marketplace hermes_agent/ # Hermes Agent memory provider 适配器 ``` 默认 workspace 目录由 `ApplicationConfig` 定义: ```text / metadata/ # file_store、file_graph、keyword_index、file_catalog 等持久状态 session/ # 记忆工作流使用的对话来源记录 mem_session/ # Agent wrapper 生成的 session 和配置 resource/ # 外部资源 daily/ # 浅加工记忆 digest/ # 长期 digest 记忆 ``` `Application.__init__()` 会先确保这些目录存在,然后初始化 service、components、jobs。 ## 3. 启动与调用链 ### 3.1 CLI 入口是 `reme/reme.py::main()`: ```mermaid flowchart LR A["main()"] --> B["parse_args(*sys.argv[1:])"] B --> C{action} C -->|" start "| D["load_env()"] D --> E["resolve_app_config(**kwargs)"] E --> F["precheck_start(service)"] F --> G["ReMe(**config).run_app()"] C -->|" find_reme "| H["cli_find_reme()"] C -->|" 其他 action "| I["call_server(action, **kwargs)"] I --> J["R.get(ComponentEnum.CLIENT, backend)"] J --> K["client(action=action, **kwargs)"] ``` 常用命令: ```bash reme start reme start service.port=8181 reme version reme search query="memory" limit=5 reme search query="memory" backend=mcp ``` 配置解析支持: | 能力 | 源码 | 说明 | |--------------|-------------------------|---------------------------------------------------| | 默认配置 | `resolve_app_config()` | 未指定 `config` 时加载 `reme/config/default.yaml` | | 指定配置 | `config=` | 可传内置配置名或 YAML/JSON 文件路径 | | dot notation | `parse_dot_notation()` | 例如 `service.port=8181` | | 环境变量 | `_expand_env_vars()` | 支持 `${VAR}` 和 `${VAR:-default}` | | 值转换 | `_convert_value()` | bool、int、float、JSON list/dict/null 会自动转换 | ### 3.2 Service `BaseService.run_app()` 的顺序: 可通过可选的 `service.jobs` 列表将 HTTP 或 MCP 仅暴露给指定 Job。未配置时,所有 `enable_serve: true` 的 Job 仍可被暴露;配置为空列表时不暴露任何 Job。该白名单不会覆盖 `enable_serve: false`。配置该列表后,缺失、禁用、不受支持或无效的已选 Job 会导致服务启动失败。 ```mermaid flowchart LR A["Service.build_service(app)"] --> B["读取 app.context.jobs"] B --> C{"已启用且被 service.jobs 选中?"} C -->|是| D["Service.add_job(job)"] C -->|否| E["跳过注册"] D --> F["Service.start_service(app)"] E --> F F --> G["lifespan 中 app.start()"] G --> H["Application 启动 jobs"] ``` HTTP service 行为: | Job 类型 | HTTP 暴露方式 | |----------------------------------------|----------------------------------------------| | 非 `StreamJob` 且 `enable_serve: true` | `POST /`,返回 `Response` JSON | | `StreamJob` | `POST /`,返回 `text/event-stream` | | `enable_serve: false` | 不注册 endpoint | HTTP service 还可以在所有 Job endpoint 注册完成后挂载 ReMe Studio 单页应用。默认 `service.web_enabled=true`;构建产物按 `service.web_static_dir`、`REME_WEB_STATIC_DIR`、由 `web` 或 `core` extra 安装的可选 `reme_studio` 包,以及源码树 `reme_studio/dist-static` 等候选位置解析。找不到 `index.html` 时只跳过前端,Job API 仍然可用。Studio 的 `GET` fallback 不会覆盖 已有的 `POST /`。 MCP service 行为: | Job 类型 | MCP 暴露方式 | |----------------------------------------|-------------------------------------------| | 非 `StreamJob` 且 `enable_serve: true` | 注册为 MCP tool | | `StreamJob` | 当前跳过,不注册 | | `BackgroundJob` | 构造时强制 `enable_serve=False`,不会暴露 | MCP 服务可通过 `injected_job_kwargs` 注入由服务端管理的参数,调用方不能覆盖这些参数。设置 `tool_error_on_failure: true` 后,不成功的 ReMe `Response` 会作为 MCP tool error 返回。 ## 4. Registry 与依赖注入 ### 4.1 全局注册表 R ReMe 使用进程级单例 `R = ComponentRegistry()`。所有组件、Job、Step 都通过 `@R.register("name")` 注册。 ```python from ...components import R @R.register("version_step") class VersionStep(BaseStep): ... ``` 注册表 key 是: ```text (component_type, register_name) -> class ``` 其中 `component_type` 来自类属性,例如: | 类型 | 类属性 | |-----------|-----------------------------------------------------------| | Step | `BaseStep.component_type = ComponentEnum.STEP` | | Job | `BaseJob.component_type = ComponentEnum.JOB` | | Service | `BaseService.component_type = ComponentEnum.SERVICE` | | FileStore | `BaseFileStore.component_type = ComponentEnum.FILE_STORE` | 所以同名 backend 在不同 component type 下可以共存。例如 `http` 同时可以是 service backend 和 client backend。 `ComponentEnum` 提供内置类型标识;已安装插件也可以用 `example.reranker` 这样的命名空间字符串声明新类型。自定义标识仅使用 小写字母和数字,并以 `.`、`_` 或 `-` 分隔。它们配置在 `components` 下,与内置组件参与相同的依赖排序和生命周期。 ### 4.2 内置注册与插件注册 内置实现通过 package import 填充内置注册表;bootstrap 完成后 ReMe 会冻结这个模板,并为每个 `Application` 创建可写副本。 运行期代码通过当前 Application 的注册表解析 backend,不能修改进程级模板。随后只加载最终配置中 `plugins` 明确启用的已安装插件。 插件通过 Python `reme.plugins` entry-point group 暴露其 package。package 内的 `plugin.yaml` 只有两个可选 mapping: `backends` 将注册名映射到 `module:Class`,`application_defaults` 提供低优先级的 `ApplicationConfig` 配置片段。 entry-point 名称就是插件标识;使用 应用配置的 `plugins` 列表或 CLI 的 `plugins=[...]` override 显式启用插件。插件注册因此只影响当前 Application;两个插件提供相同 `(component_type, backend)` 时会在装配阶段失败, 不会互相覆盖。 迁移期间仍兼容旧的 Python `Plugin` descriptor 和 `reme.configs` entry point。配置的 `extends` 可以继承内置配置、 旧插件配置或文件配置。独立打包示例见 [Auto Fin](../../plugins/auto-fin/README_ZH.md) 与 [每日论文](../../plugins/daily_paper/README_ZH.md) 插件。 插件包的本地管理与单个应用是否启用插件相互独立: ```bash reme plugins list reme plugins install reme-auto-fin reme plugins install reme-daily-paper reme plugins show daily-paper reme plugins validate daily-paper reme plugins uninstall daily-paper reme start plugins='["auto-fin","daily-paper"]' ``` 这些管理命令使用当前 Python 解释器对应的 pip,不通过 HTTP 或 MCP service 执行。 ### 4.3 Component.bind 组件之间的依赖用 `BaseComponent.bind()` 声明。启动时 `Application._topological_order()` 读取每个组件的 `dependencies` ,按拓扑顺序启动。 ```mermaid flowchart LR A["Component.__init__
self.keyword_index = self.bind(...)"] --> B["Dependency placeholder"] B --> C["Application._topological_order()"] C --> D["component.start()"] D --> E["_resolve_bindings()"] E --> F["self.keyword_index = app_context.components[type][name]"] F --> G["component._start()"] ``` `BaseComponent.bind(name, BaseClass, optional=True)` 的规则: | 场景 | 行为 | |-----------------------------|-----------------------------------------------| | `name` 为空 | 返回 `None`,跳过依赖 | | `app_context` 存在 | 从 `app_context.components[ctype][name]` 查找 | | 依赖缺失且 `optional=True` | 解析为 `None` | | 依赖缺失且 `optional=False` | 启动时报错 | | standalone 模式 | 可用 `default_factory` 创建自有组件 | ### 4.4 Step.Ref Step 不参与组件拓扑启动,它每次 Job 调用时临时创建。Step 访问组件主要靠 `BaseStep.Ref`: ```python file_store: BaseFileStore = Ref(BaseFileStore, ComponentEnum.FILE_STORE) agent_wrapper: BaseAgentWrapper = Ref(BaseAgentWrapper, ComponentEnum.AGENT_WRAPPER, optional=True) ``` 解析优先级: ```mermaid flowchart LR A["访问 self.file_store"] --> B{"kwargs 里有同名对象?"} B -->|是| C["使用 kwargs 对象"] B -->|否| D{"context.data 里有同名对象?"} D -->|是| E["使用 context 对象"] D -->|否| F["读取 kwargs['file_store'] 名称,默认 default"] F --> G["app_context.components[FILE_STORE][name]"] ``` 因此在 step 配置里可以写: ```yaml steps: - backend: update_catalog_step file_catalog: resource ``` 这里 `file_catalog: resource` 表示解析名为 `resource` 的 `file_catalog` 组件。 ## 5. Application 生命周期 `Application` 的职责是把配置转换成运行时对象,并按顺序启动和关闭。 ```mermaid flowchart LR A["Application(**kwargs)"] --> B["ApplicationContext(**kwargs)
解析 ApplicationConfig"] B --> C["_setup_workspace_directories()"] C --> D["_init_service()"] D --> E["_init_components()"] E --> F["_init_jobs()"] F --> G["run_app()"] G --> H["service.run_app(app)"] ``` 启动顺序在 `Application._start()` 中: ```mermaid flowchart LR A["创建 thread_pool,可选"] --> B["components 拓扑排序"] B --> C["启动 components"] C --> D["启动 BaseJob"] D --> E["启动 StreamJob"] E --> F["启动 BackgroundJob"] F --> G["启动 CronJob"] ``` 关闭时按 `_started_components` 的反序关闭,保证依赖方先关闭,被依赖方后关闭。 ## 6. Job 模型 Job 是外部可调用能力或后台任务的编排单元。配置位置是 `reme/config/default.yaml` 的 `jobs:`。 ### 6.1 BaseJob `BaseJob` 是最常见的请求型 Job: ```mermaid flowchart LR Caller["Caller"] --> Job["BaseJob
job(**kwargs)"] Job --> Ctx["RuntimeContext
merged_kwargs"] Ctx --> S1["Step 1
await step(context)"] S1 --> D1["读写 context.data / response"] D1 --> S2["Step 2
await step(context)"] S2 --> D2["读写 context.data / response"] D2 --> Resp["context.response"] Resp --> Caller ``` 关键源码行为: | 源码 | 行为 | |------------------|--------------------------------------------------------| | `_start()` | 把 YAML 中每个 step config 解析成 `(step_cls, params)` | | `_build_steps()` | 每次调用都创建新的 Step 实例,避免跨请求共享状态 | | `__call__()` | 创建 `RuntimeContext`,按顺序执行 step | | 异常处理 | 捕获异常,`response.success=False`,`answer=str(e)` | ### 6.2 StreamJob `StreamJob` 继承 `BaseJob`,但返回流式 chunk: | 行为 | 说明 | |-----------|-----------------------------------------------------------| | context | 带 `stream_queue` | | Step 输出 | 调用 `context.add_stream_string(text, ChunkEnum.CONTENT)` | | 异常 | 写入 `ChunkEnum.ERROR` | | 结束 | 总是发送 `DONE` chunk | ### 6.3 BackgroundJob `BackgroundJob` 用于长运行循环,例如文件监听。它在构造时强制 `enable_serve=False`。 ```mermaid flowchart LR A["Application 启动 BackgroundJob"] --> B["_start() 创建 stop_event 和 task"] B --> C["_run_with_supervisor()"] C --> D["await self()"] D --> E{"异常?"} E -->|否,正常返回| F["结束"] E -->|是且 supervisor = True| G["指数退避 + jitter"] G --> C E -->|是且 supervisor = False| H["抛出异常"] I["close()"] --> J["stop_event.set()"] J --> K["等待 close_timeout,超时 cancel"] ``` 默认 `BackgroundJob.__call__()` 也会按顺序执行配置里的 steps,但异常不会被吞掉,便于 supervisor 重启。 ### 6.4 CronJob `CronJob` 继承 `BackgroundJob`,增加 `cron` 表达式: ```yaml jobs: nightly_dream: backend: cron cron: "0 3 * * *" steps: - backend: dream_extract_step - backend: dream_integrate_step - backend: dream_topics_step - backend: dream_finish_step ``` 当前实现使用 `croniter` 计算下一次触发时间,时区来自 `app_config.timezone`。 ### 6.5 默认 Job 类型分布 ```mermaid flowchart LR Jobs["default.yaml jobs"] --> BG["background
index_update_loop
resource_watch_loop
digest_watch_loop"] Jobs --> Cron["cron
dream_cron
optimize_index_cron"] Jobs --> Stream["stream
chat"] Jobs --> Base["base
version / help / health_check / status / app_config
search / node_search / traverse / graph_snapshot / reindex
read / load / read_image / write / save / edit / delete / move / list / stat / frontmatter_*
daily_list / daily_reindex / daily_write
auto_memory / auto_memory_cc / auto_resource / auto_dream / proactive"] ``` ## 7. Step 模型 Step 是具体业务动作。所有 Step 都继承 `BaseStep` 并实现 `execute()`。 ```mermaid flowchart LR A["Job._build_steps()"] --> B["Step.__init__()"] B --> C["加载 prompt
类名对应 YAML + prompt_dict override"] C --> D["Step.__call__(context, **kwargs)"] D --> E["清理 Ref cache"] E --> F["RuntimeContext.from_context()"] F --> G["input_mapping"] G --> H["execute()"] H --> I["output_mapping"] I --> J["返回 result"] ``` ### 7.1 RuntimeContext `RuntimeContext` 是一次 Job 调用内所有 Step 共享的上下文: | 字段 | 说明 | |----------------|--------------------------------------------------| | `response` | 最终返回的 `Response(answer, success, metadata)` | | `data` | 自由字典,保存输入参数和中间结果 | | `stream_queue` | 流式 Job 的输出队列 | | `stop_event` | 后台 Job 的停止信号 | Step 里常见写法: ```python assert self.context is not None query = self.context.get("query", "") self.context["processed_query"] = query.strip().lower() self.context.response.answer = "..." self.context.response.metadata["key"] = "value" return self.context.response ``` ### 7.2 input_mapping / output_mapping `BaseStep.__call__()` 会在执行前后调用 `RuntimeContext.apply_mapping()`: ```yaml steps: - backend: some_step input_mapping: user_query: query output_mapping: result: final_result ``` 语义是把 `context.data[source]` 复制到 `context.data[target]`。 ### 7.3 dispatch_steps 部分 Step 会产生批量事件,然后把事件分发给其他 Step。`BaseStep.dispatch_steps()` 会按配置解析并执行子 Step。 默认配置中的例子: ```yaml index_update_loop: backend: background watch_dirs: [ daily_dir, digest_dir ] watch_suffixes: [ md ] steps: - backend: init_changes_step monitor_type: file_store monitor_name: default dispatch_steps: [ update_index_step ] - backend: watch_changes_step dispatch_steps: [ update_index_step ] ``` 流程图: ```mermaid flowchart LR Init["init_changes_step"] --> Batch["changes batch"] Watch["watch_changes_step"] --> Batch Batch --> Dispatch["dispatch_steps(...)"] Dispatch --> Update["update_index_step"] Update --> Store["file_store"] ``` ## 8. 默认配置里的组件 `reme/config/default.yaml` 当前默认组件: | ComponentEnum | 名称 | backend | 说明 | |-------------------|---------------------------------|-----------------------------|------------------------------------------------------------| | `service` | 单例 | `http` | 默认 HTTP 服务 | | `tokenizer` | `default` | `regex` | BM25 分词器 | | `as_embedding` | `default` | 默认未配置;示例为 `openai` | 取消配置注释后提供 embedding 模型封装 | | `embedding_store` | `default` | 默认未配置;示例为 `local` | 取消配置注释后依赖 `as_embedding: default` | | `as_llm` | `default` | `${LLM_BACKEND:-openai}` | LLM 模型封装 | | `agent_wrapper` | `default` | `agentscope` | AgentScope wrapper | | `agent_wrapper` | `claude_code` | `claude_code` | Claude Code wrapper | | `agent_wrapper` | `codex/codex_oauth` | `codex` | 分别使用 API key 与 OAuth 的 Codex wrapper | | `file_graph` | `default` | `local` | wikilink 图谱 | | `file_catalog` | `default/resource/digest/dream` | `local` | 文件变更 checkpoint | | `file_chunker` | `markdown` | `markdown` | Markdown AST 分块 | | `file_chunker` | `json/jsonl/default` | `json/jsonl/default` | JSON、JSONL 与通用文本分块;默认通用分块支持 `txt`、`log` | | `keyword_index` | `default` | `bm25` | BM25 关键词索引 | | `file_store` | `default` | `local` | 组合 file_graph、keyword_index;默认 `embedding_store: ""` | 注意:`search` step 的配置含 `vector_weight`,但默认 `file_store.default.embedding_store` 为空,因此实际是否有向量检索取决于运行配置是否启用 embedding store。 ## 9. 新增 Step ### 9.1 最小 Step 假设要新增一个把输入文本转大写的 Step。 新建文件,例如 `reme/steps/common/uppercase.py`: ```python from ..base_step import BaseStep from ...components import R @R.register("uppercase_step") class UppercaseStep(BaseStep): async def execute(self): assert self.context is not None text = self.context.get("text", "") result = str(text).upper() self.context["uppercase_text"] = result self.context.response.answer = result self.context.response.metadata["length"] = len(result) return self.context.response ``` ### 9.2 让 Step 被注册 确认 `reme/steps/common/__init__.py` import 了新模块。新增: ```python from . import uppercase ``` 原因:`@R.register("uppercase_step")` 只有在模块被 import 后才会执行。 ### 9.3 访问组件 如果 Step 需要访问已有组件,优先使用 `BaseStep` 已提供的 Ref: ```python class MySearchStep(BaseStep): async def execute(self): assert self.context is not None results = await self.file_store.keyword_search( self.context.get("query", ""), limit=5, ) ... ``` 可直接用的常见属性: | 属性 | 默认解析的组件 | |----------------------|--------------------------------| | `self.as_llm` | `as_llm: default` 的 `.model` | | `self.agent_wrapper` | `agent_wrapper: default`,可选 | | `self.file_catalog` | `file_catalog: default`,可选 | | `self.file_store` | `file_store: default` | 如果希望 Job 配置指定非 default 组件: ```yaml steps: - backend: my_step file_catalog: dream ``` ### 9.4 Step 设计建议 | 建议 | 原因 | |-----------------------------------------------------|--------------------------------------------------| | 从 `context` 读取输入,向 `context` 写中间结果 | 多 Step Job 依赖同一个上下文传递数据 | | 最终结果写到 `context.response` | Service 和 client 只关心标准 `Response` | | 不在 Step 实例上保存请求级状态 | 每次 Job 调用会重建 Step,但保持无状态更容易测试 | | 需要中断的后台循环检查 `context.stop_event` | `BackgroundJob.close()` 依赖 stop_event 优雅退出 | | 流式输出只在 StreamJob 中调用 `add_stream_string()` | 普通 Job 没有 stream queue | ### 9.5 单测示例 可以直接实例化 Step 并传入 `RuntimeContext`: ```python import pytest from reme.components.runtime_context import RuntimeContext from reme.steps.common.uppercase import UppercaseStep @pytest.mark.asyncio async def test_uppercase_step(): ctx = RuntimeContext(text="hello") resp = await UppercaseStep()(ctx) assert resp.answer == "HELLO" assert ctx["uppercase_text"] == "HELLO" ``` ## 10. 新增 Job Job 通常不需要写 Python 类,只需要在配置里编排已有 Step。只有需要新的运行方式时,才新增 Job backend。 ### 10.1 新增普通请求型 Job 在 YAML 配置的 `jobs:` 下新增: ```yaml jobs: uppercase: backend: base description: "Convert text to uppercase." parameters: type: object properties: text: type: string description: "input text" required: - text steps: - backend: uppercase_step ``` 启动后调用: ```bash reme start reme uppercase text="hello" ``` 调用链: ```mermaid flowchart LR CLI["CLI
reme uppercase text=hello"] --> HTTP["HTTP Client"] HTTP --> Req["POST /uppercase"] Req --> S["HttpService"] S --> J["uppercase BaseJob
job(text='hello')"] J --> Step["uppercase_step
await step(context)"] Step --> Resp["context.response.answer = HELLO"] Resp --> JSON["Response JSON"] JSON --> CLIOut["CLI print answer"] ``` ### 10.2 新增多 Step Job 一个 Job 可以串联多个 Step: ```yaml jobs: demo_echo: backend: base description: "Normalize query, then echo it." parameters: type: object properties: query: type: string default: "" min_score: type: number default: 0.5 steps: - backend: demo_echo_step1 - backend: demo_echo_step2 ``` 第一个 Step 写入: ```text context["processed_query"] context["adjusted_min_score"] ``` 第二个 Step 再读取这些字段并写最终 `response`。 ### 10.3 新增 Stream Job 配置使用 `backend: stream`: ```yaml jobs: stream_uppercase: backend: stream description: "Stream uppercase text." parameters: type: object properties: text: type: string required: - text steps: - backend: uppercase_prepare_step - backend: uppercase_stream_step ``` 流式 Step 示例: ```python from ..base_step import BaseStep from ...components import R from ...enumeration import ChunkEnum @R.register("uppercase_stream_step") class UppercaseStreamStep(BaseStep): async def execute(self): assert self.context is not None for ch in self.context.get("uppercase_text", ""): await self.context.add_stream_string(ch, ChunkEnum.CONTENT) return self.context.response ``` ### 10.4 新增后台 Job 配置使用 `backend: background`: ```yaml jobs: my_watch_loop: backend: background watch_dirs: [ daily_dir ] watch_suffixes: [ md ] steps: - backend: init_changes_step monitor_type: file_store monitor_name: default dispatch_steps: [ update_index_step ] - backend: watch_changes_step dispatch_steps: [ update_index_step ] ``` 后台 Job 的特点: | 特点 | 说明 | |---------------|------------------------------------------------------| | 不对外暴露 | `BackgroundJob.__init__()` 强制 `enable_serve=False` | | 有 supervisor | 默认异常后指数退避重启 | | 有 stop_event | close 时通知循环退出 | | 适合监听/消费 | 文件监听、队列消费、周期性长循环 | ### 10.5 新增 Cron Job 配置使用 `backend: cron`: ```yaml jobs: daily_auto_dream: backend: cron cron: "30 3 * * *" steps: - backend: dream_extract_step file_catalog: dream - backend: dream_integrate_step - backend: dream_topics_step - backend: dream_finish_step file_catalog: dream ``` `cron` 表达式无效时会在启动时报错。 ### 10.6 什么时候需要新增 Job backend 大多数场景只需要新增 Step + YAML Job。只有这些情况才考虑新增 `reme/components/job/*.py`: | 需求 | 是否需要新 Job 类 | |----------------------------|------------------------------| | 新增一个业务命令 | 否,用 `backend: base` | | 串联多个已有步骤 | 否,用 `steps:` | | 要 SSE/流式输出 | 否,用 `backend: stream` | | 要后台循环 | 否,用 `backend: background` | | 要 cron 定时 | 否,用 `backend: cron` | | 要全新的调度/并发/事务语义 | 是,新增 Job backend | 新增 Job backend 的最小形态: ```python from .base_job import BaseJob from ..component_registry import R @R.register("my_job_backend") class MyJob(BaseJob): async def __call__(self, **kwargs): # 自定义调度逻辑 return await super().__call__(**kwargs) ``` 同样需要确保模块被 `reme/components/job/__init__.py` import。