Merge remote-tracking branch 'upstream/main' into feat/resource-image-caption

This commit is contained in:
wang-qisen 2026-09-04 18:23:46 +08:00
commit 552940211f
129 changed files with 6978 additions and 2757 deletions

View file

@ -28,6 +28,7 @@ jobs:
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
fetch-depth: 0
persist-credentials: false
- name: Set up Node

View file

@ -11,11 +11,17 @@ on:
- 'README_ZH.md'
- 'docs/**'
- 'github-pages/**'
- 'reme/config/default.yaml'
- 'integrations/claude_code/README.md'
- 'integrations/hermes_agent/README.md'
- 'reme_studio/README*.md'
- 'reme_studio/public/og.jpg'
- 'typescript/README*.md'
- 'typescript/docs/**'
- 'typescript/figures/**'
- 'plugins/*/README*.md'
- 'benchmark/*/README*.md'
- 'benchmark/toolmemory/gitcha.png'
pull_request:
branches: [main, master, dev, develop]
paths:
@ -26,11 +32,17 @@ on:
- 'README_ZH.md'
- 'docs/**'
- 'github-pages/**'
- 'reme/config/default.yaml'
- 'integrations/claude_code/README.md'
- 'integrations/hermes_agent/README.md'
- 'reme_studio/README*.md'
- 'reme_studio/public/og.jpg'
- 'typescript/README*.md'
- 'typescript/docs/**'
- 'typescript/figures/**'
- 'plugins/*/README*.md'
- 'benchmark/*/README*.md'
- 'benchmark/toolmemory/gitcha.png'
workflow_dispatch:
concurrency:

View file

@ -34,7 +34,7 @@ jobs:
- name: Install
run: |
pip install -q -e reme_studio -e ".[dev,core]"
pip install -q --no-deps -e plugins/auto-fin -e plugins/daily_paper
pip install -q --no-deps -e plugins/auto-fin -e plugins/daily_paper -e plugins/lme -e plugins/beam
- name: Pre-commit starts
run: pre-commit run --all-files

View file

@ -40,11 +40,12 @@ jobs:
pip install -e reme_studio -e ".[dev,core]"
pip install --no-deps -e plugins/auto-fin
pip install -e plugins/daily_paper
pip install -e plugins/lme -e plugins/beam
pip install coverage
- name: Run unit tests
run: |
coverage run -m pytest tests/unit plugins/auto-fin plugins/daily_paper \
coverage run -m pytest tests/unit plugins/auto-fin plugins/daily_paper plugins/lme plugins/beam \
-v \
--tb=long \
-s \

View file

@ -6,13 +6,19 @@ on:
paths:
- "github-pages/**"
- "docs/**"
- "reme/config/default.yaml"
- "integrations/claude_code/README.md"
- "integrations/hermes_agent/README.md"
- "README.md"
- "README_ZH.md"
- "reme_studio/README*.md"
- "reme_studio/public/og.jpg"
- "typescript/README*.md"
- "typescript/docs/**"
- "typescript/figures/**"
- "plugins/*/README*.md"
- "benchmark/*/README*.md"
- "benchmark/toolmemory/gitcha.png"
- "AGENTS.md"
- ".github/workflows/deploy-docs.yml"
- ".github/workflows/_build-docs.yml"

View file

@ -2,8 +2,9 @@
# 1. Update typescript/package.json and package-lock.json to the release version and merge them.
# 2. Configure npm Trusted Publishing for agentscope-ai/ReMe and this workflow file.
# 3. Run this workflow manually with the exact package version (an optional v prefix is accepted).
# 4. Configure ClawHub Trusted Publishing for agentscope-ai/ReMe and this workflow file.
# 5. Use the `next` tag for prereleases and `latest` only for stable releases.
# 4. Keep the ClawHub publication docs in typescript/docs/openclaw.md and openclaw.zh-CN.md.
# 5. Configure ClawHub Trusted Publishing for agentscope-ai/ReMe and this workflow file.
# 6. Use the `next` tag for prereleases and `latest` only for stable releases.
name: Release / TypeScript integrations
@ -112,6 +113,8 @@ jobs:
- name: Pack ClawHub tarball
working-directory: typescript
run: |
test -f docs/openclaw.md
test -f docs/openclaw.zh-CN.md
mkdir -p "${RUNNER_TEMP}/reme-clawhub-package"
npm run pack:clawhub -- "${RUNNER_TEMP}/reme-clawhub-package"
@ -171,7 +174,7 @@ jobs:
tags: ${{ inputs.npm_tag }}
source_repo: ${{ github.repository }}
source_commit: ${{ github.sha }}
source_ref: ${{ github.ref }}
source_ref: ${{ github.sha }}
source_path: typescript
package_artifact_name: agentscope-ai-reme-clawhub-${{ inputs.version }}
dry_run: false

View file

@ -46,8 +46,8 @@ and concise documentation together.
- `reme/components/service/`: local CLI, HTTP, and MCP service backends.
- `reme/components/`: agent wrappers, model adapters, stores, catalogs, graphs, indexes, clients, tokenizers, and
outbound proxies.
- `reme/steps/`: registered job steps grouped by common, file I/O, index, evolve, cookbook, benchmark, and transfer
concerns.
- `reme/steps/`: registered job steps grouped by common, file I/O, index, evolve, cookbook, and transfer
concerns, plus shared benchmark base classes under `benchmark/`.
- `reme/utils/`: shared utilities, including service discovery, logging, web-static resolution, session I/O, token
accounting, and wikilink handling.
- `tests/unit/`: primary fast, isolated validation suite.
@ -56,12 +56,14 @@ and concise documentation together.
`@agentscope-ai/reme_studio` npm static distribution.
- `typescript/`: the independently published `@agentscope-ai/reme` package, including the shared TypeScript client and
DeepSeek Harness and OpenClaw adapters.
- `plugins/`: installable ReMe extensions, such as Auto Fin.
- `plugins/`: installable ReMe extensions, including Auto Fin and LME/BEAM benchmark Steps and application presets.
- `integrations/`: adapters that connect ReMe to external agent hosts, such as Claude Code, DSH, and Hermes Agent.
- `skills/`: standalone skills; `reme_memory` calls ReMe, while other skills may use separate tools or direct-file
conventions.
- `benchmark/` and `cookbook/`: runnable evaluations and example workflows.
- `docs/`: README-linked supporting pages and figures.
- `github-pages/`: VitePress build shell, generated-content assembly, documentation checks, and GitHub Pages output. The
canonical theme and guides remain under `docs/`; `.generated/` and `dist/` are disposable.
## Development Setup
@ -195,6 +197,9 @@ Integration tests may contact real model providers, services, or agent subproces
run credentialed or externally mutating tests automatically; run them only when the task requires them and the necessary
environment has been supplied or authorized. Mock network, model, and subprocess boundaries in unit tests.
If documentation or the documentation theme changes, run `npm test` and `npm run build` from `github-pages/`. The Job
reference is generated from `reme/config/default.yaml`; do not edit generated pages directly.
## Change Guardrails
- Preserve unrelated user changes in a dirty working tree.

View file

@ -51,12 +51,12 @@ users retain control of the durable files.
- [2026.08] - Published [`@agentscope-ai/reme`](https://www.npmjs.com/package/@agentscope-ai/reme), providing native
ReMe memory integrations for DeepSeek Harness and OpenClaw plus a shared TypeScript HTTP client.
- [2026.08] - Published the [ReMe blog](https://agentscope-ai.github.io/ReMe/?doc=en-reme-blog), an end-to-end introduction to its local-first memory
- [2026.08] - Published the [ReMe blog](https://reme.agentscope.io/en/reme-blog), an end-to-end introduction to its local-first memory
architecture, self-evolving workflows, hybrid search, proactive discovery, and benchmark results.
- [2026.08] - [Experience-driven enhancement method](https://reme.agentscope.io/?doc=toolmemory-en) of agent tool-use execution built
- [2026.08] - [Experience-driven enhancement method](https://reme.agentscope.io/en/benchmarks/toolmemory) of agent tool-use execution built
on ReMe is available on [arXiv:2608.03403](https://arxiv.org/abs/2608.03403).
- [2026.07] - Introduced optional plugins: [Daily Paper](https://reme.agentscope.io/?doc=daily-paper-en) for paper discovery and
analysis, and [Auto Fin](https://reme.agentscope.io/?doc=auto-fin-en) for researching the latest 24 hours of topic-related CLS news
- [2026.07] - Introduced optional plugins: [Daily Paper](https://reme.agentscope.io/en/plugins/daily-paper) for paper discovery and
analysis, and [Auto Fin](https://reme.agentscope.io/en/plugins/auto-fin) for researching the latest 24 hours of topic-related CLS news
with local-memory search and validated historical wikilinks.
- [2026.07] - Our
paper [Remember Me, Refine Me: A Dynamic Procedural Memory Framework for Experience-Driven Agent Evolution](https://aclanthology.org/2026.findings-acl.829/)
@ -171,7 +171,7 @@ Related: [[digest/wiki/memory-as-file.md]]
The `core` installation includes Studio. After starting ReMe, open <http://127.0.0.1:2333/> to browse, edit, and search
the workspace. To add Studio to a base installation, use `pip install "reme-ai[web]"`. See the
[ReMe Studio guide](https://reme.agentscope.io/?doc=studio-en) for source builds, configuration, and development.
[ReMe Studio guide](https://reme.agentscope.io/en/workspace/studio) for source builds, configuration, and development.
## 🤝 Use ReMe with Your Agent
@ -308,12 +308,12 @@ benchmark.
| Benchmark | Setting | Sample size | Agentic score | Focus |
| --------------------------------------------------------------------------- | ------------ | -----------------------: | ------------: | ------------------------------------------------------------------ |
| **[LongMemEval cleaned-s](https://reme.agentscope.io/?doc=longmemeval-en)** | **Overall** | **500 questions** | **89.4%** | Cross-session retrieval, knowledge updates, and temporal reasoning |
| [BEAM](https://reme.agentscope.io/?doc=beam-en) | 100K context | 20 cases / 400 questions | 66.1% | Ten types of long-context memory tasks |
| [BEAM](https://reme.agentscope.io/?doc=beam-en) | 1M context | 35 cases / 700 questions | 65.0% | Ultra-long conversation settings |
| **[LongMemEval cleaned-s](https://reme.agentscope.io/en/benchmarks/longmemeval)** | **Overall** | **500 questions** | **89.4%** | Cross-session retrieval, knowledge updates, and temporal reasoning |
| [BEAM](https://reme.agentscope.io/en/benchmarks/beam) | 100K context | 20 cases / 400 questions | 66.1% | Ten types of long-context memory tasks |
| [BEAM](https://reme.agentscope.io/en/benchmarks/beam) | 1M context | 35 cases / 700 questions | 65.0% | Ultra-long conversation settings |
ReMe also achieved a **0.580 PROC score across five user personas** in the repository's
[π-Bench evaluation](https://reme.agentscope.io/?doc=pibench-en), 2.4% above NanoBot under the same test-model configuration. PROC
[π-Bench evaluation](https://reme.agentscope.io/en/benchmarks/pibench), 2.4% above NanoBot under the same test-model configuration. PROC
measures proactive handling of hidden intent, clarification, cross-session preferences and conventions, task
dependencies, and underspecified requests.
@ -326,8 +326,8 @@ plugins; see the source distributions and their documentation for [Daily Paper](
| Plugin | Capability |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| [Daily Paper](https://reme.agentscope.io/?doc=daily-paper-en) | Discover and rank papers, analyze PDFs with an agent, and generate file-native notes and a five-minute brief. |
| [Auto Fin](https://reme.agentscope.io/?doc=auto-fin-en) | Fetch topic-related CLS news, search ReMe history, and generate wikilink-backed Markdown reports. |
| [Daily Paper](https://reme.agentscope.io/en/plugins/daily-paper) | Discover and rank papers, analyze PDFs with an agent, and generate file-native notes and a five-minute brief. |
| [Auto Fin](https://reme.agentscope.io/en/plugins/auto-fin) | Fetch topic-related CLS news, search ReMe history, and generate wikilink-backed Markdown reports. |
See [Plugin Management](docs/en/plugin_management.md) to install, inspect, validate, enable, and uninstall ReMe plugins.
@ -338,6 +338,8 @@ These guides cover the main user workflows and the runtime contracts implemented
| Guide | What you will learn |
| ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| [Quick Start](docs/en/quick_start.md) | Install ReMe, start the service, and run the first file and memory operations. |
| [Configuration](docs/en/configuration.md) | Configure the workspace, models, Service, Jobs, Components, plugins, and CLI overrides. |
| [Services and Deployment](docs/en/services.md) | Use HTTP, SSE, MCP, and Studio while respecting the default security boundary. |
| [Memory as File](docs/en/memory_as_file.md) | Understand workspace layers, frontmatter, wikilinks, chunks, and the file-as-source-of-truth model. |
| [Auto Memory](docs/en/auto_memory.md) | Preserve source conversations and distill reusable daily memory cards. |
| [Auto Resource](docs/en/auto_resource.md) | Import supported text and image resources as source-linked daily cards. |
@ -347,7 +349,9 @@ These guides cover the main user workflows and the runtime contracts implemented
| [Application Scenarios](docs/en/reme_scene.md) | Follow concrete financial research, coding-memory, and personal knowledge-base examples. |
| [Framework](docs/en/framework.md) | Understand Application, Job, Step, Component, service, configuration, and lifecycle boundaries. |
| [TypeScript integrations](typescript/README.md) | Configure the shared client and native DeepSeek Harness and OpenClaw adapters. |
| [ReMe Blog](https://agentscope-ai.github.io/ReMe/?doc=en-reme-blog) | Read the product story, design rationale, examples, and benchmark summary. |
| [CLI and Job API](docs/en/reference/cli.md) | Learn command syntax and use the generated default Job parameter reference. |
| [Operations and Recovery](docs/en/operations.md) | Diagnose services, maintain indexes, and back up, migrate, or recover a workspace. |
| [ReMe Blog](https://reme.agentscope.io/en/reme-blog) | Read the product story, design rationale, examples, and benchmark summary. |
## 🛠️ Common Commands

View file

@ -49,13 +49,13 @@
- [2026.08] - 发布 [`@agentscope-ai/reme`](https://www.npmjs.com/package/@agentscope-ai/reme),提供统一 TypeScript HTTP
client,以及 DeepSeek Harness 和 OpenClaw 的原生 ReMe 记忆集成。
- [2026.08] - 发布 [ReMe 博客](https://agentscope-ai.github.io/ReMe/?doc=zh-reme-blog),系统介绍本地优先的记忆架构、自进化工作流、混合检索、
- [2026.08] - 发布 [ReMe 博客](https://reme.agentscope.io/zh/reme-blog),系统介绍本地优先的记忆架构、自进化工作流、混合检索、
主动发现与评测结果。
- [2026.08] - 基于 ReMe 的智能体工具使用
[经验驱动增强方法](https://reme.agentscope.io/?doc=toolmemory-zh)已发布,见
[经验驱动增强方法](https://reme.agentscope.io/zh/benchmarks/toolmemory)已发布,见
[arXiv:2608.03403](https://arxiv.org/abs/2608.03403)。
- [2026.07] - 新增可选插件:[每日论文](https://reme.agentscope.io/?doc=daily-paper-zh)用于论文发现与解析,
[Auto Fin](https://reme.agentscope.io/?doc=auto-fin-zh)用于研究最近 24 小时的主题相关财联社新闻,通过本地记忆搜索回顾历史材料并构建
- [2026.07] - 新增可选插件:[每日论文](https://reme.agentscope.io/zh/plugins/daily-paper)用于论文发现与解析,
[Auto Fin](https://reme.agentscope.io/zh/plugins/auto-fin)用于研究最近 24 小时的主题相关财联社新闻,通过本地记忆搜索回顾历史材料并构建
wikilink。
- [2026.07] -
我们的论文 [Remember Me, Refine Me: A Dynamic Procedural Memory Framework for Experience-Driven Agent Evolution](https://aclanthology.org/2026.findings-acl.829/)
@ -170,7 +170,7 @@ ReMe 会把 Agent 记忆保存为可读的 Markdown。
上面的 `core` 安装已包含 Studio。启动 ReMe 后,打开 <http://127.0.0.1:2333/> 即可浏览、编辑和搜索 workspace。
如需为基础安装单独添加 Studio,可使用 `pip install "reme-ai[web]"`。源码构建、配置和开发说明见
[ReMe Studio 指南](https://reme.agentscope.io/?doc=studio-zh)。
[ReMe Studio 指南](https://reme.agentscope.io/zh/workspace/studio)。
## 🤝 将 ReMe 接入你的 Agent
@ -301,11 +301,11 @@ ReMe 通过 Agent 多轮搜索与读取的方式,评测多会话和超长上
| 基准 | 设置 | 样本量 | Agentic 得分 | 主要检验内容 |
| --------------------------------------------------------------------------- | ----------- | ----------------: | -----------: | ------------------------------ |
| **[LongMemEval cleaned-s](https://reme.agentscope.io/?doc=longmemeval-zh)** | **整体** | **500 题** | **89.4%** | 跨会话检索、知识更新与时间推理 |
| [BEAM](https://reme.agentscope.io/?doc=beam-zh) | 100K 上下文 | 20 cases / 400 题 | 66.1% | 十类长上下文记忆任务 |
| [BEAM](https://reme.agentscope.io/?doc=beam-zh) | 1M 上下文 | 35 cases / 700 题 | 65.0% | 超长对话设置 |
| **[LongMemEval cleaned-s](https://reme.agentscope.io/zh/benchmarks/longmemeval)** | **整体** | **500 题** | **89.4%** | 跨会话检索、知识更新与时间推理 |
| [BEAM](https://reme.agentscope.io/zh/benchmarks/beam) | 100K 上下文 | 20 cases / 400 题 | 66.1% | 十类长上下文记忆任务 |
| [BEAM](https://reme.agentscope.io/zh/benchmarks/beam) | 1M 上下文 | 35 cases / 700 题 | 65.0% | 超长对话设置 |
在仓库的 [π-Bench 评测](https://reme.agentscope.io/?doc=pibench-zh)中,ReMe Agent 在 5 种用户角色上的平均 **PROC 得分为 0.580**
在仓库的 [π-Bench 评测](https://reme.agentscope.io/zh/benchmarks/pibench)中,ReMe Agent 在 5 种用户角色上的平均 **PROC 得分为 0.580**
,比相同测试模型配置的 NanoBot 高 2.4%。PROC 用于评估隐藏意图完成、针对性澄清、跨会话偏好和规范复用、跨任务依赖推断以及欠规格请求推进等主动性能力。
## 🧩 扩展与插件
@ -316,8 +316,8 @@ ReMe 通过 Agent 多轮搜索与读取的方式,评测多会话和超长上
| 插件 | 能力 |
| ---------------------------------------------------------- | ------------------------------------------------------------------------------ |
| [每日论文](https://reme.agentscope.io/?doc=daily-paper-zh) | 发现并排序论文,使用 Agent 解读 PDF,生成文件化论文笔记和五分钟简报。 |
| [Auto Fin](https://reme.agentscope.io/?doc=auto-fin-zh) | 拉取主题相关财联社新闻,搜索 ReMe 历史材料并生成带 wikilink 的 Markdown 报告。 |
| [每日论文](https://reme.agentscope.io/zh/plugins/daily-paper) | 发现并排序论文,使用 Agent 解读 PDF,生成文件化论文笔记和五分钟简报。 |
| [Auto Fin](https://reme.agentscope.io/zh/plugins/auto-fin) | 拉取主题相关财联社新闻,搜索 ReMe 历史材料并生成带 wikilink 的 Markdown 报告。 |
安装、查看、校验、启用和卸载 ReMe 插件的方法见[插件管理](docs/zh/plugin_management.md)。
@ -328,6 +328,8 @@ ReMe 通过 Agent 多轮搜索与读取的方式,评测多会话和超长上
| 文档 | 主要内容 |
| ------------------------------------------------------------------------ | ---------------------------------------------------------------------- |
| [快速开始](docs/zh/quick_start.md) | 安装 ReMe、启动服务,并执行首次文件和记忆操作。 |
| [基础配置](docs/zh/configuration.md) | 配置 workspace、模型、Service、Job、Component、插件和命令行覆盖。 |
| [服务与部署](docs/zh/services.md) | 使用 HTTP、SSE、MCP 和 Studio,并理解默认安全边界。 |
| [Memory as File](docs/zh/memory_as_file.md) | 理解 workspace 分层、frontmatter、wikilink、chunk 和文件事实来源模型。 |
| [Auto Memory](docs/zh/auto_memory.md) | 保留过滤后的对话来源记录,并提炼可复用的 daily 记忆卡片。 |
| [Auto Resource](docs/zh/auto_resource.md) | 导入支持的文本与图像资料,转换为可追溯来源的 daily 卡片。 |
@ -337,7 +339,9 @@ ReMe 通过 Agent 多轮搜索与读取的方式,评测多会话和超长上
| [应用场景](docs/zh/reme_scene.md) | 查看金融研究、研发记忆和个人知识库的完整使用示例。 |
| [框架说明](docs/zh/framework.md) | 理解 Application、Job、Step、Component、service、配置和生命周期边界。 |
| [TypeScript 集成](typescript/README_ZH.md) | 配置统一 client,以及 DeepSeek Harness 和 OpenClaw 原生适配器。 |
| [ReMe 博客](https://agentscope-ai.github.io/ReMe/?doc=zh-reme-blog) | 了解完整产品故事、设计动机、使用示例和评测摘要。 |
| [CLI 与 Job API](docs/zh/reference/cli.md) | 查询命令语法,以及由默认配置自动生成的 Job 参数参考。 |
| [运维与恢复](docs/zh/operations.md) | 诊断服务、维护索引,并备份、迁移和恢复 workspace。 |
| [ReMe 博客](https://reme.agentscope.io/zh/reme-blog) | 了解完整产品故事、设计动机、使用示例和评测摘要。 |
## 🛠️ 常用命令

View file

@ -15,8 +15,20 @@ include abstention, contradiction resolution, event ordering, information
extraction, instruction following, knowledge update, multi-session reasoning,
preference following, summarization, and temporal reasoning.
> For the shared setup (dependencies, credentials, log conventions) see the
> [top-level benchmark README](../README.md).
Install ReMe and the BEAM plugin in editable mode from the repository root:
```bash
python -m pip install -e ".[as]"
reme plugins install ./plugins/beam --editable
reme plugins validate beam
```
The runner explicitly enables the installed `beam` plugin and combines its defaults with
ReMe's built-in `benchmark` preset. Editable installation keeps changes under
[`plugins/beam`](../../plugins/beam/README.md) visible without reinstalling the plugin.
Custom application config paths still work through `reme.config` and can use `extends: benchmark`.
This directory continues to own the runner, evaluation settings, dataset and outputs.
Model credentials use the environment variables declared by the shared benchmark configuration.
## 1. Get the Dataset
@ -59,7 +71,7 @@ python benchmark/beam/run.py --eval_only # reuse existing workspac
| `dataset.start_index` / `num_items` | Case pagination (`num_items` `0` = all). |
| `dataset.workspace_root` | Per-case workspace root (`benchmark/beam/workspaces/beam`). |
| `evaluation.num_workers` | `0` = auto, `1` = sequential, `>1` = parallel. |
| `reme.config` | ReMe config used (`beam.yaml`). |
| `reme.config` | ReMe config used (`benchmark`). |
| `output.dir` | Results directory (`benchmark/beam/results`). |
## 5. Outputs

View file

@ -13,7 +13,19 @@ ordering(事件排序)、information extraction(信息抽取)、instruct
knowledge update(知识更新)、multi-session reasoning(多会话推理)、preference following
(偏好遵循)、summarization(摘要)与 temporal reasoning(时间推理)。
> 公共设置(依赖、凭据、日志约定)见[总评测说明](../README_ZH.md)。
在仓库根目录以 editable 模式安装 ReMe 和 BEAM 插件:
```bash
python -m pip install -e ".[as]"
reme plugins install ./plugins/beam --editable
reme plugins validate beam
```
runner 显式启用已安装的 `beam` 插件,并将插件默认配置与 ReMe 内置的 `benchmark` 配置组合。
editable 安装会让 [`plugins/beam`](../../plugins/beam/README_ZH.md) 下的源码修改直接生效,无需重复安装。
本目录继续保留评测参数、数据集及输出。自定义完整应用配置路径仍可通过 `reme.config` 指定,
并可使用 `extends: benchmark`。
模型凭据通过公共 benchmark 配置中声明的环境变量设置。
## 1. 获取数据集
@ -55,7 +67,7 @@ python benchmark/beam/run.py --eval_only # 复用已有工作区
| `dataset.start_index` / `num_items` | case 分页(`num_items` 为 `0` 表示全部)。 |
| `dataset.workspace_root` | case 工作区根目录(`benchmark/beam/workspaces/beam`)。 |
| `evaluation.num_workers` | `0` = 自动,`1` = 串行,`>1` = 并行。 |
| `reme.config` | 使用的 ReMe 配置(`beam.yaml`)。 |
| `reme.config` | 使用的 ReMe 配置(`benchmark`)。 |
| `output.dir` | 结果目录(`benchmark/beam/results`)。 |
## 5. 输出

View file

@ -14,7 +14,7 @@ evaluation:
compress_session: false # true = compress session chunks in search_v2 (query-aware); false = no compression
reme:
config: "beam.yaml" # reme config (in reme/config/)
config: "benchmark" # shared ReMe benchmark preset; runner enables the installed beam plugin
output:
dir: "benchmark/beam/results"

View file

@ -29,7 +29,7 @@ import yaml
from dotenv import load_dotenv
# Load .env from project root
_PROJECT_ROOT = Path(__file__).parent.parent.parent
_PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
load_dotenv(_PROJECT_ROOT / ".env")
# Workspace root — read from config.yaml (dataset.workspace_root)
@ -151,6 +151,22 @@ def load_eval_config(config_path: str | None = None) -> dict:
return yaml.safe_load(raw)
def create_reme_app(config: str = "benchmark", **overrides):
"""Create an app with the installed BEAM plugin explicitly enabled.
Plugin discovery remains environment-based; editable installation keeps local
plugin source changes visible to every multiprocessing worker.
"""
from reme import Application
from reme.config import resolve_app_config
enabled_plugins = list(overrides.pop("plugins", ()) or ())
if "beam" not in enabled_plugins:
enabled_plugins.append("beam")
app_config = resolve_app_config(config=config, plugins=enabled_plugins, **overrides)
return Application(**app_config)
# ---------------------------------------------------------------------------
# BEAM data loading
# ---------------------------------------------------------------------------
@ -319,8 +335,6 @@ async def evaluate_case(eval_config: dict, case_id: str, eval_only: bool = False
Returns:
A results dict with all questions, answers, and judgments.
"""
from reme import Application
from reme.config import resolve_app_config
dataset_cfg = eval_config["dataset"]
chat_size = dataset_cfg["chat_size"]
@ -375,7 +389,7 @@ async def evaluate_case(eval_config: dict, case_id: str, eval_only: bool = False
force_init=True,
)
cfg = resolve_app_config(
app = create_reme_app(
config=eval_config["reme"]["config"],
workspace_dir=workspace_dir,
log_to_console=output_cfg.get("log_to_console", True),
@ -383,7 +397,6 @@ async def evaluate_case(eval_config: dict, case_id: str, eval_only: bool = False
enable_logo=False,
)
app = Application(**cfg)
await app.start()
from reme.utils.evaluation_interface import check_agent_token_usage # noqa: E402

View file

@ -12,8 +12,20 @@ agentic (ReAct) mode, and scores the answer with an LLM-as-judge.
Question types include single-session (user / assistant / preference),
multi-session reasoning, knowledge update, and temporal reasoning.
> For the shared setup (dependencies, credentials, log conventions) see the
> [top-level benchmark README](../README.md).
Install ReMe and the LongMemEval plugin in editable mode from the repository root:
```bash
python -m pip install -e ".[as]"
reme plugins install ./plugins/lme --editable
reme plugins validate lme
```
The runner explicitly enables the installed `lme` plugin and combines its defaults with
ReMe's built-in `benchmark` preset. Editable installation keeps changes under
[`plugins/lme`](../../plugins/lme/README.md) visible without reinstalling the plugin.
Custom application config paths still work through `reme.config` and can use `extends: benchmark`.
This directory continues to own the runner, evaluation settings, dataset and outputs.
Model credentials use the environment variables declared by the shared benchmark configuration.
## 1. Get the Dataset
@ -46,7 +58,8 @@ python benchmark/longmemeval/run.py --eval_only # reuse existing w
1. Load the dataset (ground truth is embedded in the data file).
2. For each item, create an isolated workspace and ingest sessions in chronological order.
3. Trigger `auto_dream` when consecutive sessions cross the configured hour (default 23:00).
3. If a custom application configuration enables `auto_dream`, trigger it when sessions cross the configured hour
(default 23:00). The packaged preset leaves it disabled.
4. Answer each question via agentic (ReAct) mode.
5. Judge the answer (binary yes/no) with the `answer_judge` job and print per-type accuracy.
@ -60,7 +73,7 @@ python benchmark/longmemeval/run.py --eval_only # reuse existing w
| `dataset.workspace_root` | Per-item workspace root (`benchmark/longmemeval/workspaces/longmemeval-s`). |
| `evaluation.num_workers` | `0` = auto (cpu-2), `1` = sequential, `>1` = parallel. |
| `evaluation.filter_future_sessions` | Only ingest sessions with timestamp ≤ `question_date`. |
| `reme.config` | ReMe config used (`lme.yaml`). |
| `reme.config` | ReMe config used (`benchmark`). |
| `reme.dream_trigger_hour` / `dream_scan_days` / `dream_max_units` | Dream triggering behavior. |
| `output.dir` | Results directory (`benchmark/longmemeval/results`). |
@ -93,4 +106,4 @@ agentscope==2.0.4.post1, conda reme env, 32 workers, eval-only (reusing prebuilt
| single-session-preference | 0.633 | 36,802 | 818 | 37,620 | 3.60 |
| single-session-user | 0.986 | 27,433 | 359 | 27,792 | 2.60 |
| temporal-reasoning | 0.902 | 62,674 | 985 | 63,659 | 4.97 |
| **OVERALL** | **0.894** | **43,448** | **876** | **44,324** | **3.69** |
| **OVERALL** | **0.894** | **43,448** | **876** | **44,324** | **3.69** |

View file

@ -8,7 +8,19 @@ LongMemEval 是一个面向**多轮多会话历史的长期记忆能力**的评
题型包括单会话(user / assistant / preference)、多会话推理、知识更新与时间推理等。
> 公共设置(依赖、凭据、日志约定)见[总评测说明](../README_ZH.md)。
在仓库根目录以 editable 模式安装 ReMe 和 LongMemEval 插件:
```bash
python -m pip install -e ".[as]"
reme plugins install ./plugins/lme --editable
reme plugins validate lme
```
runner 显式启用已安装的 `lme` 插件,并将插件默认配置与 ReMe 内置的 `benchmark` 配置组合。
editable 安装会让 [`plugins/lme`](../../plugins/lme/README_ZH.md) 下的源码修改直接生效,无需重复安装。
本目录继续保留评测参数、数据集及输出。自定义完整应用配置路径仍可通过 `reme.config` 指定,
并可使用 `extends: benchmark`。
模型凭据通过公共 benchmark 配置中声明的环境变量设置。
## 1. 获取数据集
@ -41,7 +53,7 @@ python benchmark/longmemeval/run.py --eval_only # 复用已有工
1. 加载数据集(ground truth 已内嵌在数据文件中)。
2. 为每个条目创建独立工作区,按时间顺序摄入会话。
3. 当相邻会话跨越配置的时刻(默认 23:00)时触发 `auto_dream`。
3. 若自定义应用配置启用了 `auto_dream`,在相邻会话跨越配置时刻(默认 23:00)时触发;插件预设保持关闭。
4. 以 agentic(ReAct)模式回答每个问题。
5. 通过 `answer_judge` 任务对答案做二元(yes/no)评判,并输出各类型准确率。
@ -55,7 +67,7 @@ python benchmark/longmemeval/run.py --eval_only # 复用已有工
| `dataset.workspace_root` | 条目工作区根目录(`benchmark/longmemeval/workspaces/longmemeval-s`)。 |
| `evaluation.num_workers` | `0` = 自动(cpu-2),`1` = 串行,`>1` = 并行。 |
| `evaluation.filter_future_sessions` | 仅摄入时间戳 ≤ `question_date` 的会话。 |
| `reme.config` | 使用的 ReMe 配置(`lme.yaml`)。 |
| `reme.config` | 使用的 ReMe 配置(`benchmark`)。 |
| `reme.dream_trigger_hour` / `dream_scan_days` / `dream_max_units` | dream 触发行为。 |
| `output.dir` | 结果目录(`benchmark/longmemeval/results`)。 |

View file

@ -10,7 +10,7 @@ dataset:
workspace_root: "benchmark/longmemeval/workspaces/longmemeval-s" # workspace root for item workspaces
evaluation:
# LLM-as-judge uses the 'judge' as_llm component defined in lme.yaml
# LLM-as-judge uses the 'judge' as_llm component defined in benchmark.yaml
# Model and credentials are configured there (reading from .env)
# Judgment is always binary (yes/no) — defined in lme/llm_judge.yaml
num_workers: 32 # 0 = auto (cpu_count - 2, min 1); 1 = sequential; >1 = parallel
@ -18,7 +18,7 @@ evaluation:
compress_session: false # true = compress session chunks in search_v2 (query-aware); false = no compression
reme:
config: "lme.yaml" # reme config to use (in reme/config/)
config: "benchmark" # shared ReMe benchmark preset; runner enables the installed lme plugin
# Dream trigger: when gap between consecutive sessions crosses this hour (23:00)
dream_trigger_hour: 23
# Dream scan_days for each trigger

View file

@ -28,7 +28,7 @@ import yaml
from dotenv import load_dotenv
# Load .env from project root
_PROJECT_ROOT = Path(__file__).parent.parent.parent
_PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
load_dotenv(_PROJECT_ROOT / ".env")
# Workspace root for evaluation items — read from config.yaml (dataset.workspace_root)
@ -150,6 +150,22 @@ def load_eval_config(config_path: str | None = None) -> dict:
return yaml.safe_load(raw)
def create_reme_app(config: str = "benchmark", **overrides):
"""Create an app with the installed LongMemEval plugin explicitly enabled.
Plugin discovery remains environment-based; editable installation keeps local
plugin source changes visible to every multiprocessing worker.
"""
from reme import Application
from reme.config import resolve_app_config
enabled_plugins = list(overrides.pop("plugins", ()) or ())
if "lme" not in enabled_plugins:
enabled_plugins.append("lme")
app_config = resolve_app_config(config=config, plugins=enabled_plugins, **overrides)
return Application(**app_config)
# ---------------------------------------------------------------------------
# Date utilities
# ---------------------------------------------------------------------------
@ -257,8 +273,6 @@ async def evaluate_item(item: dict, eval_config: dict, item_index: int, eval_onl
using the existing workspace. Useful for re-evaluating different query
configurations without re-ingesting sessions.
"""
from reme import Application
from reme.config import resolve_app_config
from reme.utils.evaluation_interface import track_agent_token_usage, track_job_counts
reme_cfg = eval_config["reme"]
@ -325,7 +339,7 @@ async def evaluate_item(item: dict, eval_config: dict, item_index: int, eval_onl
force_init=True,
)
cfg = resolve_app_config(
app = create_reme_app(
config=reme_cfg["config"],
workspace_dir=workspace_dir,
log_to_console=output_cfg.get("log_to_console", True),
@ -333,7 +347,6 @@ async def evaluate_item(item: dict, eval_config: dict, item_index: int, eval_onl
enable_logo=False,
)
app = Application(**cfg)
await app.start()
try:

View file

@ -289,7 +289,7 @@ removed"). They need the executed tool calls in the trace. The pipeline:
| user_agent / judger models | `config/models/reme.yaml` |
| Agent system prompt | `bridge_reme.py` `build_system_prompt()` |
| Memory retrieval limit/threshold | `--search-limit/--search-min-score` on the bridge command in `run_persona.sh` |
| ReMe internal parameters | **Do not modify ReMe source**; write a dedicated config modeled on `reme/config/beam.yaml` and override via `resolve_app_config(config=...)` (see bridge `_init_reme_app`) |
| ReMe internal parameters | **Do not modify ReMe source**; extend the built-in `benchmark` config and override via `resolve_app_config(config=...)` (see bridge `_init_reme_app`) |
| Turn timeout / tool iteration cap | `config/models/reme.yaml` `run.turn_timeout`, `model.max_tool_iterations` |
## 11. Troubleshooting

View file

@ -255,7 +255,7 @@ grep -h "overall_average_score\|overall_proactiveness" \
| user_agent / judger 模型 | `config/models/reme.yaml` |
| agent system prompt | `bridge_reme.py` `build_system_prompt()` |
| 记忆检索条数/阈值 | `run_persona.sh` bridge 启动命令的 `--search-limit/--search-min-score` |
| ReMe 内部参数 | **不要改 ReMe 源码**;仿照 `reme/config/beam.yaml` 写专有配置,经 `resolve_app_config(config=...)` 覆盖(见 bridge `_init_reme_app`) |
| ReMe 内部参数 | **不要改 ReMe 源码**;继承内置 `benchmark` 配置,并经 `resolve_app_config(config=...)` 覆盖(见 bridge `_init_reme_app`) |
| 轮超时/工具迭代上限 | `config/models/reme.yaml` `run.turn_timeout`、`model.max_tool_iterations` |
## 11. 故障排查

View file

@ -6,7 +6,7 @@
> Code: [https://github.com/WangCan1178/ExpG](https://github.com/WangCan1178/ExpG)
<p align="center">
<img src="gitcha.png" alt="ExpG challenges and overview" width="85%">
<img src="./gitcha.png" alt="ExpG challenges and overview" width="85%">
</p>
### Overview

View file

@ -6,7 +6,7 @@
> 代码:[https://github.com/WangCan1178/ExpG](https://github.com/WangCan1178/ExpG)
<p align="center">
<img src="gitcha.png" alt="ExpG 挑战与概览" width="85%">
<img src="./gitcha.png" alt="ExpG 挑战与概览" width="85%">
</p>
### 简介

344
docs/.vitepress/config.mts Normal file
View file

@ -0,0 +1,344 @@
import fs from "node:fs";
import path from "node:path";
import { execFileSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import { defineConfig, type DefaultTheme } from "vitepress";
import { legacyRoutes } from "./legacy-routes.mjs";
const sourceRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const repositoryRoot = path.resolve(sourceRoot, "../../..");
const repository = "https://github.com/agentscope-ai/ReMe";
const base = process.env.DOCS_BASE || "/";
function readSourceMap(): Record<string, string> {
try {
return JSON.parse(fs.readFileSync(path.join(sourceRoot, ".source-map.json"), "utf8"));
} catch {
return {};
}
}
const sourceMap = readSourceMap();
function collectMarkdown(directory: string, root = directory): string[] {
const files: string[] = [];
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
if (entry.name.startsWith(".") || entry.name === "public" || entry.name === "figure") continue;
const absolute = path.join(directory, entry.name);
if (entry.isDirectory()) files.push(...collectMarkdown(absolute, root));
else if (entry.name.endsWith(".md")) files.push(path.relative(root, absolute).replaceAll(path.sep, "/"));
}
return files.sort();
}
function buildLlmsFiles(outDir: string) {
const pages = collectMarkdown(sourceRoot);
const index = [
"# ReMe Documentation",
"",
"> Local-first, file-native memory for agents.",
"",
...pages.map((relativePath) => {
const source = fs.readFileSync(path.join(sourceRoot, relativePath), "utf8");
const title = source.match(/^#\s+(.+)$/m)?.[1]
|| source.match(/^title:\s*(.+)$/m)?.[1]
|| path.basename(relativePath, ".md");
const route = relativePath.replace(/(?:^|\/)index\.md$/, "").replace(/\.md$/, "");
return `- [${title}](https://reme.agentscope.io/${route})`;
}),
"",
];
fs.writeFileSync(path.join(outDir, "llms.txt"), index.join("\n"), "utf8");
const full = ["# ReMe Documentation", ""];
for (const relativePath of pages) {
const source = fs.readFileSync(path.join(sourceRoot, relativePath), "utf8");
full.push(`<!-- source: ${sourcePathFor(relativePath)} -->`, "", source, "", "---", "");
const pageDir = path.join(outDir, relativePath.replace(/\.md$/, ""));
fs.mkdirSync(pageDir, { recursive: true });
fs.writeFileSync(path.join(pageDir, "llms.txt"), source, "utf8");
}
fs.writeFileSync(path.join(outDir, "llms-full.txt"), full.join("\n"), "utf8");
}
function sourcePathFor(relativePath: string) {
return sourceMap[relativePath] || `docs/${relativePath}`;
}
function sourceLastUpdated(relativePath: string): number | undefined {
const sourcePath = sourcePathFor(relativePath);
try {
const timestamp = execFileSync("git", ["log", "-1", "--format=%ct", "--", sourcePath], {
cwd: repositoryRoot,
encoding: "utf8",
}).trim();
if (timestamp) return Number(timestamp) * 1000;
} catch {
// Fall back to the canonical file timestamp outside a Git checkout.
}
try {
return fs.statSync(path.join(repositoryRoot, sourcePath)).mtimeMs;
} catch {
return undefined;
}
}
const legacyRedirectScript = `(() => {
const routes = ${JSON.stringify(legacyRoutes)};
const id = new URLSearchParams(window.location.search).get("doc");
const target = id && routes[id];
const base = ${JSON.stringify(base)};
if (target) {
const destination = /^https?:/.test(target)
? target
: base.replace(/\\/$/, "") + target;
window.location.replace(destination + window.location.hash);
return;
}
const root = base.endsWith("/") ? base : base + "/";
if (window.location.pathname === root) {
window.location.replace(root + "zh/" + window.location.hash);
}
})();`;
function nav(language: "zh" | "en"): DefaultTheme.NavItem[] {
const zh = language === "zh";
return [
{ text: zh ? "开始使用" : "Get Started", link: `/${language}/quick_start` },
{ text: zh ? "核心概念" : "Concepts", link: `/${language}/memory_as_file` },
{ text: zh ? "指南" : "Guides", link: `/${language}/auto_memory` },
{ text: zh ? "集成" : "Integrations", link: `/${language}/integrations` },
{ text: zh ? "API 参考" : "API Reference", link: `/${language}/reference/cli` },
{ text: zh ? "常见问题" : "FAQ", link: `/${language}/faq` },
];
}
function sidebar(language: "zh" | "en"): DefaultTheme.SidebarItem[] {
const zh = language === "zh";
return [
{
text: zh ? "开始使用" : "Get Started",
collapsed: false,
items: [
{ text: zh ? "项目介绍" : "Introduction", link: `/${language}/` },
{ text: zh ? "快速开始" : "Quick Start", link: `/${language}/quick_start` },
{ text: zh ? "基础配置" : "Configuration", link: `/${language}/configuration` },
{ text: zh ? "服务与部署" : "Services and Deployment", link: `/${language}/services` },
],
},
{
text: zh ? "核心概念" : "Core Concepts",
collapsed: false,
items: [
{ text: zh ? "文件即记忆" : "Memory as File", link: `/${language}/memory_as_file` },
{ text: zh ? "记忆检索" : "Memory Search", link: `/${language}/memory_search` },
{ text: zh ? "自动关联" : "Auto Link", link: `/${language}/auto_link` },
{ text: zh ? "应用场景" : "Application Scenarios", link: `/${language}/reme_scene` },
],
},
{
text: zh ? "记忆工作流" : "Memory Workflows",
collapsed: false,
items: [
{ text: "Auto Memory", link: `/${language}/auto_memory` },
{ text: "Auto Resource", link: `/${language}/auto_resource` },
{ text: "Auto Dream", link: `/${language}/auto_dream` },
{ text: "Proactive", link: `/${language}/proactive` },
],
},
{
text: zh ? "Agent 集成" : "Agent Integrations",
collapsed: true,
items: [
{ text: zh ? "集成总览" : "Overview", link: `/${language}/integrations` },
{ text: "Claude Code", link: `/${language}/integrations/claude-code` },
{ text: "Hermes Agent", link: `/${language}/integrations/hermes` },
{ text: zh ? "TypeScript 客户端" : "TypeScript Client", link: `/${language}/integrations/typescript` },
{ text: "DeepSeek Harness", link: `/${language}/integrations/dsh` },
{ text: "OpenClaw", link: `/${language}/integrations/openclaw` },
],
},
{
text: zh ? "工作区与插件" : "Workspace and Plugins",
collapsed: true,
items: [
{ text: "ReMe Studio", link: `/${language}/workspace/studio` },
{ text: zh ? "插件管理" : "Plugin Management", link: `/${language}/plugin_management` },
{ text: zh ? "插件开发" : "Plugin Development", link: `/${language}/plugin_development` },
{ text: zh ? "每日论文" : "Daily Paper", link: `/${language}/plugins/daily-paper` },
{ text: "Auto Fin", link: `/${language}/plugins/auto-fin` },
{ text: "LME", link: `/${language}/plugins/lme` },
{ text: "BEAM", link: `/${language}/plugins/beam` },
],
},
{
text: zh ? "API 参考" : "API Reference",
collapsed: true,
items: [
{ text: "CLI", link: `/${language}/reference/cli` },
{ text: zh ? "Job API" : "Job API", link: `/${language}/reference/jobs` },
{ text: "HTTP / MCP", link: `/${language}/services#http-api` },
],
},
{
text: zh ? "运维" : "Operations",
collapsed: true,
items: [
{ text: zh ? "诊断、备份与恢复" : "Diagnostics, Backup, and Recovery", link: `/${language}/operations` },
{ text: zh ? "常见问题" : "FAQ", link: `/${language}/faq` },
],
},
{
text: zh ? "开发者" : "Development",
collapsed: true,
items: [
{ text: zh ? "代码框架" : "Framework", link: `/${language}/framework` },
{ text: zh ? "开源与贡献" : "Contributing", link: `/${language}/contributing` },
],
},
{
text: zh ? "评测" : "Benchmarks",
collapsed: true,
items: [
{ text: "BEAM", link: `/${language}/benchmarks/beam` },
{ text: "LongMemEval", link: `/${language}/benchmarks/longmemeval` },
{ text: "π-Bench", link: `/${language}/benchmarks/pibench` },
{ text: "Tool Memory / ExpG", link: `/${language}/benchmarks/toolmemory` },
],
},
];
}
function configureRepositoryLinks(md: any) {
for (const ruleName of ["link_open", "image"] as const) {
const original = md.renderer.rules[ruleName];
md.renderer.rules[ruleName] = (tokens: any[], index: number, options: any, env: any, self: any) => {
const attribute = ruleName === "image" ? "src" : "href";
const token = tokens[index];
const attributeIndex = token.attrIndex(attribute);
const target = attributeIndex >= 0 ? token.attrs[attributeIndex][1] : "";
if (target && !/^(?:[a-z]+:|#|\/)/i.test(target)) {
const cleanTarget = target.split("#")[0].split("?")[0];
const generatedTarget = path.resolve(sourceRoot, path.dirname(env.relativePath), cleanTarget);
if (!fs.existsSync(generatedTarget)) {
const originalPage = sourcePathFor(env.relativePath);
const originalTarget = path.posix.normalize(path.posix.join(path.posix.dirname(originalPage), cleanTarget));
const suffix = target.slice(cleanTarget.length);
const url = ruleName === "image"
? `https://raw.githubusercontent.com/agentscope-ai/ReMe/main/${originalTarget}${suffix}`
: `${repository}/blob/main/${originalTarget}${suffix}`;
token.attrs[attributeIndex][1] = url;
}
}
return original ? original(tokens, index, options, env, self) : self.renderToken(tokens, index, options);
};
}
}
export default defineConfig({
lang: "zh-CN",
title: "ReMe",
description: "Local-first, file-native memory for agents",
base,
cleanUrls: true,
lastUpdated: true,
ignoreDeadLinks: [/^http:\/\/localhost(?::\d+)?(?:\/|$)/],
sitemap: {
hostname: "https://reme.agentscope.io",
transformItems(items) {
const isRoot = (url: string) => url.replace(/^\/+|\/+$/g, "") === "";
return items.filter((item) => !isRoot(item.url)).map((item) => {
const route = item.url.replace(/^\/+/, "");
const relativePath = !route || route.endsWith("/") ? `${route}index.md` : `${route}.md`;
const links = item.links?.filter((link) => !isRoot(link.url));
return { ...item, links, lastmod: sourceLastUpdated(relativePath) };
});
},
},
head: [
["link", { rel: "icon", type: "image/svg+xml", href: `${base}reme-icon.svg` }],
["meta", { name: "theme-color", content: "#087f6a" }],
["script", {}, legacyRedirectScript],
],
markdown: {
config: configureRepositoryLinks,
},
transformPageData(pageData, { siteConfig }) {
const sourcePath = path.join(siteConfig.srcDir, pageData.relativePath);
pageData.frontmatter._sourcePath = sourcePathFor(pageData.relativePath);
pageData.lastUpdated = sourceLastUpdated(pageData.relativePath);
try {
pageData.frontmatter._rawMarkdown = fs.readFileSync(sourcePath, "utf8");
} catch {
pageData.frontmatter._rawMarkdown = "";
}
},
buildEnd(siteConfig) {
buildLlmsFiles(siteConfig.outDir);
},
themeConfig: {
logo: "/reme-icon.svg",
siteTitle: "ReMe",
nav: [
...nav("zh"),
{
text: "语言",
items: [
{ text: "简体中文", link: "/zh/" },
{ text: "English", link: "/en/" },
],
},
],
outline: { label: "页面导航", level: [2, 3] },
search: {
provider: "local",
options: {
locales: {
zh: {
translations: {
button: { buttonText: "搜索文档", buttonAriaLabel: "搜索文档" },
modal: {
noResultsText: "没有找到相关内容",
resetButtonTitle: "清除查询",
footer: { selectText: "选择", navigateText: "切换", closeText: "关闭" },
},
},
},
},
},
},
socialLinks: [{ icon: "github", link: repository }],
footer: {
message: "Released under the Apache-2.0 License.",
copyright: "Copyright ReMe contributors",
},
},
locales: {
zh: {
label: "简体中文",
lang: "zh-CN",
link: "/zh/",
themeConfig: {
nav: nav("zh"),
sidebar: { "/zh/": sidebar("zh") },
outline: { label: "页面导航", level: [2, 3] },
docFooter: { prev: "上一页", next: "下一页" },
darkModeSwitchLabel: "外观",
sidebarMenuLabel: "菜单",
returnToTopLabel: "返回顶部",
langMenuLabel: "切换语言",
},
},
en: {
label: "English",
lang: "en-US",
link: "/en/",
themeConfig: {
nav: nav("en"),
sidebar: { "/en/": sidebar("en") },
outline: { label: "On this page", level: [2, 3] },
docFooter: { prev: "Previous page", next: "Next page" },
},
},
},
});

View file

@ -0,0 +1,47 @@
export const legacyRoutes = {
"readme-zh": "/zh/",
"readme-en": "/en/",
"zh-quick_start": "/zh/quick_start",
"en-quick_start": "/en/quick_start",
"zh-plugin_management": "/zh/plugin_management",
"en-plugin_management": "/en/plugin_management",
"zh-memory_as_file": "/zh/memory_as_file",
"en-memory_as_file": "/en/memory_as_file",
"zh-memory_search": "/zh/memory_search",
"en-memory_search": "/en/memory_search",
"zh-auto_memory": "/zh/auto_memory",
"en-auto_memory": "/en/auto_memory",
"zh-auto_resource": "/zh/auto_resource",
"en-auto_resource": "/en/auto_resource",
"zh-auto_link": "/zh/auto_link",
"en-auto_link": "/en/auto_link",
"zh-auto_dream": "/zh/auto_dream",
"en-auto_dream": "/en/auto_dream",
"zh-proactive": "/zh/proactive",
"en-proactive": "/en/proactive",
"zh-reme_scene": "/zh/reme_scene",
"en-reme_scene": "/en/reme_scene",
"zh-framework": "/zh/framework",
"en-framework": "/en/framework",
"zh-reme-blog": "/zh/reme-blog",
"en-reme-blog": "/en/reme-blog",
"zh-contributing": "/zh/contributing",
"en-contributing": "/en/contributing",
"typescript-zh": "/zh/integrations/typescript",
"typescript-en": "/en/integrations/typescript",
"studio-zh": "/zh/workspace/studio",
"studio-en": "/en/workspace/studio",
"daily-paper-zh": "/zh/plugins/daily-paper",
"daily-paper-en": "/en/plugins/daily-paper",
"auto-fin-zh": "/zh/plugins/auto-fin",
"auto-fin-en": "/en/plugins/auto-fin",
"beam-zh": "/zh/benchmarks/beam",
"beam-en": "/en/benchmarks/beam",
"longmemeval-zh": "/zh/benchmarks/longmemeval",
"longmemeval-en": "/en/benchmarks/longmemeval",
"pibench-zh": "/zh/benchmarks/pibench",
"pibench-en": "/en/benchmarks/pibench",
"toolmemory-zh": "/zh/benchmarks/toolmemory",
"toolmemory-en": "/en/benchmarks/toolmemory",
"agents-guide": "https://github.com/agentscope-ai/ReMe/blob/main/AGENTS.md",
};

View file

@ -0,0 +1,32 @@
<script setup lang="ts">
import { computed, ref } from "vue";
import { useData } from "vitepress";
const { frontmatter, lang } = useData();
const copied = ref(false);
const label = computed(() => {
if (copied.value) return lang.value.startsWith("zh") ? "已复制" : "Copied";
return lang.value.startsWith("zh") ? "复制 Markdown" : "Copy Markdown";
});
async function copyMarkdown() {
const markdown = String(frontmatter.value._rawMarkdown || "");
if (!markdown) return;
await navigator.clipboard.writeText(markdown);
copied.value = true;
window.setTimeout(() => { copied.value = false; }, 1800);
}
</script>
<template>
<div class="copy-markdown-wrap">
<button class="copy-markdown" type="button" :class="{ copied }" @click="copyMarkdown">
<svg v-if="!copied" viewBox="0 0 24 24" aria-hidden="true">
<rect x="9" y="9" width="13" height="13" rx="2" />
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
</svg>
<svg v-else viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12 4 4L19 6" /></svg>
{{ label }}
</button>
</div>
</template>

View file

@ -0,0 +1,15 @@
<script setup lang="ts">
import { computed } from "vue";
import { useData } from "vitepress";
const { frontmatter, lang } = useData();
const sourcePath = computed(() => String(frontmatter.value._sourcePath || ""));
const label = computed(() => lang.value.startsWith("zh") ? "在 GitHub 查看源文件" : "View source on GitHub");
const href = computed(() => `https://github.com/agentscope-ai/ReMe/blob/main/${sourcePath.value}`);
</script>
<template>
<div v-if="sourcePath" class="source-link-wrap">
<a :href="href" target="_blank" rel="noreferrer">{{ label }} ↗</a>
</div>
</template>

View file

@ -0,0 +1,336 @@
:root {
--vp-layout-max-width: 1560px;
--vp-c-brand-1: #087f6a;
--vp-c-brand-2: #086554;
--vp-c-brand-3: #19a98f;
--vp-c-brand-soft: rgba(8, 127, 106, 0.14);
--vp-c-bg: #ffffff;
--vp-c-bg-alt: #f4f7f5;
--vp-c-bg-elv: #ffffff;
--vp-c-bg-soft: #f1f6f3;
--vp-c-text-1: #17221d;
--vp-c-text-2: #526159;
--vp-c-text-3: #718078;
--vp-c-divider: #dce5e0;
--vp-font-family-base: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--vp-font-family-mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
--reme-blue: #3156d9;
--reme-green: #087f6a;
}
.dark {
--vp-c-brand-1: #57dfc3;
--vp-c-brand-2: #35c6a9;
--vp-c-brand-3: #087f6a;
--vp-c-brand-soft: rgba(87, 223, 195, 0.14);
--vp-c-bg: #0d1512;
--vp-c-bg-alt: #09100d;
--vp-c-bg-elv: #14201b;
--vp-c-bg-soft: #17251f;
--vp-c-text-1: #edf7f3;
--vp-c-text-2: #bacbc4;
--vp-c-text-3: #91a49c;
--vp-c-divider: #283a33;
}
body {
background:
radial-gradient(circle at 8% 8%, rgba(25, 201, 176, 0.055), transparent 28rem),
var(--vp-c-bg);
}
.VPNav {
border-bottom: 1px solid color-mix(in srgb, var(--vp-c-divider) 84%, transparent);
background: color-mix(in srgb, var(--vp-c-bg) 88%, transparent);
backdrop-filter: blur(18px) saturate(140%);
}
.VPNavBarTitle .logo {
width: 30px;
height: 30px;
}
.VPNavBarTitle .title {
font-weight: 780;
letter-spacing: -0.02em;
}
.VPNavBarSearch .DocSearch-Button,
.VPNavBarSearch button {
min-width: 190px;
border: 1px solid var(--vp-c-divider);
border-radius: 10px;
background: var(--vp-c-bg-alt);
}
.VPSidebar {
border-right: 1px solid var(--vp-c-divider);
background: color-mix(in srgb, var(--vp-c-bg-alt) 82%, transparent);
}
.VPSidebarItem .text {
font-size: 14px;
}
.VPSidebarItem.level-0 > .item > .text {
color: var(--vp-c-text-1);
font-weight: 750;
}
.VPSidebarItem.is-active > .item .link > .text {
color: var(--vp-c-brand-1);
}
.VPDocAsideOutline {
border-left-color: var(--vp-c-divider);
}
.VPDoc .container > .content {
min-width: 0;
}
.VPDoc .content-container {
max-width: 900px !important;
}
.vp-doc {
color: var(--vp-c-text-1);
font-size: 16px;
line-height: 1.78;
}
.vp-doc h1 {
margin-bottom: 28px;
font-size: clamp(36px, 5vw, 52px);
line-height: 1.08;
letter-spacing: -0.045em;
}
.vp-doc h2 {
margin-top: 52px;
border-top-color: var(--vp-c-divider);
font-size: 27px;
letter-spacing: -0.025em;
}
.vp-doc h3 {
margin-top: 34px;
font-size: 20px;
}
.vp-doc :not(pre) > code {
border-radius: 5px;
color: color-mix(in srgb, var(--vp-c-brand-1) 82%, var(--vp-c-text-1));
}
.vp-doc div[class*="language-"] {
border: 1px solid var(--vp-c-divider);
border-radius: 12px;
box-shadow: inset 3px 0 0 color-mix(in srgb, var(--vp-c-brand-1) 62%, transparent);
}
.copy-markdown-wrap {
display: flex;
justify-content: flex-end;
margin-bottom: 18px;
}
.copy-markdown {
display: inline-flex;
align-items: center;
gap: 7px;
min-height: 34px;
padding: 6px 12px;
border: 1px solid var(--vp-c-divider);
border-radius: 9px;
color: var(--vp-c-text-2);
background: var(--vp-c-bg-soft);
cursor: pointer;
font-size: 13px;
font-weight: 650;
}
.copy-markdown:hover,
.copy-markdown.copied {
border-color: var(--vp-c-brand-1);
color: var(--vp-c-brand-1);
}
.copy-markdown svg {
width: 15px;
height: 15px;
fill: none;
stroke: currentColor;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2;
}
.source-link-wrap {
margin-top: 48px;
padding-top: 20px;
border-top: 1px solid var(--vp-c-divider);
font-size: 13px;
}
.source-link-wrap a {
color: var(--vp-c-text-3);
text-decoration: none;
}
.source-link-wrap a:hover {
color: var(--vp-c-brand-1);
}
.VPHome {
background:
radial-gradient(circle at 14% 14%, rgba(25, 201, 176, 0.14), transparent 30rem),
radial-gradient(circle at 86% 10%, rgba(49, 86, 217, 0.11), transparent 28rem);
}
.VPHero .name {
background: linear-gradient(120deg, var(--reme-green), var(--reme-blue));
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.VPHero .name,
.VPHero .text {
letter-spacing: -0.05em;
}
.VPHero .image-bg {
width: min(88%, 420px);
height: 220px;
border-radius: 42%;
background: linear-gradient(125deg, rgba(25, 201, 176, 0.34), rgba(49, 86, 217, 0.25));
filter: blur(52px);
}
.VPHero .image-container {
isolation: isolate;
perspective: 900px;
}
.VPHero .image-container::before,
.VPHero .image-container::after {
position: absolute;
content: "";
pointer-events: none;
}
.VPHero .image-container::before {
z-index: 0;
top: 50%;
left: 50%;
width: min(88%, 430px);
height: 210px;
border: 1px solid color-mix(in srgb, var(--vp-c-bg-elv) 64%, var(--reme-blue));
border-radius: 30px;
background:
linear-gradient(135deg, color-mix(in srgb, var(--vp-c-bg-elv) 92%, transparent), color-mix(in srgb, var(--vp-c-bg-soft) 76%, transparent)),
radial-gradient(circle at 15% 15%, rgba(25, 201, 176, 0.14), transparent 42%);
box-shadow:
0 30px 70px rgba(19, 70, 91, 0.16),
inset 0 1px 0 color-mix(in srgb, white 72%, transparent);
backdrop-filter: blur(22px) saturate(135%);
transform: translate(-50%, -50%) rotate(-1.5deg);
}
.VPHero .image-container::after {
z-index: -1;
top: 50%;
left: 50%;
width: min(78%, 380px);
height: 210px;
border: 1px solid rgba(49, 86, 217, 0.17);
border-radius: 30px;
background: linear-gradient(135deg, rgba(25, 201, 176, 0.1), rgba(49, 86, 217, 0.11));
transform: translate(-46%, -48%) rotate(7deg);
}
.VPHero .image-src {
z-index: 1;
width: min(76%, 380px);
max-width: 380px !important;
max-height: 150px !important;
object-fit: contain;
filter: drop-shadow(0 12px 20px rgba(18, 78, 105, 0.16));
}
.VPHomeFeatures .item:nth-child(1) { --feature-accent: #18b99e; }
.VPHomeFeatures .item:nth-child(2) { --feature-accent: #25a8dc; }
.VPHomeFeatures .item:nth-child(3) { --feature-accent: #6575e8; }
.VPHomeFeatures .item:nth-child(4) { --feature-accent: #e69a42; }
.VPHomeFeatures .VPFeature {
border-color: color-mix(in srgb, var(--feature-accent) 28%, var(--vp-c-divider));
border-radius: 16px;
background:
radial-gradient(circle at 10% 4%, color-mix(in srgb, var(--feature-accent) 16%, transparent), transparent 52%),
linear-gradient(150deg, var(--vp-c-bg-elv), color-mix(in srgb, var(--feature-accent) 7%, var(--vp-c-bg-soft)));
box-shadow:
inset 0 1px 0 color-mix(in srgb, white 76%, transparent),
0 8px 24px color-mix(in srgb, var(--feature-accent) 7%, transparent);
transition: transform 160ms ease, border-color 160ms ease, box-shadow 160ms ease;
}
.VPHomeFeatures .VPFeature .box {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
grid-template-rows: auto 1fr;
column-gap: 12px;
align-items: center;
}
.VPHomeFeatures .VPFeature .icon {
grid-column: 1;
grid-row: 1;
width: auto;
height: auto;
margin: 0;
border: 0;
background: transparent;
box-shadow: none;
font-size: 25px;
}
.VPHomeFeatures .VPFeature .title {
grid-column: 2;
grid-row: 1;
color: color-mix(in srgb, var(--feature-accent) 22%, var(--vp-c-text-1));
}
.VPHomeFeatures .VPFeature .details {
grid-column: 1 / -1;
grid-row: 2;
align-self: start;
padding-top: 18px;
}
.VPHomeFeatures .VPFeature .link-text {
grid-column: 1 / -1;
}
.VPHomeFeatures .VPFeature:hover {
transform: translateY(-3px);
border-color: color-mix(in srgb, var(--feature-accent) 48%, var(--vp-c-divider));
box-shadow: 0 18px 38px color-mix(in srgb, var(--feature-accent) 15%, transparent);
}
.dark .VPHomeFeatures .VPFeature {
background:
radial-gradient(circle at 10% 4%, color-mix(in srgb, var(--feature-accent) 18%, transparent), transparent 54%),
linear-gradient(150deg, var(--vp-c-bg-elv), color-mix(in srgb, var(--feature-accent) 8%, var(--vp-c-bg-soft)));
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
@media (max-width: 768px) {
.vp-doc h1 { font-size: 34px; }
.vp-doc h2 { margin-top: 44px; font-size: 24px; }
.copy-markdown-wrap { margin-top: -8px; }
.VPHero .image-container::before,
.VPHero .image-container::after { height: 176px; border-radius: 24px; }
.VPHero .image-src { width: 72%; max-height: 120px !important; }
}

View file

@ -0,0 +1,15 @@
import { h } from "vue";
import DefaultTheme from "vitepress/theme";
import CopyMarkdownButton from "./CopyMarkdownButton.vue";
import SourceLink from "./SourceLink.vue";
import "./custom.css";
export default {
extends: DefaultTheme,
Layout() {
return h(DefaultTheme.Layout, null, {
"doc-before": () => h(CopyMarkdownButton),
"doc-after": () => h(SourceLink),
});
},
};

157
docs/en/configuration.md Normal file
View file

@ -0,0 +1,157 @@
---
title: Configuration
description: ReMe configuration files, environment expansion, command-line overrides, and core components.
---
# Configuration
ReMe uses YAML or JSON to describe its Service, Jobs, and Components. The built-in default is `reme/config/default.yaml`. Select another configuration at startup and apply command-line overrides when needed.
## Precedence
Configuration is merged in this order, with later values winning:
1. `application_defaults` from enabled plugins.
2. The selected file; `default` is used when none is specified.
3. CLI dot-notation overrides.
```bash
reme start
reme start config=demo
reme start config=/absolute/path/to/app.yaml
reme start service.port=8181 workspace_dir=/data/reme
```
`config` accepts a built-in name or a `.yaml`, `.yml`, or `.json` file. Overrides are deep-merged, so changing `service.port` preserves sibling service settings.
## CLI values
Arguments use `key=value`; leading `-` or `--` is accepted:
```bash
reme start --service.port=8181 --service.web_enabled=false
```
Values support null, booleans, numbers, JSON arrays and objects, quoted JSON strings, and plain strings. Numeric-looking values with leading zeroes, such as `007`, remain strings. Quote values such as `"true"` in JSON when they must remain strings.
## Environment variables
Configuration recursively expands:
```yaml
api_key: ${LLM_API_KEY}
base_url: ${LLM_BASE_URL:-https://example.com/v1}
```
`${VAR}` fails when undefined; `${VAR:-default}` uses its fallback. ReMe also searches for `.env` from the command's working directory through at most five parents.
Keep secrets in `.env` or the process environment, never in committed configuration.
## Application fields
| Field | Default | Purpose |
|---|---|---|
| `app_name` | `ReMe` | Display name |
| `workspace_dir` | `.reme` | User-owned workspace root, normalized to an absolute path |
| `metadata_dir` | `metadata` | Rebuildable indexes, graphs, and catalogs |
| `session_dir` | `session` | Agent sessions; standard transcripts use `session/dialog` |
| `mem_session_dir` | `mem_session` | Agent-wrapper sessions and configuration |
| `resource_dir` | `resource` | External resources |
| `daily_dir` | `daily` | Daily memory |
| `digest_dir` | `digest` | Consolidated long-term memory |
| `timezone` | `Asia/Shanghai` | IANA timezone used for dates and cron jobs |
| `language` | empty | Default language for LLM interactions |
| `plugins` | `[]` | Installed plugins enabled for this Application |
| `service` | HTTP | Service configuration |
| `jobs` | default Jobs | Job configurations by name |
| `components` | defaults | Components grouped by type and name |
`session_dir` must remain workspace-relative.
## LLM
The default LLM uses an OpenAI-compatible interface:
```yaml
components:
as_llm:
default:
backend: openai
model: qwen3.7-plus
context_size: 200000
credential:
api_key: ${LLM_API_KEY:-}
base_url: ${LLM_BASE_URL:-}
```
Built-in registrations include `openai`, `anthropic`, `dashscope`, `deepseek`, `gemini`, `moonshot`, `ollama`, and `xai`. Their detailed model fields follow the corresponding AgentScope wrappers.
File operations, BM25 search, and wikilink traversal do not require an LLM. Evolution workflows such as `auto_memory`, `auto_resource`, and `auto_dream` do.
## Embeddings
Vector retrieval is disabled by default. Credentials alone do not enable it: configure `as_embedding`, `embedding_store`, and connect the store to `file_store`.
```yaml
components:
as_embedding:
default:
backend: openai
model: text-embedding-v4
dimensions: 1024
credential:
api_key: ${EMBEDDING_API_KEY}
base_url: ${EMBEDDING_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1}
embedding_store:
default:
backend: local
as_embedding: default
file_store:
default:
backend: local
embedding_store: default
keyword_index: default
file_graph: default
```
Rebuild the embedding index after changing the model or dimensions.
## Service and Jobs
Minimal HTTP configuration:
```yaml
service:
backend: http
host: 127.0.0.1
port: 2333
web_enabled: true
mcp_enabled: true
mcp_path: /mcp
```
A Job declares a backend, parameter schema, and ordered Steps:
```yaml
jobs:
example:
backend: base
description: Example job
parameters:
type: object
properties:
text: { type: string }
required: [text]
steps:
- backend: example_step
```
Set `enable_serve: false` to keep a Job internal. Background and cron Jobs are never service-exposed.
## Inspect the effective configuration
```bash
reme app_config
```
The result is the merged, validated configuration with secrets redacted. Use it when diagnosing plugin or override precedence. The authoritative contracts remain `reme/schema/application_config.py` and `reme/config/default.yaml`.

View file

@ -209,6 +209,13 @@ Documentation lives under:
docs/
```
User guides should have matching `docs/zh/` and `docs/en/` versions and appear in the corresponding navigation in
`docs/.vitepress/config.mts`. The ReMe Studio, TypeScript, plugin, and benchmark READMEs remain canonical in their own
directories; `github-pages/scripts/generate-content.mjs` mirrors them during builds. Never edit `.generated/` or `dist/`.
The Job API reference is generated from `reme/config/default.yaml`. Update that YAML and its tests when a default Job
contract changes rather than editing generated pages.
Documentation should:
- Use clear titles that directly identify a capability or flow.
@ -216,6 +223,15 @@ Documentation should:
- Use real repository paths such as `reme/config/default.yaml`, `reme/steps/`, and `tests/unit/`.
- Describe default behavior according to the current code, `pyproject.toml`, and default configuration.
Validate the documentation site with:
```bash
cd github-pages
npm ci
npm test
npm run build
```
---
## Getting Help

71
docs/en/faq.md Normal file
View file

@ -0,0 +1,71 @@
---
title: Frequently Asked Questions
description: Quick answers for ReMe installation, services, models, retrieval, files, and plugins.
---
# Frequently Asked Questions
## Do basic file operations require a model API key?
No. `write`, `read`, `list`, `stat`, BM25 search, and wikilink traversal work without model credentials. `auto_memory`, `auto_resource`, and `auto_dream` require an LLM.
## Why is search still BM25-only after setting an embedding key?
Embeddings are disabled by default. Configure `as_embedding` and `embedding_store`, then connect `file_store.default.embedding_store` to that component. See [Configuration](./configuration.md#embeddings).
## Why did `reme reindex` not discover a new file?
`reindex` rebuilds indexes from current `file_chunks`; it does not scan the workspace. Check `index_update_loop`, the watched directory and extension, and `health_check`.
## How do I use another workspace?
```bash
reme start workspace_dir=/absolute/path/to/memory
```
Ordinary CLI calls discover the running service, so they do not need the workspace argument again.
## What if port 2333 is occupied?
Do not stop an unknown listener. Select another port:
```bash
reme start service.port=8181
```
Then confirm it with `reme find_reme`.
## Why is an installed plugin missing its Jobs?
Installation only makes the distribution discoverable in the active Python environment. Enable it for the Application:
```bash
reme start plugins='["auto-fin"]'
```
Restart a running service after changing package or enablement state.
## May I edit workspace Markdown directly?
Yes. Files are the source of truth and watchers ingest changes. Keep frontmatter valid, use complete workspace-relative wikilinks, and avoid unconditional concurrent saves.
## May I expose ReMe publicly?
Not with the default configuration alone. Jobs can write and delete, HTTP CORS is permissive, and there is no general authentication layer. Use a controlled network or authenticated TLS reverse proxy and restrict `service.jobs`.
## How should I back up and migrate memory?
Stop writes and back up the complete workspace. `session/`, `resource/`, `daily/`, and `digest/` are the key sources; `metadata/` can be backed up or rebuilt. See [Diagnostics, Backup, and Recovery](./operations.md).
## Why is Studio unavailable?
The base `reme-ai` package has no frontend assets. Install `reme-ai[web]` or `reme-ai[core]`, or set `service.web_static_dir`. Missing Studio assets do not disable the Job API.
## Which capabilities does the running service expose?
```bash
reme help
reme app_config
```
Static documentation describes defaults; plugins and custom configuration may change the active service.

36
docs/en/index.md Normal file
View file

@ -0,0 +1,36 @@
---
layout: home
title: ReMe Documentation
titleTemplate: false
hero:
name: ReMe
text: Memory that works for agents
tagline: Files remain yours. ReMe turns conversations and resources into readable, editable, searchable, interconnected local files.
image:
src: /reme-logo.svg
alt: ReMe logo
actions:
- theme: brand
text: Quick Start
link: /en/quick_start
- theme: alt
text: Configuration
link: /en/configuration
features:
- icon: 📁
title: Memory as files
details: Workspace files are the durable source of truth; indexes, graphs, and caches remain rebuildable.
link: /en/memory_as_file
- icon: 🧠
title: Memory workflows
details: Turn conversations and resources into daily notes, then consolidate them into connected long-term digests.
link: /en/auto_memory
- icon: 🔎
title: Search and graph
details: Combine keyword and optional vector retrieval with wikilink graph expansion.
link: /en/memory_search
- icon: 🔌
title: Agent integrations
details: Connect existing agents through the CLI, HTTP, MCP, and host adapters.
link: /en/integrations
---

63
docs/en/integrations.md Normal file
View file

@ -0,0 +1,63 @@
---
title: Agent Integrations
description: Connect ReMe to agents through the CLI, HTTP, MCP, Skills, and host adapters.
---
# Agent Integrations
ReMe keeps memory in an independent service and a user-owned workspace. Multiple agents can call the same memory system without binding storage to one model or host.
## Choose an interface
| Scenario | Recommended interface |
|---|---|
| Local script or hook | ReMe CLI |
| Application backend | HTTP Client |
| Tool-protocol host | MCP |
| TypeScript agent | `@agentscope-ai/reme` |
| Claude Code | MCP + Skill + Stop Hook |
| Hermes Agent | Memory provider adapter |
| Codex or another coding agent | `reme_memory` Skill or MCP |
## General memory loop
1. Before answering, call `search` for relevant memory.
2. Use `read` on high-value results and `traverse` when relationships matter.
3. Retain workspace-relative source paths in the answer.
4. At session end, pass source messages to `auto_memory`.
5. Let background or scheduled workflows consolidate daily notes into digest memory.
An empty search result must remain empty; do not present model inference as recalled history.
## MCP
The default HTTP service exposes streamable HTTP MCP at `http://127.0.0.1:2333/mcp`. Common tools include `search`, `read`, `traverse`, `list`, `auto_memory`, and `proactive`.
Use `service.jobs` to expose a read-only subset or keep write tools in a separate configuration.
## CLI and Skill
`skills/reme_memory/SKILL.md` defines a general workflow for agents that can run local commands: installation checks, service discovery, retrieval, reading, and persistence boundaries.
It deliberately avoids silently modifying Python environments, stopping unknown processes on port conflicts, writing recalled tool output back as conversation source, or persisting credentials.
## TypeScript, OpenClaw, and DeepSeek Harness
The [`@agentscope-ai/reme` TypeScript package](./integrations/typescript.md) provides the shared HTTP client and host adapters. See the dedicated guides for [DeepSeek Harness](./integrations/dsh.md) and [OpenClaw](./integrations/openclaw.md).
## Claude Code
`integrations/claude_code/` provides streamable HTTP MCP configuration, a `reme-memory` Skill, and a Stop hook that calls `auto_memory_cc`. Follow that directory's README for installation.
## Hermes Agent
`integrations/hermes_agent/` provides a memory provider that recalls context before model calls and asynchronously invokes `auto_memory` after each turn.
## Production guidance
- choose a stable absolute `workspace_dir`;
- reuse a service discovered by `reme find_reme`;
- treat `reme help` as the active Job contract;
- apply timeouts and failure logging to writes;
- do not block the host's core response path when memory is temporarily unavailable;
- use authentication, TLS, and a minimal Job allowlist for remote access.

102
docs/en/operations.md Normal file
View file

@ -0,0 +1,102 @@
---
title: Diagnostics, Backup, and Recovery
description: ReMe health checks, logs, index maintenance, workspace backup, migration, and recovery.
---
# Diagnostics, Backup, and Recovery
ReMe recovery protects user-owned workspace files and rebuilds catalogs, indexes, and graphs from those sources. Never delete or rewrite user memory merely to repair derived state.
## Quick diagnosis
Run these in order:
```bash
reme find_reme
reme version
reme health_check
reme status
reme app_config
```
- `find_reme` confirms the actual host, port, and PID;
- `version` verifies that the CLI reaches the service;
- `health_check` reports component health;
- `status` estimates stateful component memory and process RSS;
- `app_config` returns the effective configuration with secrets redacted.
## Logs and common symptoms
`log_to_console` and `log_to_file` control logging. For startup failures, inspect the first exception rather than later client connection errors.
| Symptom | Check first |
|---|---|
| CLI cannot find ReMe | `reme find_reme`, process state, startup directory, and port |
| Automatic memory fails | LLM backend, model, API key, and base URL |
| Search is BM25-only | Whether an embedding store is connected to `file_store` |
| New files are absent | Directory, extension, watcher, and `health_check` |
| Studio fails but API works | Installed web extra, static path, and browser console |
| Installed plugin is unavailable | Python interpreter, `plugins` configuration, and service restart |
## Index maintenance
```bash
reme reindex scope=all
reme reindex scope=bm25
reme reindex scope=embedding
```
`reindex` rebuilds BM25 and/or embedding indexes from the current `file_chunks`. It does not scan the workspace, rechunk files, or rebuild the wikilink graph. Diagnose the watcher first when ingestion is the problem.
Rebuild a daily index page separately:
```bash
reme daily_reindex date=2026-09-04
```
## Backup
Stop writes or stop the service, then back up the complete workspace. The most important sources are:
- `session/` for conversation sources;
- `resource/` for external resources;
- `daily/` for daily memory;
- `digest/` for consolidated memory.
`metadata/` contains indexes, graphs, and catalogs. Backing it up accelerates restoration, but it is not the sole source of truth.
Use an explicit, stable absolute `workspace_dir` for durable deployments rather than relying on an incidental `.reme/` under the current directory.
## Migrate a workspace
1. Stop the old service to prevent writes during the copy.
2. Copy the complete workspace while preserving timestamps.
3. Start with the new absolute path:
```bash
reme start workspace_dir=/new/location/reme-memory
```
4. Run `health_check`, `status`, and a representative `search`.
5. Rebuild embeddings if their model or dimensions changed.
Do not push a workspace containing private conversations to a public repository.
## Recover derived state
Do not remove anything until a backup exists. Then:
1. preserve `session/`, `resource/`, `daily/`, and `digest/`;
2. record the effective configuration and component backends;
3. verify that the failure is limited to `metadata/`;
4. move suspect derived state to an isolated backup location;
5. restart with the same configuration and let watchers rebuild;
6. validate search, graph traversal, and daily indexes.
The internal layout of metadata files is not a public automation contract.
## Concurrent editing
When Studio or an editor saves a complete file, pass the mtime from `stat` as `save.expected_mtime`. A save then fails if another actor changed the file after it was opened, avoiding silent overwrites.
File Jobs enforce workspace containment and per-path locking. Do not bypass them to write arbitrary absolute paths.

View file

@ -0,0 +1,101 @@
---
title: Plugin Development
description: Create, register, configure, test, and publish a ReMe plugin.
---
# Plugin Development
A ReMe plugin is a regular Python distribution exposed through the `reme.plugins` entry-point group. Its package-level `plugin.yaml` can register Step and Component backends and provide default Application configuration.
## Minimal structure
```text
my-plugin/
├── pyproject.toml
└── src/my_plugin/
├── __init__.py
├── plugin.yaml
└── steps.py
```
`pyproject.toml`:
```toml
[project.entry-points."reme.plugins"]
my-plugin = "my_plugin"
```
`plugin.yaml`:
```yaml
name: my-plugin
backends:
my_step: my_plugin.steps:MyStep
application_defaults:
jobs:
my_action:
backend: base
description: Run my plugin action
parameters:
type: object
properties:
text: { type: string }
required: [text]
steps:
- backend: my_step
```
## Implement a Step
```python
from reme.components.component_registry import R
from reme.steps.base_step import BaseStep
@R.register("my_step")
class MyStep(BaseStep):
async def execute(self):
self.context.response.answer = self.context.data["text"]
```
Step instances belong to one Job invocation. Put shared in-memory state under a narrow `app_context.metadata` key. Promote state that needs lifecycle, locking, or persistence to a Component or workspace file.
## Configuration merge
`application_defaults` is a partial `ApplicationConfig`:
```text
plugin defaults < selected/default config < CLI overrides
```
Plugins must not rewrite user configuration. Their backends enter an Application-local registry only when the plugin appears in that Application's `plugins` list.
## Local validation
```bash
reme plugins validate ./path/to/my-plugin
reme plugins install ./path/to/my-plugin --editable
reme plugins list
reme plugins show my-plugin
reme start plugins='["my-plugin"]'
reme my_action text=hello
```
Validation imports plugin code, so run it only for trusted sources.
## Test boundaries
- create workspaces with `tmp_path`;
- mock network, model, and subprocess boundaries;
- verify disabled plugins do not mutate the built-in registry;
- verify plugin defaults and explicit configuration precedence;
- keep tasks, clients, and executors under Component lifecycle;
- never delete or rewrite user source files to repair derived state.
The repository's Daily Paper, Auto Fin, LME, and BEAM plugins are complete examples.
## Compatibility
Legacy Python Plugin descriptors and the `reme.configs` entry point remain supported during migration, but new plugins should use `plugin.yaml`. Enablement always belongs to an Application rather than a process-global switch.
See [Plugin Management](./plugin_management.md) for installation, upgrades, and removal.

View file

@ -176,6 +176,16 @@ When the application uses an MCP service, service-enabled plugin Jobs appear as
Custom application configs must provide the plugin's runtime dependencies, including an `agent_wrapper.default` and
the `search` and `read` Jobs used by Auto Fin.
## Benchmark application presets
The [LME](../../plugins/lme/README.md) and [BEAM](../../plugins/beam/README.md) plugins
register their backends and plugin-owned Jobs in `plugin.yaml`. ReMe's built-in `benchmark`
preset provides the shared core Jobs and components without inheriting `default`, so default
background and cron jobs are not included. Install the selected benchmark plugin, then use
`config=benchmark` together with `plugins=["lme"]` or `plugins=["beam"]`. The repository's
benchmark runners enable the corresponding installed plugin automatically; editable installation
keeps local plugin changes visible. Dataset runners remain under `benchmark/`.
## Uninstall a plugin
Use the plugin entry-point name, not necessarily the distribution name:

View file

@ -1,5 +1,12 @@
---
title: Quick Start
description: Install and start ReMe, then complete a first file, retrieval, and automatic-memory workflow.
---
# Quick Start
This page gets one working loop running. See [Configuration](./configuration.md) for the full configuration contract and [Services and Deployment](./services.md) for HTTP or MCP integration.
## Installation
ReMe requires Python 3.11+.
@ -237,3 +244,5 @@ You can also specify a YAML or JSON configuration file:
```bash
reme start config=/path/to/custom.yaml
```
Continue with the [CLI Reference](./reference/cli.md), [Job API Reference](./reference/jobs.md), or [Diagnostics, Backup, and Recovery](./operations.md).

87
docs/en/reference/cli.md Normal file
View file

@ -0,0 +1,87 @@
---
title: CLI Reference
description: ReMe command syntax, service invocation, configuration overrides, and plugin commands.
---
# CLI Reference
The basic syntax is:
```text
reme ACTION key=value ...
```
## Start an Application
```bash
reme start
reme start config=demo
reme start workspace_dir=/data/reme service.port=8181
reme start job=search query="keywords" limit=5
```
`start job=<name>` runs one Job through a one-shot service; plain `start` runs the configured Service.
## Call Jobs
Once a service is running, each action name is a Job name:
```bash
reme help
reme health_check
reme search query="project decision" limit=10
reme read path=digest/wiki/project.md start_line=1 end_line=80
```
Use JSON for structured values:
```bash
reme auto_memory \
session_id=example \
messages='[{"role":"user","content":"Remember this preference"}]'
```
Client-selection arguments—`backend`, `transport`, `host`, `port`, `timeout`, `command`, `args`, and `show_metadata`—configure the client and never leak into the Job payload.
## Configuration overrides
```bash
reme start \
config=/path/to/custom.yaml \
service.port=8181 \
service.web_enabled=false \
plugins='["auto-fin"]'
```
Leading `-` or `--` is optional. Use dots for nested keys and JSON for arrays and objects.
## Service discovery
```bash
reme find_reme
```
This reports a discovered service but never starts or replaces a process.
## Plugin commands
Package management runs locally rather than through HTTP or MCP:
```bash
reme plugins list
reme plugins show auto-fin
reme plugins validate auto-fin
reme plugins install reme-auto-fin
reme plugins uninstall auto-fin
```
See [Plugin Management](../plugin_management.md) for the complete workflow.
## Discover active capabilities
The [Job API Reference](./jobs.md) describes the default configuration. Plugins and custom YAML may change the running service, so automation should prefer:
```bash
reme help
reme app_config
```

115
docs/en/services.md Normal file
View file

@ -0,0 +1,115 @@
---
title: Services and Deployment
description: Run ReMe through HTTP, SSE, MCP, the CLI, and ReMe Studio while respecting its local security boundary.
---
# Services and Deployment
ReMe can run as a local HTTP service, a standalone MCP server, or a one-shot CLI Job. The default starts HTTP on `127.0.0.1:2333` and serves JSON, SSE, streamable HTTP MCP, and optional ReMe Studio from one process.
## HTTP API
```bash
reme start
reme start service.host=127.0.0.1 service.port=8181
```
Regular Jobs become `POST /<job-name>`:
```bash
curl -s http://127.0.0.1:2333/search \
-H 'Content-Type: application/json' \
-d '{"query":"user preferences","limit":5}'
```
Job arguments live at the request body's top level. A regular response follows the `Response` schema:
```json
{"success": true, "answer": "...", "metadata": {}}
```
Unhandled Step failures become unsuccessful responses.
## Streaming and SSE
Jobs with `backend: stream` also use `POST /<job-name>`, returning `text/event-stream`. Error paths emit an error chunk and always terminate the stream. The default `chat` Job is streaming:
```bash
curl -N http://127.0.0.1:2333/chat \
-H 'Content-Type: application/json' \
-d '{"query":"Summarize my long-term preferences"}'
```
MCP does not expose Stream Jobs.
## MCP
The default HTTP service mounts streamable HTTP MCP at `/mcp`:
```yaml
service:
backend: http
mcp_enabled: true
mcp_path: /mcp
mcp_stateless_http: false
```
For a standalone MCP service:
```bash
reme start service.backend=mcp service.transport=stdio
reme start service.backend=mcp service.transport=sse service.port=2333
reme start service.backend=mcp service.transport=streamable-http service.port=2333
```
MCP tools come from non-stream Jobs with `enable_serve: true`. Use `service.jobs` as an allowlist. `injected_job_kwargs` adds server-managed values that callers cannot override.
```yaml
service:
backend: http
jobs: [search, read, traverse, auto_memory]
injected_job_kwargs:
tenant_id: local-user
tool_error_on_failure: true
```
## ReMe Studio
After installing `reme-ai[web]` or `reme-ai[core]`, the default HTTP origin also serves Studio:
```text
http://127.0.0.1:2333/
```
Disable it with `service.web_enabled=false` or select a build with `service.web_static_dir`. Missing static assets do not prevent the Job API from starting.
## One-shot Jobs
```bash
reme start job=search query="user preferences" limit=5
```
This selects the one-shot CLI Service while retaining the normal Application, Component, and Job lifecycle.
## Service discovery
```bash
reme find_reme
```
Ordinary `reme <action>` commands prefer the running service's actual backend, host, port, and transport. They fall back to local configuration only when no service is discovered.
## Security boundary
ReMe is local-first:
- the default binds to `127.0.0.1`;
- HTTP CORS allows any origin;
- Jobs may write, move, or delete files;
- the service layer has no general-purpose user authentication.
Do not expose the default service directly to the public internet. For remote access, place it on a controlled network or behind an authenticated TLS reverse proxy, apply access controls and request-size limits, and expose only necessary Jobs.
## OpenAPI
FastAPI exposes the active endpoints through `/docs`, `/redoc`, and `/openapi.json`. The Studio SPA fallback preserves these reserved paths.

27
docs/figure/reme-icon.svg Normal file
View file

@ -0,0 +1,27 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="ReMe">
<defs>
<linearGradient id="brand" x1="14" y1="12" x2="51" y2="51" gradientUnits="userSpaceOnUse">
<stop stop-color="#21d1bb"/>
<stop offset="0.52" stop-color="#159fc9"/>
<stop offset="1" stop-color="#3156d9"/>
</linearGradient>
<linearGradient id="glass" x1="10" y1="7" x2="56" y2="59" gradientUnits="userSpaceOnUse">
<stop stop-color="#ffffff" stop-opacity="0.94"/>
<stop offset="0.48" stop-color="#e4fffa" stop-opacity="0.82"/>
<stop offset="1" stop-color="#dbe7ff" stop-opacity="0.74"/>
</linearGradient>
<filter id="shadow" x="-30%" y="-30%" width="160%" height="170%">
<feDropShadow dx="0" dy="3" stdDeviation="3" flood-color="#287ca6" flood-opacity="0.22"/>
</filter>
</defs>
<g filter="url(#shadow)">
<rect x="3" y="3" width="58" height="58" rx="18" fill="url(#glass)"/>
<rect x="3.75" y="3.75" width="56.5" height="56.5" rx="17.25" fill="none" stroke="url(#brand)" stroke-opacity="0.46" stroke-width="1.5"/>
<path d="M10 21C18 8 43 7 55 17" fill="none" stroke="white" stroke-width="2.2" stroke-linecap="round" opacity="0.9"/>
</g>
<path d="M18 49V15h14.5C41 15 46 19.5 46 27s-5 12-13.5 12H27" fill="none" stroke="url(#brand)" stroke-width="7.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M27 38.5 46 49" fill="none" stroke="url(#brand)" stroke-width="7.5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="27" cy="38.5" r="2.2" fill="white" stroke="#43d7c5" stroke-width="0.8"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,46 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 780 220" role="img" aria-labelledby="title description">
<title id="title">ReMe</title>
<desc id="description">ReMe gradient wordmark and open memory ribbon</desc>
<defs>
<linearGradient id="brand" x1="24" y1="28" x2="746" y2="188" gradientUnits="userSpaceOnUse">
<stop stop-color="#20d2b7"/>
<stop offset="0.4" stop-color="#12a9cc"/>
<stop offset="0.74" stop-color="#2877e6"/>
<stop offset="1" stop-color="#5146e5"/>
</linearGradient>
<linearGradient id="icon-glass" x1="22" y1="18" x2="194" y2="204" gradientUnits="userSpaceOnUse">
<stop stop-color="#ffffff" stop-opacity="0.96"/>
<stop offset="0.5" stop-color="#e8fffb" stop-opacity="0.84"/>
<stop offset="1" stop-color="#dce8ff" stop-opacity="0.76"/>
</linearGradient>
<linearGradient id="wordmark" x1="252" y1="72" x2="742" y2="164" gradientUnits="userSpaceOnUse">
<stop stop-color="#12b6bd"/>
<stop offset="0.5" stop-color="#168fd0"/>
<stop offset="1" stop-color="#4961dc"/>
</linearGradient>
<filter id="soft-shadow" x="-25%" y="-30%" width="150%" height="170%">
<feDropShadow dx="0" dy="8" stdDeviation="8" flood-color="#237caa" flood-opacity="0.16"/>
</filter>
<filter id="icon-shadow" x="-25%" y="-25%" width="150%" height="160%">
<feDropShadow dx="0" dy="7" stdDeviation="8" flood-color="#287ca6" flood-opacity="0.2"/>
</filter>
<filter id="wordmark-shadow" x="-10%" y="-20%" width="120%" height="150%">
<feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#2876b8" flood-opacity="0.18"/>
</filter>
</defs>
<!-- The memory ribbon lives in its own glass app tile, separated from the wordmark. -->
<g filter="url(#icon-shadow)">
<rect x="15" y="15" width="190" height="190" rx="54" fill="url(#icon-glass)"/>
<rect x="16.5" y="16.5" width="187" height="187" rx="52.5" fill="none" stroke="url(#brand)" stroke-opacity="0.48" stroke-width="3"/>
<path d="M37 69C63 27 144 24 182 57" fill="none" stroke="white" stroke-width="7" stroke-linecap="round" opacity="0.92"/>
</g>
<g fill="none" stroke="url(#brand)" stroke-linecap="round" stroke-linejoin="round">
<path d="M64 166V54h48c28 0 45 15 45 39.5S140 133 112 133H94" stroke-width="25"/>
<path d="M94 131 157 166" stroke-width="25"/>
</g>
<circle cx="94" cy="131" r="7" fill="white" stroke="#3bd7c2" stroke-width="3"/>
<text x="252" y="159" fill="url(#wordmark)" font-family="Optima, Candara, 'Segoe UI', sans-serif" font-size="132" font-weight="600" letter-spacing="-3" filter="url(#wordmark-shadow)">ReMe</text>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

43
docs/index.md Normal file
View file

@ -0,0 +1,43 @@
---
layout: home
title: ReMe Documentation
titleTemplate: false
head:
- - meta
- http-equiv: refresh
content: "0; url=/zh/"
- - link
- rel: canonical
href: https://reme.agentscope.io/zh/
hero:
name: ReMe
text: 让 Agent 真正记住
tagline: 文件属于你,记忆服务于 Agent。ReMe 将对话和资料沉淀为可读、可编辑、可检索、相互链接的本地文件。
image:
src: /reme-logo.svg
alt: ReMe Logo
actions:
- theme: brand
text: 快速开始
link: /zh/quick_start
- theme: alt
text: 核心概念
link: /zh/memory_as_file
features:
- icon: 📁
title: Local-first
details: Workspace 文件是持久事实源;索引、图谱和缓存都可以重新构建。
link: /zh/memory_as_file
- icon: 🧠
title: Memory workflows
details: 将对话和资料写入 daily,自动整理为互相关联的长期 digest。
link: /zh/auto_memory
- icon: 🔎
title: Search and graph
details: 结合关键词、可选向量检索与 wikilink 图谱,渐进式展开上下文。
link: /zh/memory_search
- icon: 🔌
title: Agent integrations
details: 通过 CLI、HTTP、MCP 和宿主适配器接入已有 Agent。
link: /zh/integrations
---

168
docs/zh/configuration.md Normal file
View file

@ -0,0 +1,168 @@
---
title: 基础配置
description: ReMe 配置文件、环境变量、命令行覆盖和核心组件配置。
---
# 基础配置
ReMe 使用 YAML 或 JSON 描述 Service、Job 和 Component。默认配置位于 `reme/config/default.yaml`;启动时可以选择其他配置,再用命令行覆盖其中的字段。
## 配置优先级
配置按下面的顺序合并,靠后的值优先:
1. 已启用插件提供的 `application_defaults`。
2. 选中的配置文件;未指定时使用内置 `default`。
3. 命令行 dot notation 覆盖。
```bash
reme start
reme start config=demo
reme start config=/absolute/path/to/app.yaml
reme start service.port=8181 workspace_dir=/data/reme
```
`config` 支持内置配置名以及 `.yaml`、`.yml`、`.json` 文件。覆盖采用深度合并,不会因为修改 `service.port` 而丢失 `service` 下的其他字段。
## 值的解析
CLI 参数使用 `key=value`,前导 `-` 或 `--` 也会被接受:
```bash
reme start --service.port=8181 --service.web_enabled=false
```
值支持:
- `null`、布尔值、整数和浮点数;
- JSON 数组和对象;
- JSON 引号字符串;
- 普通字符串。
类似 `007` 的前导零字符串不会被转换成数字。需要保留 `true`、`false` 等字面字符串时,使用 JSON 引号:`value='"true"'`。
## 环境变量
配置文件会递归展开两种表达式:
```yaml
api_key: ${LLM_API_KEY}
base_url: ${LLM_BASE_URL:-https://example.com/v1}
```
`${VAR}` 在变量未定义时会报错;`${VAR:-default}` 使用默认值。ReMe 还会从命令启动目录向上查找 `.env`,最多检查五级父目录。
不要把密钥提交到配置文件或 Git。推荐把密钥放在 `.env` 或进程环境中。
## Application 字段
| 字段 | 默认值 | 作用 |
|---|---|---|
| `app_name` | `ReMe` | 应用显示名称 |
| `workspace_dir` | `.reme` | 用户拥有的 workspace 根目录,会规范化为绝对路径 |
| `metadata_dir` | `metadata` | 索引、图谱和 catalog 等派生状态 |
| `session_dir` | `session` | Agent 对话记录;标准 transcript 位于 `session/dialog` |
| `mem_session_dir` | `mem_session` | Agent wrapper 的会话和配置 |
| `resource_dir` | `resource` | 外部资料 |
| `daily_dir` | `daily` | Daily memory |
| `digest_dir` | `digest` | 长期整理后的记忆 |
| `timezone` | `Asia/Shanghai` | Cron、日期和梦境流程使用的 IANA 时区 |
| `language` | 空 | LLM 交互默认语言 |
| `plugins` | `[]` | 为当前 Application 启用的已安装插件 |
| `service` | HTTP | 服务端配置 |
| `jobs` | 默认 Job | Job 名到 Job 配置的映射 |
| `components` | 默认组件 | 按类型和名称组织的组件配置 |
`session_dir` 必须保持 workspace-relative。其他 workspace 子目录也应使用清晰、稳定的相对名称。
## LLM 配置
默认 LLM 使用 OpenAI-compatible 接口:
```yaml
components:
as_llm:
default:
backend: openai
model: qwen3.7-plus
context_size: 200000
credential:
api_key: ${LLM_API_KEY:-}
base_url: ${LLM_BASE_URL:-}
```
可注册的内置 backend 包括 `openai`、`anthropic`、`dashscope`、`deepseek`、`gemini`、`moonshot`、`ollama` 和 `xai`。实际字段由对应 AgentScope model wrapper 决定。
基础文件操作、BM25 检索、wikilink 遍历不需要 LLM。`auto_memory`、`auto_resource`、`auto_dream` 等演化流程需要可用 LLM。
## Embedding 配置
向量检索默认关闭。只设置 `EMBEDDING_API_KEY` 不会自动启用它;还需要同时启用 `as_embedding`、`embedding_store`,并把它连接到 `file_store`:
```yaml
components:
as_embedding:
default:
backend: openai
model: text-embedding-v4
dimensions: 1024
credential:
api_key: ${EMBEDDING_API_KEY}
base_url: ${EMBEDDING_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1}
embedding_store:
default:
backend: local
as_embedding: default
file_store:
default:
backend: local
embedding_store: default
keyword_index: default
file_graph: default
```
修改 embedding 模型或维度后,应重新构建 embedding 索引。
## Service 和 Job
最小 HTTP 配置:
```yaml
service:
backend: http
host: 127.0.0.1
port: 2333
web_enabled: true
mcp_enabled: true
mcp_path: /mcp
```
Job 由 backend、参数 schema 和顺序执行的 Step 组成:
```yaml
jobs:
example:
backend: base
description: Example job
parameters:
type: object
properties:
text: { type: string }
required: [text]
steps:
- backend: example_step
```
设置 `enable_serve: false` 可以保留内部 Job、禁止 Service 暴露。后台和 Cron Job始终不会作为请求端点暴露。
## 查看生效配置
服务启动后运行:
```bash
reme app_config
```
返回的是已合并、已校验并隐藏密钥后的配置。排查覆盖顺序或插件配置时,应以它为准,而不是只查看某一个 YAML 文件。
完整字段定义以 `reme/schema/application_config.py` 和 `reme/config/default.yaml` 为准。

View file

@ -190,6 +190,12 @@ pytest tests/unit/test_reme_cli.py
docs/
```
用户指南应同时提供 `docs/zh/` 与 `docs/en/` 版本,并在 `docs/.vitepress/config.mts` 的对应导航中注册。ReMe Studio、
TypeScript、插件和评测的 README 是各自目录中的规范源文件;`github-pages/scripts/generate-content.mjs` 会在构建时镜像它们,
不要编辑 `.generated/` 或 `dist/`。
`Job API 参考`由 `reme/config/default.yaml` 自动生成。修改默认 Job 参数时更新 YAML 和测试,不要手工维护生成页。
建议文档保持:
- 标题明确,直接说明能力或流程。
@ -197,6 +203,15 @@ docs/
- 涉及路径时使用仓库内真实路径,例如 `reme/config/default.yaml`、`reme/steps/`、`tests/unit/`。
- 涉及默认行为时,以当前代码和 `pyproject.toml`、默认配置为准。
文档站检查:
```bash
cd github-pages
npm ci
npm test
npm run build
```
---
## 获取帮助

75
docs/zh/faq.md Normal file
View file

@ -0,0 +1,75 @@
---
title: 常见问题
description: ReMe 安装、服务、模型、检索、文件和插件问题的快速答案。
---
# 常见问题
## 基础文件操作需要模型 API Key 吗?
不需要。`write`、`read`、`list`、`stat`、BM25 搜索和 wikilink 遍历可以在没有模型凭据时运行。`auto_memory`、`auto_resource` 和 `auto_dream` 需要 LLM。
## 为什么配置了 Embedding Key 仍然只有 BM25?
Embedding 默认未启用。除了凭据,还必须配置 `as_embedding`、`embedding_store`,并让 `file_store.default.embedding_store` 指向该组件。参见[基础配置](./configuration.md#embedding-配置)。
## 为什么 `reme reindex` 没有发现新文件?
`reindex` 只从当前 `file_chunks` 重建 BM25 或 embedding 索引,不扫描 workspace。检查 `index_update_loop` watcher、文件目录、后缀和 `health_check`。
## 怎样使用另一个 workspace?
启动时传入稳定的绝对路径:
```bash
reme start workspace_dir=/absolute/path/to/memory
```
普通 CLI 调用会发现运行服务,不必重复 workspace 参数。
## 端口 2333 被占用怎么办?
不要停止未知监听者。选择另一个端口:
```bash
reme start service.port=8181
```
然后用 `reme find_reme` 确认发现结果。
## 为什么插件安装后仍然没有对应 Job?
安装只让 distribution 在当前 Python 环境中可见。还需要在应用配置中启用:
```bash
reme start plugins='["auto-fin"]'
```
修改安装或启用状态后需要重启已运行的服务。
## 可以直接编辑 workspace 中的 Markdown 吗?
可以。文件是事实源,watcher 会摄取修改。应保留有效 frontmatter、使用完整 workspace-relative wikilink,并避免同时由多个编辑器无条件覆盖同一文件。
## 可以把服务暴露到公网吗?
默认配置不适合直接公网暴露。服务包含写入和删除 Job,HTTP CORS 宽松,且没有通用认证层。请使用受控网络或带 TLS、认证、访问控制的反向代理,并限制 `service.jobs`。
## 怎样备份和迁移?
停止写入后备份整个 workspace。`session/`、`resource/`、`daily/` 和 `digest/` 是最重要的用户数据;`metadata/` 可以随同备份,也可以从源文件重建。详见[诊断、备份与恢复](./operations.md)。
## Studio 找不到怎么办?
基础 `reme-ai` 包不包含前端资源。安装 `reme-ai[web]` 或 `reme-ai[core]`,或通过 `service.web_static_dir` 指向构建产物。Studio 缺失不会影响 Job API。
## 当前服务到底开放了哪些能力?
运行:
```bash
reme help
reme app_config
```
静态文档描述默认配置;运行服务可能由自定义配置和插件改变。

36
docs/zh/index.md Normal file
View file

@ -0,0 +1,36 @@
---
layout: home
title: ReMe 文档
titleTemplate: false
hero:
name: ReMe
text: 让 Agent 真正记住
tagline: 文件属于你,记忆服务于 Agent。ReMe 将对话和资料沉淀为可读、可编辑、可检索、相互链接的本地文件。
image:
src: /reme-logo.svg
alt: ReMe Logo
actions:
- theme: brand
text: 快速开始
link: /zh/quick_start
- theme: alt
text: 查看配置
link: /zh/configuration
features:
- icon: 📁
title: 文件即记忆
details: Workspace 文件是持久事实源;索引、图谱和缓存都可以重新构建。
link: /zh/memory_as_file
- icon: 🧠
title: 自动记忆
details: 将对话和资料写入 daily,自动整理为互相关联的长期 digest。
link: /zh/auto_memory
- icon: 🔎
title: 检索与图谱
details: 结合关键词、可选向量检索与 wikilink 图谱,渐进式展开上下文。
link: /zh/memory_search
- icon: 🔌
title: Agent 集成
details: 通过 CLI、HTTP、MCP 和宿主适配器接入已有 Agent。
link: /zh/integrations
---

88
docs/zh/integrations.md Normal file
View file

@ -0,0 +1,88 @@
---
title: Agent 集成
description: 通过 CLI、HTTP、MCP、Skill 和宿主适配器把 ReMe 接入 Agent。
---
# Agent 集成
ReMe 把记忆能力放在独立服务和用户拥有的 workspace 中。Agent 可以通过标准接口调用同一套记忆,而不必把存储逻辑绑定到某一个模型或宿主。
## 选择接入方式
| 场景 | 推荐方式 |
|---|---|
| 本机脚本或 Hook | ReMe CLI |
| 应用后端 | HTTP Client |
| 支持工具协议的 Agent | MCP |
| TypeScript Agent | `@agentscope-ai/reme` |
| Claude Code | MCP + Skill + Stop Hook |
| Hermes Agent | Memory provider adapter |
| Codex 或其他 coding agent | `reme_memory` Skill 或 MCP |
## 通用接入循环
一个完整但可控的 Agent 记忆循环通常包含:
1. 会话开始或回答前,用 `search` 找到相关记忆;
2. 对高价值结果使用 `read`,必要时用 `traverse` 展开关系;
3. 在回答中保留 workspace-relative 来源路径;
4. 会话结束后,把原始消息交给 `auto_memory`;
5. 由后台或定时任务把 daily 内容整理到 digest。
搜索不到内容时应明确返回空结果,不应把模型推测当成历史记忆。
## MCP
默认 HTTP 服务在 `http://127.0.0.1:2333/mcp` 提供 streamable HTTP MCP。常用工具包括:
- `search`
- `read`
- `traverse`
- `list`
- `auto_memory`
- `proactive`
根据宿主风险模型,可以用 `service.jobs` 只暴露只读工具,或将写入工具放在单独配置中。
## CLI 和 Skill
仓库中的 `skills/reme_memory/SKILL.md` 描述了一个通用 Agent 工作流,包括安装检查、服务发现、检索、读取和写入边界。它适合能够执行本地命令的 Agent。
Skill 不应:
- 未经允许安装或升级 Python 环境;
- 发现端口冲突后停止未知进程;
- 把召回的工具结果再次写入对话来源;
- 将密钥或敏感信息写入记忆。
## TypeScript、OpenClaw 与 DeepSeek Harness
统一 HTTP 客户端和包能力见 [TypeScript Agent 集成](./integrations/typescript.md)。宿主的完整安装、配置和运行说明见 [DeepSeek Harness](./integrations/dsh.md) 与 [OpenClaw](./integrations/openclaw.md) 指南。它们包含:
- HTTP Client;
- DeepSeek Harness adapter;
- OpenClaw adapter;
- 构建与发布检查。
## Claude Code
仓库的 `integrations/claude_code/` 提供:
- streamable HTTP MCP 配置;
- `reme-memory` Skill;
- 会话停止时调用 `auto_memory_cc` 的 Hook。
完整安装步骤以仓库中的 `integrations/claude_code/README.md` 为准。
## Hermes Agent
`integrations/hermes_agent/` 提供 memory provider:模型调用前检索相关记忆,每轮结束后异步调用 `auto_memory`。完整配置见该目录 README。
## 生产接入建议
- 明确选择稳定、绝对的 `workspace_dir`;
- 启动前复用 `reme find_reme` 发现的服务;
- 以 `reme help` 为当前 Job 契约;
- 为写入动作设置超时和失败日志;
- 不因记忆服务暂时不可用而阻塞宿主的核心回答流程;
- 对远程访问使用认证、TLS 和最小 Job allowlist。

View file

@ -0,0 +1,67 @@
---
title: Claude Code 集成
description: 通过 MCP、reme-memory Skill 和 Stop Hook 将 Claude Code 连接到 ReMe。
---
# Claude Code 集成
ReMe 的 Claude Code 插件提供长期记忆召回,并在每次会话结束后异步记录对话。Daily 到 digest 的整理仍由共享的 ReMe 服务负责。
## 能力
- 通过 MCP 使用 `search`、`traverse`、`daily_list`、`frontmatter_read`、`read`、`auto_memory_cc` 等工具;
- `reme-memory` Skill 在回答前召回长期记忆并保留来源路径;
- Stop Hook 只把 Claude Code `session_id` 传给服务端,服务端从本地 transcript 解析会话;
- 记录在脱离 Claude Code 的后台进程中进行,不延迟退出;服务不可用时记录日志并结束。
## 部署模型
插件连接到用户预先启动的共享 HTTP MCP 服务,不为每个 Claude Code 窗口创建 ReMe。这样所有窗口共享一个 workspace、一组 watcher 和一次 dream cron。
## 准备 ReMe
```bash
pip install "reme-ai[core]"
```
在稳定目录配置 LLM 环境,然后启动:
```bash
reme start service.backend=http
```
默认 JSON Job API 和 MCP 地址分别位于同一个 `127.0.0.1:2333` 服务,MCP 路径是 `/mcp`。使用其他端口时,必须同步修改插件的 `.mcp.json`。
默认搜索使用 BM25;只有启用向量检索时才需要 Embedding 配置。
## 安装插件
在 Claude Code 中运行:
```text
/plugin marketplace add ./integrations/claude_code
/plugin install reme@reme-marketplace
```
重启 Claude Code,再运行 `/mcp`,确认 `reme` server 和工具已经连接。
## Hook 与路径
- MCP 配置:`integrations/claude_code/reme/.mcp.json`;
- 自动记忆 Hook:`integrations/claude_code/reme/hooks/auto_memory.py`;
- Hook 日志:`integrations/claude_code/reme/logs/auto_memory_hook.log`;
- 默认 transcript 根目录:`~/.claude/projects`;
- 可通过 `CLAUDE_CONFIG_DIR` 修改 transcript 根目录;
- 可通过 `REME_HOST`、`REME_PORT` 覆盖 Hook 使用的服务地址。
Hook 需要 `python3` 位于 `PATH`。MCP 工具名前缀可能随 Claude Code 版本包含 server segment;Skill 使用 `mcp__reme__*` 匹配这一差异。
## 验证
1. `reme health_check` 返回健康;
2. Claude Code `/mcp` 显示 ReMe;
3. `reme-memory` 能召回一条已存在记忆;
4. 完成测试会话后,Hook 日志没有错误;
5. 对应内容出现在当天 daily note 中。
英文原始部署说明位于 `integrations/claude_code/README.md`。

View file

@ -0,0 +1,59 @@
---
title: Hermes Agent 集成
description: 使用 ReMe memory provider 在 Hermes 调用模型前召回、每轮结束后异步记录。
---
# Hermes Agent 集成
Hermes memory provider 连接到一个已经运行的 ReMe HTTP 服务,在每次模型调用前召回相关记忆,并在用户/助手回合完成后异步调用 `auto_memory`。
## Workspace 隔离
ReMe 的搜索范围是一个完整 workspace。多个 Hermes profile 指向同一个 workspace 时会共享召回结果;需要隔离时,为每个 profile 使用独立 workspace 和端点。
```bash
reme start \
workspace_dir="/absolute/path/to/reme-hermes-default" \
service.backend=http \
service.host=127.0.0.1 \
service.port=2333
```
自动记忆需要 LLM;默认 BM25 搜索不需要 Embedding。
## 安装与配置
```bash
hermes plugins install agentscope-ai/ReMe/integrations/hermes_agent
hermes memory setup
```
选择 `reme`,接受默认的 `http://127.0.0.1:2333`,或输入上一步使用的端点。Setup 会先调用 `health_check`,只有新端点健康时才替换现有 provider 配置。
配置存放在 `$HERMES_HOME/reme.json`:
```json
{
"endpoint": "http://127.0.0.1:2333",
"request_timeout": 600.0,
"recall_timeout": 5.0,
"health_timeout": 2.0,
"health_retry_seconds": 30.0,
"shutdown_timeout": 30.0,
"recall_limit": 5
}
```
运行 `hermes memory status` 检查安装和配置。新的 Hermes 会话还会重新检查端点健康状态。
## 生命周期和失败行为
- `prefetch` 调用 ReMe `search`,Hermes 将结果放入受保护的 memory context;
- `sync_turn` 把完成的回合加入串行后台写队列,再调用 `auto_memory`;
- cron、flush 和 subagent context 不写入对话记忆;
- 健康检查失败后,在 cooldown 结束前暂停召回和记录;
- 召回与写入有独立 cooldown,单项失败不会关闭另一项;
- 召回使用较短超时,避免慢搜索长期阻塞模型调用;
- shutdown 会在有限时间内排空写队列,ReMe 服务仍由用户独立管理。
英文权威安装说明位于 `integrations/hermes_agent/README.md`。

106
docs/zh/operations.md Normal file
View file

@ -0,0 +1,106 @@
---
title: 诊断、备份与恢复
description: ReMe 服务健康检查、日志、索引维护、备份迁移和常见恢复流程。
---
# 诊断、备份与恢复
ReMe 的恢复原则是:保护 workspace 中的用户文件,通过源文件重建索引、图谱和 catalog。不要为了修复索引而删除或改写用户记忆。
## 快速诊断
依次执行:
```bash
reme find_reme
reme version
reme health_check
reme status
reme app_config
```
- `find_reme`:服务是否存在,以及实际 host、port、PID;
- `version`:CLI 能否成功访问服务;
- `health_check`:组件健康状态;
- `status`:状态组件的内存估算和进程 RSS;
- `app_config`:隐藏密钥后的实际生效配置。
## 日志
`log_to_console` 和 `log_to_file` 控制日志目标。排查启动失败时先查看第一条异常,而不是后续客户端连接错误。
常见类别:
| 现象 | 优先检查 |
|---|---|
| CLI 找不到服务 | `reme find_reme`、启动目录、端口和进程状态 |
| 自动记忆失败 | LLM backend、model、API key、base URL |
| 只有 BM25 结果 | embedding 组件是否真正接入 `file_store` |
| 新文件没有进入搜索 | 文件所在目录、后缀、watcher 和 `health_check` |
| Studio 空白但 API 正常 | web extra、静态构建路径和浏览器控制台 |
| 插件安装后不可用 | 当前 Python 解释器、`plugins` 配置、服务重启 |
## 索引维护
```bash
reme reindex scope=all
reme reindex scope=bm25
reme reindex scope=embedding
```
`reindex` 从当前 `file_chunks` 重建 BM25 和/或 embedding 派生索引。它不会扫描 workspace、重新分块或重建 wikilink 图谱。
如果问题发生在文件摄取阶段,应先确认后台 watcher 正常运行;不能把 `reindex` 当成通用“重新扫描”命令。
Daily 索引页可单独重建:
```bash
reme daily_reindex date=2026-09-04
```
## 备份
停止写入或停止服务后,优先备份整个 workspace。最重要的目录是:
- `session/`:原始对话来源;
- `resource/`:外部资料;
- `daily/`:每日记忆;
- `digest/`:长期记忆。
`metadata/` 包含索引、图谱和 file catalog。一起备份可以加速恢复,但它不是唯一事实源。
不要只备份进程目录下偶然生成的 `.reme/`;生产使用应明确设置稳定的绝对 `workspace_dir`。
## 迁移 workspace
1. 停止旧服务,避免迁移期间继续写入。
2. 复制完整 workspace,并保留文件时间信息。
3. 使用新的绝对路径启动:
```bash
reme start workspace_dir=/new/location/reme-memory
```
4. 运行 `health_check`、`status` 和一次代表性 `search`。
5. 如果 embedding 模型或维度发生变化,再重建 embedding 索引。
普通 Markdown 和资源文件可以使用版本控制或同步工具;包含敏感对话的 workspace 不应推送到公开仓库。
## 从派生状态故障恢复
在确认备份可用前,不要删除任何内容。恢复顺序应为:
1. 保留 `session/`、`resource/`、`daily/` 和 `digest/`;
2. 记录当前配置和组件 backend;
3. 确认故障只发生在 `metadata/`;
4. 将有问题的派生状态移到隔离备份位置;
5. 用同一配置启动 ReMe,让 watcher 从源文件重建;
6. 验证搜索、图谱和 Daily 索引。
具体 metadata 文件属于实现细节,不应在自动化脚本里依赖其内部格式。
## 并发编辑
Studio 或其他编辑器保存完整文件时,应使用 `stat` 返回的 mtime 作为 `save.expected_mtime`。如果文件在打开后被外部修改,保存会失败,从而避免静默覆盖。
文件 Job 会校验 workspace containment,并对同一路径加锁。不要绕过这些 Job 直接向不受控制的绝对路径写入。

View file

@ -0,0 +1,101 @@
---
title: 插件开发
description: 创建、注册、配置、测试和发布 ReMe 插件。
---
# 插件开发
ReMe 插件是一个普通 Python distribution,通过 `reme.plugins` entry-point group 暴露 package-level `plugin.yaml`。插件可以注册新的 Step、Component backend,并提供默认 Application 配置。
## 最小结构
```text
my-plugin/
├── pyproject.toml
└── src/my_plugin/
├── __init__.py
├── plugin.yaml
└── steps.py
```
`pyproject.toml`:
```toml
[project.entry-points."reme.plugins"]
my-plugin = "my_plugin"
```
`plugin.yaml`:
```yaml
name: my-plugin
backends:
my_step: my_plugin.steps:MyStep
application_defaults:
jobs:
my_action:
backend: base
description: Run my plugin action
parameters:
type: object
properties:
text: { type: string }
required: [text]
steps:
- backend: my_step
```
## 实现 Step
```python
from reme.components.component_registry import R
from reme.steps.base_step import BaseStep
@R.register("my_step")
class MyStep(BaseStep):
async def execute(self):
self.context.response.answer = self.context.data["text"]
```
Step 实例属于单次 Job 调用。跨调用的内存状态应放在带命名空间的 `app_context.metadata`;需要生命周期、锁或持久化时,应升级为 Component 或 workspace 文件。
## 配置合并
`application_defaults` 是不完整的 `ApplicationConfig`。合并顺序为:
```text
插件默认值 < 选中/default 配置 < CLI 覆盖
```
插件不应自动修改用户的配置文件。只有在 Application 的 `plugins` 列表中显式启用后,插件 backend 才会加入该 Application 的局部 registry。
## 本地验证
```bash
reme plugins validate ./path/to/my-plugin
reme plugins install ./path/to/my-plugin --editable
reme plugins list
reme plugins show my-plugin
reme start plugins='["my-plugin"]'
reme my_action text=hello
```
校验会导入插件代码,因此只应对可信源码执行。
## 测试边界
- 使用 `tmp_path` 创建 workspace;
- Mock 网络、模型和子进程;
- 验证插件未启用时不会污染 built-in registry;
- 验证默认配置与显式配置的合并优先级;
- 验证后台任务、客户端和 executor 都由 Component 生命周期关闭;
- 验证失败不会删除或重写用户源文件。
仓库中的 `plugins/daily_paper`、`plugins/auto-fin`、`plugins/lme` 和 `plugins/beam` 是完整参考实现。
## 兼容性
迁移期间仍兼容旧的 Python Plugin descriptor 和 `reme.configs` entry point,但新插件应使用 `plugin.yaml`。不要依赖进程级全局 enable/disable 状态;插件启用始终属于具体 Application 配置。
包管理命令、升级与卸载行为见[插件管理](./plugin_management.md)。

View file

@ -169,6 +169,15 @@ curl -s http://127.0.0.1:2333/auto_fin \
自定义应用配置需要提供插件的运行依赖,包括 `agent_wrapper.default`,以及 Auto Fin 使用的 `search` 和 `read` Jobs。
## Benchmark 应用配置
[LME](../../plugins/lme/README_ZH.md) 和 [BEAM](../../plugins/beam/README_ZH.md) 插件通过
`plugin.yaml` 注册 backend 和插件拥有的 Job。ReMe 内置的 `benchmark` 配置提供公共核心 Job 和
Component,并且不继承 `default`,因此不包含默认后台和定时任务。先安装所需的 benchmark 插件,
再使用 `config=benchmark`,同时指定 `plugins=["lme"]` 或 `plugins=["beam"]`。仓库内的 benchmark
runner 会自动启用对应的已安装插件;editable 安装可让本地源码修改直接生效。数据集 runner 仍位于
`benchmark/`。
## 卸载插件
这里使用插件 entry-point 名称,它不一定等于 distribution 名称:

View file

@ -1,5 +1,12 @@
---
title: 快速开始
description: 安装并启动 ReMe,完成文件写入、检索和自动记忆的第一个闭环。
---
# 快速开始
本页用于完成第一次可运行闭环。需要完整配置字段时查看[基础配置](./configuration.md);接入 HTTP 或 MCP 时查看[服务与部署](./services.md)。
## 安装
ReMe 要求 Python 3.11+。
@ -229,3 +236,5 @@ reme start \
```bash
reme start config=/path/to/custom.yaml
```
下一步可以查看 [CLI 参考](./reference/cli.md)、[Job API 参考](./reference/jobs.md)和[诊断、备份与恢复](./operations.md)。

87
docs/zh/reference/cli.md Normal file
View file

@ -0,0 +1,87 @@
---
title: CLI 参考
description: ReMe 命令行语法、服务调用、配置覆盖与插件命令。
---
# CLI 参考
ReMe 的基本语法是:
```text
reme ACTION key=value ...
```
## 启动应用
```bash
reme start
reme start config=demo
reme start workspace_dir=/data/reme service.port=8181
reme start job=search query="关键词" limit=5
```
`start job=<name>` 运行一次性 Job;普通 `start` 启动配置中的 Service。
## 调用 Job
服务运行后,Action 名就是 Job 名:
```bash
reme help
reme health_check
reme search query="项目决策" limit=10
reme read path=digest/wiki/project.md start_line=1 end_line=80
```
复杂值使用 JSON:
```bash
reme auto_memory \
session_id=example \
messages='[{"role":"user","content":"记住这条偏好"}]'
```
服务选择参数 `backend`、`transport`、`host`、`port`、`timeout`、`command`、`args` 和 `show_metadata` 只用于构造客户端,不会泄漏到 Job 参数。
## 配置覆盖
```bash
reme start \
config=/path/to/custom.yaml \
service.port=8181 \
service.web_enabled=false \
plugins='["auto-fin"]'
```
参数前的 `-` 或 `--` 可以省略。嵌套键使用点号,数组和对象使用 JSON。
## 服务发现
```bash
reme find_reme
```
成功时输出可复用的服务信息;找不到服务时不会自动启动新进程。
## 插件命令
插件包管理是本地命令,不经过 HTTP 或 MCP:
```bash
reme plugins list
reme plugins show auto-fin
reme plugins validate auto-fin
reme plugins install reme-auto-fin
reme plugins uninstall auto-fin
```
完整说明见[插件管理](../plugin_management.md)。
## 发现当前能力
默认 Job 参数见 [Job API 参考](./jobs.md)。运行配置可能由插件和自定义 YAML 改变,因此自动化程序应优先调用:
```bash
reme help
reme app_config
```

131
docs/zh/services.md Normal file
View file

@ -0,0 +1,131 @@
---
title: 服务与部署
description: 使用 ReMe 的 HTTP、SSE、MCP 和 Studio 服务,并理解默认安全边界。
---
# 服务与部署
ReMe 可以作为本地 HTTP 服务、独立 MCP Server 或一次性 CLI Job 运行。默认模式是在 `127.0.0.1:2333` 启动 HTTP 服务,并在同一进程中提供 JSON API、SSE、MCP 与可选的 ReMe Studio。
## HTTP 服务
```bash
reme start
reme start service.host=127.0.0.1 service.port=8181
```
普通 Job 暴露为 `POST /<job-name>`:
```bash
curl -s http://127.0.0.1:2333/search \
-H 'Content-Type: application/json' \
-d '{"query":"用户偏好","limit":5}'
```
请求体允许 Job 参数位于顶层。响应遵循 `Response`:
```json
{
"success": true,
"answer": "...",
"metadata": {}
}
```
未被 Step 捕获的错误会转换成 `success: false` 的响应。
## Streaming 与 SSE
`backend: stream` 的 Job 仍使用 `POST /<job-name>`,但响应类型是 `text/event-stream`。每个 chunk 使用统一的 streaming schema;失败时会发出错误 chunk,并以终止事件结束。
默认 `chat` 是 Stream Job:
```bash
curl -N http://127.0.0.1:2333/chat \
-H 'Content-Type: application/json' \
-d '{"query":"总结我的长期偏好"}'
```
MCP 不暴露 Stream Job。
## MCP
默认 HTTP 服务会在 `/mcp` 挂载 streamable HTTP MCP:
```yaml
service:
backend: http
mcp_enabled: true
mcp_path: /mcp
mcp_stateless_http: false
```
也可以改用独立 MCP Service:
```bash
reme start service.backend=mcp service.transport=stdio
reme start service.backend=mcp service.transport=sse service.port=2333
reme start service.backend=mcp service.transport=streamable-http service.port=2333
```
MCP tool 来自 `enable_serve: true` 的非流式 Job。`service.jobs` 可以设置允许列表;`injected_job_kwargs` 可以注入服务端管理、调用方不能覆盖的参数。
```yaml
service:
backend: http
jobs: [search, read, traverse, auto_memory]
injected_job_kwargs:
tenant_id: local-user
tool_error_on_failure: true
```
## ReMe Studio
安装 `reme-ai[web]` 或 `reme-ai[core]` 后,默认 HTTP 地址同时提供 ReMe Studio:
```text
http://127.0.0.1:2333/
```
可通过配置关闭或指定自定义构建:
```yaml
service:
web_enabled: false
# web_static_dir: /absolute/path/to/static
```
静态资源缺失不会阻止 Job API 启动。
## 一次性 Job
需要脚本式执行而不常驻服务时:
```bash
reme start job=search query="用户偏好" limit=5
```
它会切换到一次性 CLI Service,但仍经过正常的 Application、Component 和 Job 生命周期。
## 服务发现
```bash
reme find_reme
```
ReMe 会记录本机运行服务的启动参数。普通 `reme <action>` 优先使用实际运行服务的 backend、host、port 和 transport;找不到运行服务时才回退到本地配置。
## 安全边界
ReMe 默认定位为本地服务:
- 默认绑定 `127.0.0.1`;
- HTTP CORS 配置允许任意 origin;
- Job 可进行文件写入、移动和删除;
- 当前服务层不提供通用用户认证。
不要直接把默认服务暴露到公网。需要远程访问时,在受控网络或带身份认证、TLS、访问控制和请求大小限制的反向代理后部署,并通过 `service.jobs` 只开放必要 Job。
## OpenAPI
HTTP 服务使用 FastAPI。启用 HTTP 服务时,可以通过 `/docs`、`/redoc` 和 `/openapi.json` 查看当前配置实际注册的端点;Studio 的 SPA fallback 不会覆盖这些保留路径。

View file

@ -1,8 +1,8 @@
# ReMe GitHub Pages
# ReMe documentation site
This directory contains the standalone Vite documentation site published at <https://reme.agentscope.io>. The
GitHub Pages fallback is <https://agentscope-ai.github.io/ReMe/>. It does not depend on the ReMe Studio application in
`reme_studio/`.
This package builds the VitePress site published at <https://reme.agentscope.io>. The canonical documentation lives in
`docs/`; selected product, integration, plugin, and benchmark READMEs are mirrored into a disposable source tree during
the build. Do not edit `.generated/` or `dist/`.
## Requirements
@ -11,79 +11,41 @@ GitHub Pages fallback is <https://agentscope-ai.github.io/ReMe/>. It does not de
## Local development
From the repository root:
```bash
cd github-pages
npm install
npm ci
npm run dev
```
Open the URL printed by Vite, normally <http://localhost:5173/>. The development server watches the frontend source.
When a repository Markdown file changes, restart the development command to regenerate the documentation content.
The development server prints its local URL. Restart it after changing a mirrored README or `reme/config/default.yaml`
so the generated source tree and Job reference are refreshed. Changes under `docs/` are also refreshed on restart.
For subsequent installs or CI-compatible dependency installation, use:
```bash
npm ci
```
## Preview the production build
Build and start the preview server:
## Validation
```bash
npm test
npm run build
npm run preview
```
Open the URL printed by Vite, normally <http://localhost:4173/>. Production assets use relative paths so the same build
works on both the custom domain and the GitHub Pages project path.
The test suite verifies bilingual core pages, canonical-source mappings, generated Job coverage, and disposable output.
The production build is written to `github-pages/dist/` for the existing GitHub Pages workflow.
The generated `dist/` and `.generated/` directories are disposable build output and are excluded from Git.
## Sources
## Documentation sources
The build script reads the canonical repository files directly. Do not edit generated copies under `.generated/` or
`dist/`.
- `README.md` and `README_ZH.md`: project introductions
- `docs/en/` and `docs/zh/`: English and Chinese guides
- `docs/figure/`: documentation images
- `reme_studio/README.md` and `reme_studio/README_ZH.md`: ReMe Studio guide
- `typescript/README.md` and `typescript/README_ZH.md`: TypeScript client, DeepSeek Harness, and OpenClaw integration guide
- `docs/`: canonical guides, VitePress configuration, theme, and brand assets
- `reme/config/default.yaml`: generated callable Job reference
- `reme_studio/README*.md`: ReMe Studio
- `typescript/README*.md`: TypeScript client and adapters
- `plugins/*/README*.md`: plugin guides
- `benchmark/{beam,longmemeval,pibench,toolmemory}/README*.md`: benchmark guides and results
- `AGENTS.md`: repository development guide
- `benchmark/*/README*.md`: benchmark guides
- `scripts/generate-content.mjs`: source mirroring and reference generation
To add or reorganize a document in the site navigation, update
[`scripts/generate-content.mjs`](./scripts/generate-content.mjs). Presentation and interaction code lives in `src/`.
## Project structure
```text
github-pages/
├── index.html
├── package.json
├── scripts/
│ └── generate-content.mjs
├── src/
│ ├── main.js
│ └── styles.css
└── vite.config.js
```
When adding a canonical guide, add both `docs/zh/<name>.md` and `docs/en/<name>.md`, then include it in the appropriate
sidebar in `docs/.vitepress/config.mts`. Add repository-owned READMEs to `externalDocuments` in the generator rather than
duplicating their full content under `docs/`.
## Deployment
The repository workflow `.github/workflows/deploy-docs.yml` builds this directory and publishes `dist/` to GitHub Pages.
It runs after relevant documentation or site files change on `main`, and it can also be started manually from the
GitHub Actions page.
The repository's **Settings → Pages → Build and deployment → Source** must be set to **GitHub Actions**. Its custom
domain must be set to `reme.agentscope.io`; `public/CNAME` preserves that domain in the published artifact.
Useful links:
- ReMe documentation: <https://reme.agentscope.io>
- GitHub Pages fallback: <https://agentscope-ai.github.io/ReMe/>
- ReMe repository: <https://github.com/agentscope-ai/ReMe>
`.github/workflows/deploy-docs.yml` uses the reusable documentation build workflow and publishes `dist/` to GitHub
Pages. `public/CNAME` preserves the `reme.agentscope.io` custom domain.

View file

@ -1,17 +0,0 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="ReMe documentation — a local-first, file-native memory system for agents."
/>
<link rel="icon" type="image/svg+xml" href="%BASE_URL%favicon.svg" />
<title>ReMe Documentation</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -7,16 +7,14 @@
"node": ">=22.13.0"
},
"scripts": {
"dev": "node scripts/generate-content.mjs && vite",
"build": "node scripts/generate-content.mjs && vite build",
"preview": "vite preview",
"test": "node scripts/generate-content.mjs && node --test tests/*.test.mjs"
},
"dependencies": {
"dompurify": "^3.2.6",
"marked": "^16.2.1"
"content": "node scripts/generate-content.mjs",
"dev": "npm run content && vitepress dev .generated/site",
"build": "npm run content && vitepress build .generated/site --outDir dist && node scripts/verify-build.mjs",
"preview": "vitepress preview .generated/site --outDir dist",
"test": "npm run content && node --test tests/*.test.mjs"
},
"devDependencies": {
"vite": "^7.1.1"
"vitepress": "1.6.4",
"yaml": "^2.8.1"
}
}

View file

@ -1,249 +1,236 @@
import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { parse as parseYaml } from "yaml";
const siteDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const repoDir = path.resolve(siteDir, "..");
const outputDir = path.join(siteDir, ".generated", "content");
const outputDir = path.join(siteDir, ".generated", "site");
const navigationGroupOrder = [
"overview",
"start",
"integration",
"fundamentals",
"automation",
"concepts",
"workspace",
"plugins",
"benchmarks",
"development",
const externalDocuments = [
["en/integrations/claude-code.md", "integrations/claude_code/README.md"],
["en/integrations/hermes.md", "integrations/hermes_agent/README.md"],
["zh/integrations/typescript.md", "typescript/README_ZH.md"],
["en/integrations/typescript.md", "typescript/README.md"],
["zh/integrations/dsh.md", "typescript/docs/dsh.zh-CN.md"],
["en/integrations/dsh.md", "typescript/docs/dsh.md"],
["zh/integrations/openclaw.md", "typescript/docs/openclaw.zh-CN.md"],
["en/integrations/openclaw.md", "typescript/docs/openclaw.md"],
["zh/workspace/studio.md", "reme_studio/README_ZH.md"],
["en/workspace/studio.md", "reme_studio/README.md"],
["zh/plugins/daily-paper.md", "plugins/daily_paper/README_ZH.md"],
["en/plugins/daily-paper.md", "plugins/daily_paper/README.md"],
["zh/plugins/auto-fin.md", "plugins/auto-fin/README_ZH.md"],
["en/plugins/auto-fin.md", "plugins/auto-fin/README.md"],
["zh/plugins/lme.md", "plugins/lme/README_ZH.md"],
["en/plugins/lme.md", "plugins/lme/README.md"],
["zh/plugins/beam.md", "plugins/beam/README_ZH.md"],
["en/plugins/beam.md", "plugins/beam/README.md"],
["zh/benchmarks/beam.md", "benchmark/beam/README_ZH.md"],
["en/benchmarks/beam.md", "benchmark/beam/README.md"],
["zh/benchmarks/longmemeval.md", "benchmark/longmemeval/README_ZH.md"],
["en/benchmarks/longmemeval.md", "benchmark/longmemeval/README.md"],
["zh/benchmarks/pibench.md", "benchmark/pibench/README_ZH.md"],
["en/benchmarks/pibench.md", "benchmark/pibench/README.md"],
["zh/benchmarks/toolmemory.md", "benchmark/toolmemory/README_ZH.md"],
["en/benchmarks/toolmemory.md", "benchmark/toolmemory/README.md"],
];
const topicOrder = [
"quick_start",
"plugin_management",
"memory_as_file",
"memory_search",
"auto_memory",
"auto_resource",
"auto_link",
"auto_dream",
"proactive",
"reme_scene",
"framework",
"reme-blog",
"contributing",
];
const groups = {
quick_start: "start",
plugin_management: "start",
memory_as_file: "fundamentals",
memory_search: "fundamentals",
auto_memory: "automation",
auto_resource: "automation",
auto_link: "automation",
auto_dream: "automation",
proactive: "automation",
reme_scene: "concepts",
framework: "concepts",
"reme-blog": "concepts",
contributing: "development",
const externalDocumentRewrites = {
"typescript/README.md": [
["(./README_ZH.md)", "(/zh/integrations/typescript)"],
["(./docs/dsh.md)", "(/en/integrations/dsh)"],
["(./docs/dsh.zh-CN.md)", "(/zh/integrations/dsh)"],
["(./docs/openclaw.md)", "(/en/integrations/openclaw)"],
["(./docs/openclaw.zh-CN.md)", "(/zh/integrations/openclaw)"],
["(./figures/dsh/", "(/figures/dsh/"],
],
"typescript/README_ZH.md": [
["(./README.md)", "(/en/integrations/typescript)"],
["(./docs/dsh.md)", "(/en/integrations/dsh)"],
["(./docs/dsh.zh-CN.md)", "(/zh/integrations/dsh)"],
["(./docs/openclaw.md)", "(/en/integrations/openclaw)"],
["(./docs/openclaw.zh-CN.md)", "(/zh/integrations/openclaw)"],
["(./figures/dsh/", "(/figures/dsh/"],
],
"typescript/docs/dsh.md": [
["(./dsh.zh-CN.md)", "(/zh/integrations/dsh)"],
["(../figures/dsh/", "(/figures/dsh/"],
],
"typescript/docs/dsh.zh-CN.md": [
["(./dsh.md)", "(/en/integrations/dsh)"],
["(../figures/dsh/", "(/figures/dsh/"],
],
"typescript/docs/openclaw.md": [
["(./openclaw.zh-CN.md)", "(/zh/integrations/openclaw)"],
],
"typescript/docs/openclaw.zh-CN.md": [
["(./openclaw.md)", "(/en/integrations/openclaw)"],
],
};
const localizedTitles = {
quick_start: { zh: "快速开始", en: "Quick Start" },
plugin_management: { zh: "插件管理", en: "Plugin Management" },
memory_as_file: { zh: "文件即记忆", en: "Memory as File" },
memory_search: { zh: "记忆检索", en: "Memory Search" },
auto_memory: { zh: "自动记忆", en: "Auto Memory" },
auto_resource: { zh: "自动资料整理", en: "Auto Resource" },
auto_link: { zh: "自动关联", en: "Auto Link" },
auto_dream: { zh: "自动沉淀", en: "Auto Dream" },
proactive: { zh: "主动发现", en: "Proactive" },
reme_scene: { zh: "ReMe 应用场景", en: "ReMe Application Scenarios" },
framework: { zh: "ReMe 代码框架", en: "ReMe Framework" },
"reme-blog": { zh: "ReMe 博客", en: "ReMe Blog" },
contributing: { zh: "开源与贡献", en: "Open Source and Contributing" },
const groupNames = {
zh: {
system: "系统与诊断",
memory: "记忆演化",
retrieval: "检索与图谱",
daily: "Daily Note",
files: "文件操作",
},
en: {
system: "System and diagnostics",
memory: "Memory evolution",
retrieval: "Retrieval and graph",
daily: "Daily notes",
files: "File operations",
},
};
const productDocuments = [
{
slug: "typescript",
source: "typescript",
titles: { zh: "TypeScript Agent 集成", en: "TypeScript Agent Integrations" },
descriptions: {
zh: "配置统一 HTTP client,以及 DeepSeek Harness 和 OpenClaw 原生适配器。",
en: "Configure the shared HTTP client and native DeepSeek Harness and OpenClaw adapters.",
},
group: "integration",
},
{
slug: "studio",
source: "reme_studio",
titles: { zh: "ReMe 工作台", en: "ReMe Studio" },
descriptions: {
zh: "浏览、编辑和搜索本地记忆,并探索记忆图谱。",
en: "Browse, edit, search, and explore local memory from the web workspace.",
},
group: "workspace",
},
{
slug: "daily-paper",
source: "plugins/daily_paper",
titles: { zh: "每日论文插件", en: "Daily Paper Plugin" },
descriptions: {
zh: "发现论文、解析 PDF,并生成阅读笔记与每日简报。",
en: "Discover papers, analyze PDFs, and produce reading notes and a daily brief.",
},
group: "plugins",
},
{
slug: "auto-fin",
source: "plugins/auto-fin",
titles: { zh: "Auto Fin 插件", en: "Auto Fin Plugin" },
descriptions: {
zh: "结合最新财联社新闻与本地历史记忆生成研究报告。",
en: "Research recent CLS news with historical context from local memory.",
},
group: "plugins",
},
{
slug: "beam",
source: "benchmark/beam",
titles: { zh: "BEAM", en: "BEAM" },
descriptions: {
zh: "评测大规模记忆检索能力。",
en: "Evaluate memory retrieval at scale.",
},
group: "benchmarks",
},
{
slug: "longmemeval",
source: "benchmark/longmemeval",
titles: { zh: "LongMemEval", en: "LongMemEval" },
descriptions: {
zh: "评测跨会话长期记忆问答能力。",
en: "Evaluate long-term, cross-session memory question answering.",
},
group: "benchmarks",
},
{
slug: "pibench",
source: "benchmark/pibench",
titles: { zh: "π-Bench", en: "π-Bench" },
descriptions: {
zh: "评测带持久记忆的个人智能体。",
en: "Evaluate personal agents with persistent memory.",
},
group: "benchmarks",
},
{
slug: "toolmemory",
source: "benchmark/toolmemory",
titles: { zh: "Tool Memory / ExpG", en: "Tool Memory / ExpG" },
descriptions: {
zh: "通过经验驱动的自适应指导增强 Agent 工具使用。",
en: "Improve agent tool use through experience-driven adaptive guidance.",
},
group: "benchmarks",
},
];
const jobGroups = {
version: "system",
app_config: "system",
chat: "system",
health_check: "system",
status: "system",
help: "system",
auto_dream: "memory",
auto_memory: "memory",
auto_memory_cc: "memory",
auto_resource: "memory",
proactive: "memory",
traverse: "retrieval",
graph_snapshot: "retrieval",
reindex: "retrieval",
search: "retrieval",
node_search: "retrieval",
daily_list: "daily",
daily_reindex: "daily",
daily_write: "daily",
frontmatter_delete: "files",
frontmatter_read: "files",
frontmatter_update: "files",
stat: "files",
list: "files",
move: "files",
delete: "files",
read: "files",
load: "files",
read_image: "files",
write: "files",
save: "files",
edit: "files",
};
const sharedDocuments = [
{
id: "agents-guide",
path: "AGENTS.md",
sourcePath: "AGENTS.md",
titles: {
zh: "Agent 开发指南",
en: "Agent Development Guide",
},
description: "Repository contracts, lifecycle rules, safety boundaries, and validation.",
group: "development",
language: "shared",
},
];
async function markdownTitle(filePath) {
const source = await readFile(filePath, "utf8");
return source.match(/^#\s+(.+)$/m)?.[1]?.replace(/[`*_]/g, "") || path.basename(filePath, ".md");
function typeLabel(schema = {}) {
if (schema.oneOf) return schema.oneOf.map(typeLabel).join(" or ");
if (schema.type === "array") return `${typeLabel(schema.items || {})}[]`;
return schema.type || "any";
}
async function buildManifest() {
const documents = [
{
id: "readme-zh",
path: "README_ZH.md",
sourcePath: "README_ZH.md",
title: "ReMe 项目介绍",
description: "核心理念、快速开始、使用场景与社区入口。",
group: "overview",
language: "zh",
},
{
id: "readme-en",
path: "README.md",
sourcePath: "README.md",
title: "Introducing ReMe",
description: "Core ideas, quick start, use cases, and community resources.",
group: "overview",
language: "en",
},
];
function markdownCell(value) {
if (value === undefined) return "—";
const rendered = typeof value === "string" ? value : JSON.stringify(value);
return rendered
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll("|", "\\|")
.replaceAll("\n", " ");
}
for (const language of ["zh", "en"]) {
for (const topic of topicOrder) {
const sourcePath = `docs/${language}/${topic}.md`;
documents.push({
id: `${language}-${topic}`,
path: sourcePath,
sourcePath,
title: localizedTitles[topic]?.[language] || (await markdownTitle(path.join(repoDir, sourcePath))),
description: "",
group: groups[topic],
language,
});
}
function buildJobReference(config, language) {
const isZh = language === "zh";
const jobs = Object.entries(config.jobs || {}).filter(([, job]) => !["background", "cron"].includes(job.backend));
const sections = new Map();
for (const product of productDocuments) {
const filename = language === "zh" ? "README_ZH.md" : "README.md";
documents.push({
id: `${product.slug}-${language}`,
path: `${product.source}/${filename}`,
sourcePath: `${product.source}/${filename}`,
title: product.titles[language],
description: product.descriptions[language],
group: product.group,
language,
});
}
for (const [name, job] of jobs) {
const group = jobGroups[name] || "system";
if (!sections.has(group)) sections.set(group, []);
sections.get(group).push([name, job]);
}
return [...documents, ...sharedDocuments].sort(
(left, right) => navigationGroupOrder.indexOf(left.group) - navigationGroupOrder.indexOf(right.group),
);
const lines = [
"---",
`title: ${isZh ? "Job API 参考" : "Job API Reference"}`,
`description: ${isZh ? "从默认配置自动生成的可调用 Job、参数和服务边界。" : "Callable jobs, parameters, and service boundaries generated from the default configuration."}`,
"---",
"",
`# ${isZh ? "Job API 参考" : "Job API Reference"}`,
"",
isZh
? "本页从 `reme/config/default.yaml` 自动生成。它描述默认应用中的可调用 Job;插件和自定义配置可以增加、删除或覆盖 Job。运行 `reme help` 可查看当前服务的实际能力。"
: "This page is generated from `reme/config/default.yaml`. It describes callable jobs in the default application; plugins and custom configurations may add, remove, or override jobs. Run `reme help` to inspect the active service.",
"",
isZh
? "> 后台 Job 和 Cron Job 不通过服务暴露,因此不列入调用参考。"
: "> Background and cron jobs are not service-exposed and are omitted from the callable reference.",
"",
];
for (const [group, entries] of sections) {
lines.push(`## ${groupNames[language][group]}`, "");
for (const [name, job] of entries) {
const properties = job.parameters?.properties || {};
const required = new Set(job.parameters?.required || []);
lines.push(`### \`${name}\``, "", markdownCell(job.description || ""), "");
lines.push("```bash", `reme ${name}${Object.keys(properties).length ? " ..." : ""}`, "```", "");
if (!Object.keys(properties).length) {
lines.push(isZh ? "无参数。" : "No parameters.", "");
continue;
}
lines.push(
isZh
? "| 参数 | 类型 | 必填 | 默认值 | 说明 |"
: "| Parameter | Type | Required | Default | Description |",
"|---|---|---:|---|---|",
);
for (const [parameter, schema] of Object.entries(properties)) {
lines.push(
`| \`${parameter}\` | \`${markdownCell(typeLabel(schema))}\` | ${required.has(parameter) ? (isZh ? "是" : "yes") : (isZh ? "否" : "no")} | ${markdownCell(schema.default)} | ${markdownCell(schema.description || "—")} |`,
);
}
lines.push("");
}
}
return `${lines.join("\n")}\n`;
}
await rm(path.join(siteDir, ".generated"), { recursive: true, force: true });
await mkdir(outputDir, { recursive: true });
await cp(path.join(siteDir, "public", "favicon.svg"), path.join(siteDir, ".generated", "favicon.svg"));
await cp(path.join(siteDir, "public", "CNAME"), path.join(siteDir, ".generated", "CNAME"));
for (const file of ["README.md", "README_ZH.md", "AGENTS.md"]) {
await cp(path.join(repoDir, file), path.join(outputDir, file));
}
await cp(path.join(repoDir, "docs"), path.join(outputDir, "docs"), {
await cp(path.join(repoDir, "docs"), outputDir, {
recursive: true,
filter: (source) => path.basename(source) !== ".DS_Store",
filter: (source) => ![".DS_Store", "plans"].includes(path.basename(source)),
});
for (const product of productDocuments) {
await mkdir(path.join(outputDir, product.source), { recursive: true });
for (const filename of ["README.md", "README_ZH.md"]) {
await cp(path.join(repoDir, product.source, filename), path.join(outputDir, product.source, filename));
}
await cp(path.join(siteDir, "public", "CNAME"), path.join(outputDir, "public", "CNAME"));
await cp(path.join(repoDir, "docs/figure/reme-icon.svg"), path.join(outputDir, "public", "reme-icon.svg"));
await cp(path.join(repoDir, "docs/figure/reme-logo-fashion.svg"), path.join(outputDir, "public", "reme-logo.svg"));
const sourceMap = {};
for (const [destination, source] of externalDocuments) {
const destinationPath = path.join(outputDir, destination);
await mkdir(path.dirname(destinationPath), { recursive: true });
let content = await readFile(path.join(repoDir, source), "utf8");
for (const [from, to] of externalDocumentRewrites[source] || []) content = content.replaceAll(from, to);
await writeFile(destinationPath, content);
sourceMap[destination] = source;
}
await writeFile(
path.join(outputDir, "manifest.json"),
`${JSON.stringify({ documents: await buildManifest() }, null, 2)}\n`,
);
await cp(path.join(repoDir, "typescript/figures/dsh"), path.join(outputDir, "public/figures/dsh"), {
recursive: true,
});
for (const language of ["zh", "en"]) {
await cp(
path.join(repoDir, "benchmark/toolmemory/gitcha.png"),
path.join(outputDir, language, "benchmarks", "gitcha.png"),
);
}
const defaultConfig = parseYaml(await readFile(path.join(repoDir, "reme/config/default.yaml"), "utf8"));
for (const language of ["zh", "en"]) {
const destination = path.join(outputDir, language, "reference", "jobs.md");
await mkdir(path.dirname(destination), { recursive: true });
await writeFile(destination, buildJobReference(defaultConfig, language));
sourceMap[`${language}/reference/jobs.md`] = "reme/config/default.yaml";
}
await writeFile(path.join(outputDir, ".source-map.json"), `${JSON.stringify(sourceMap, null, 2)}\n`);

View file

@ -0,0 +1,92 @@
import assert from "node:assert/strict";
import { access, readFile, readdir } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
const siteDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const outputDir = path.join(siteDir, "dist");
async function collectFiles(directory, prefix = "") {
const files = [];
for (const entry of await readdir(directory, { withFileTypes: true })) {
const relativePath = path.posix.join(prefix, entry.name);
if (entry.isDirectory()) files.push(...await collectFiles(path.join(directory, entry.name), relativePath));
else files.push(relativePath);
}
return files;
}
function pageUrl(relativePath) {
if (relativePath === "index.html") return "/";
if (relativePath.endsWith("/index.html")) return `/${relativePath.slice(0, -"index.html".length)}`;
return `/${relativePath.slice(0, -".html".length)}`;
}
function routeExists(pathname, files) {
const relativePath = decodeURIComponent(pathname).replace(/^\/+/, "");
if (!relativePath) return files.has("index.html");
if (relativePath.endsWith("/")) return files.has(`${relativePath}index.html`);
return files.has(relativePath) || files.has(`${relativePath}.html`) || files.has(`${relativePath}/index.html`);
}
const requiredFiles = [
"index.html",
"404.html",
"CNAME",
"reme-icon.svg",
"reme-logo.svg",
"hashmap.json",
"sitemap.xml",
"llms.txt",
"llms-full.txt",
"zh/index.html",
"en/index.html",
"zh/configuration.html",
"en/configuration.html",
"zh/services.html",
"en/services.html",
"zh/reference/jobs.html",
"en/reference/jobs.html",
"zh/configuration/llms.txt",
"en/configuration/llms.txt",
];
for (const relativePath of requiredFiles) await access(path.join(outputDir, relativePath));
assert.equal((await readFile(path.join(outputDir, "CNAME"), "utf8")).trim(), "reme.agentscope.io");
const homepage = await readFile(path.join(outputDir, "index.html"), "utf8");
assert.ok(homepage.includes('href="/en/"'), "root language switch must link to /en/");
assert.ok(!homepage.includes('href="/en/ex"'), "root language switch must not produce /en/ex");
assert.ok(homepage.includes('"studio-en":"/en/workspace/studio"'), "legacy redirects must be embedded");
const sitemap = await readFile(path.join(outputDir, "sitemap.xml"), "utf8");
assert.ok(sitemap.includes("<lastmod>"), "sitemap must include canonical-source update times");
assert.ok(!sitemap.includes("<loc>https://reme.agentscope.io/</loc>"), "root redirect must not be indexed");
assert.ok(!sitemap.includes('hreflang="zh-CN" href="https://reme.agentscope.io/"'), "root must not duplicate zh-CN");
const ChineseConfiguration = await readFile(path.join(outputDir, "zh/configuration.html"), "utf8");
assert.match(ChineseConfiguration, /搜索文档/);
assert.match(ChineseConfiguration, /复制 Markdown/);
assert.match(ChineseConfiguration, /在 GitHub 查看源文件/);
const jobReference = await readFile(path.join(outputDir, "en/reference/jobs.html"), "utf8");
assert.match(jobReference, /Job API Reference/);
assert.match(jobReference, /auto_memory/);
const outputFiles = new Set(await collectFiles(outputDir));
const missingLinks = [];
for (const relativePath of [...outputFiles].filter((file) => file.endsWith(".html"))) {
const html = await readFile(path.join(outputDir, relativePath), "utf8");
const currentUrl = new URL(pageUrl(relativePath), "https://reme-docs.local");
for (const match of html.matchAll(/<a\b[^>]*\bhref="([^"]+)"/g)) {
const href = match[1].replaceAll("&amp;", "&");
if (href.startsWith("#")) continue;
const target = new URL(href, currentUrl);
if (target.origin !== currentUrl.origin) continue;
if (!routeExists(target.pathname, outputFiles)) missingLinks.push(`${relativePath}: ${href}`);
}
}
assert.deepEqual(missingLinks, [], `missing internal links:\n${missingLinks.join("\n")}`);
console.log(`Verified ${requiredFiles.length} documentation build artifacts.`);

View file

@ -1,458 +0,0 @@
import DOMPurify from "dompurify";
import { marked } from "marked";
import { stripMarkdownFrontmatter } from "./markdown.js";
import "./styles.css";
const baseUrl = import.meta.env.BASE_URL;
const repositoryUrl = "https://github.com/agentscope-ai/ReMe";
const copy = {
zh: {
docs: "文档",
home: "首页",
search: "搜索文档…",
noResults: "没有找到匹配的文档",
menu: "打开导航",
toc: "本页目录",
edit: "在 GitHub 查看源文件",
quickStart: "快速开始",
groups: {
overview: "项目介绍",
start: "开始使用",
fundamentals: "核心原理",
automation: "自动化能力",
concepts: "架构与场景",
integration: "Agent 集成",
workspace: "工作区",
plugins: "插件",
benchmarks: "评测",
development: "开发者规范",
},
},
en: {
docs: "Documentation",
home: "Home",
search: "Search documentation…",
noResults: "No matching documents",
menu: "Open navigation",
toc: "On this page",
edit: "View source on GitHub",
quickStart: "Quick start",
groups: {
overview: "Introduction",
start: "Get started",
fundamentals: "Fundamentals",
automation: "Automation",
concepts: "Architecture & scenarios",
integration: "Agent integration",
workspace: "Workspace",
plugins: "Plugins",
benchmarks: "Benchmarks",
development: "Development",
},
},
};
const state = {
language: localStorage.getItem("reme-docs-language") || "zh",
documents: [],
activeDocument: null,
query: "",
};
const homeCopy = {
zh: {
eyebrow: "LOCAL-FIRST · FILE-NATIVE",
title: "让 Agent 真正记住,\n也让记忆始终属于你。",
description: "ReMe 将对话和资料沉淀为可读、可编辑、可检索、相互链接的 Markdown,并提供从工作区管理到主动研究的一整套工具。",
start: "快速开始",
project: "了解 ReMe",
explore: "按目标探索",
exploreDescription: "选择你现在想完成的事情。每个入口都直接连接到对应的完整文档。",
cards: [
{ id: "studio-zh", icon: "◫", label: "管理记忆", title: "ReMe 工作台", description: "在本地 Web 工作区中浏览、编辑、搜索记忆,并探索 wikilink 图谱。", tone: "mint" },
{ id: "daily-paper-zh", icon: "◌", label: "发现与分析", title: "每日论文插件", description: "从论文榜单筛选值得阅读的工作,解析 PDF,并生成笔记与五分钟简报。", tone: "blue" },
{ id: "auto-fin-zh", icon: "↗", label: "主题研究", title: "Auto Fin 插件", description: "连接最新财联社新闻和本地历史记忆,生成可追溯的研究报告。", tone: "amber" },
],
benchmark: "验证记忆能力",
benchmarkDescription: "从检索规模、跨会话问答、个人智能体到工具经验,查看 ReMe 的四套评测与复现实验。",
benchmarkAction: "从 BEAM 开始",
},
en: {
eyebrow: "LOCAL-FIRST · FILE-NATIVE",
title: "Memory that works for agents.\nFiles that remain yours.",
description: "ReMe turns conversations and resources into readable, editable, searchable, interconnected Markdown—with tools spanning workspace management and proactive research.",
start: "Quick start",
project: "Meet ReMe",
explore: "Explore by goal",
exploreDescription: "Start with what you want to accomplish. Every entry opens the complete guide.",
cards: [
{ id: "studio-en", icon: "◫", label: "Manage memory", title: "ReMe Studio", description: "Browse, edit, and search memory in a local web workspace, then explore its wikilink graph.", tone: "mint" },
{ id: "daily-paper-en", icon: "◌", label: "Discover & analyze", title: "Daily Paper Plugin", description: "Select useful papers from rankings, analyze PDFs, and create notes plus a five-minute brief.", tone: "blue" },
{ id: "auto-fin-en", icon: "↗", label: "Research topics", title: "Auto Fin Plugin", description: "Connect recent CLS news with historical local memory to produce traceable research reports.", tone: "amber" },
],
benchmark: "Validate memory systems",
benchmarkDescription: "Explore four reproducible evaluations covering retrieval scale, cross-session QA, personal agents, and tool-use experience.",
benchmarkAction: "Start with BEAM",
},
};
const app = document.querySelector("#app");
app.innerHTML = `
<header class="topbar">
<a class="brand" href="${baseUrl}" aria-label="ReMe documentation home">
<span class="brand-mark">R</span>
<span>ReMe</span>
<span class="brand-divider"></span>
<span class="brand-section" data-copy="docs"></span>
</a>
<nav class="top-actions" aria-label="Global navigation">
<div class="language-switch" role="group" aria-label="Language">
<button type="button" data-language="zh">中</button>
<button type="button" data-language="en">EN</button>
</div>
<a class="quick-start-link" href="?doc=zh-quick_start" data-doc="zh-quick_start" data-copy="quickStart"></a>
<a class="github-link" href="${repositoryUrl}" target="_blank" rel="noreferrer">GitHub ↗</a>
<button class="menu-button" type="button" aria-expanded="false" data-action="menu"></button>
</nav>
</header>
<div class="docs-shell">
<aside class="sidebar" aria-label="Documentation navigation">
<label class="search-box">
<span aria-hidden="true">⌕</span>
<input type="search" autocomplete="off" />
<kbd>⌘K</kbd>
</label>
<nav class="document-nav"></nav>
<div class="sidebar-footer">
<span class="status-dot"></span>
Local-first · File-native
</div>
</aside>
<main class="article-wrap">
<article class="article"><div class="loading-line"></div></article>
</main>
<aside class="toc-panel"><nav class="toc"></nav></aside>
</div>
<button class="sidebar-backdrop" type="button" aria-label="Close navigation"></button>
`;
const sidebar = app.querySelector(".sidebar");
const docsShell = app.querySelector(".docs-shell");
const documentNav = app.querySelector(".document-nav");
const article = app.querySelector(".article");
const toc = app.querySelector(".toc");
const searchInput = app.querySelector("input[type='search']");
const menuButton = app.querySelector(".menu-button");
const backdrop = app.querySelector(".sidebar-backdrop");
function slugify(value) {
return value
.toLowerCase()
.trim()
.replace(/<[^>]+>/g, "")
.replace(/[^\p{Letter}\p{Number}]+/gu, "-")
.replace(/^-|-$/g, "");
}
function resolveDocumentPath(currentPath, target) {
const cleanTarget = target.split("#")[0].split("?")[0];
const currentParts = currentPath.split("/");
currentParts.pop();
for (const part of cleanTarget.split("/")) {
if (!part || part === ".") continue;
if (part === "..") currentParts.pop();
else currentParts.push(part);
}
return currentParts.join("/");
}
function configureMarkdown(document) {
const renderer = new marked.Renderer();
const headingIds = new Map();
renderer.heading = ({ tokens, depth }) => {
const text = tokens.map((token) => token.text || token.raw || "").join("");
const baseSlug = slugify(text) || "section";
const count = headingIds.get(baseSlug) || 0;
headingIds.set(baseSlug, count + 1);
const id = count ? `${baseSlug}-${count + 1}` : baseSlug;
return `<h${depth} id="${id}">${text}</h${depth}>`;
};
renderer.image = ({ href, title, text }) => {
const url = /^(https?:|data:)/.test(href)
? href
: `${baseUrl}content/${resolveDocumentPath(document.path, href)}`;
const titleAttribute = title ? ` title="${title}"` : "";
return `<img src="${url}" alt="${text}" loading="lazy"${titleAttribute}>`;
};
renderer.link = ({ href, title, tokens }) => {
const label = tokens.map((token) => token.text || token.raw || "").join("");
const titleAttribute = title ? ` title="${title}"` : "";
if (href.startsWith("#")) return `<a href="${href}"${titleAttribute}>${label}</a>`;
if (!/^(https?:|mailto:)/.test(href)) {
const resolved = resolveDocumentPath(document.path, href);
const localDocument = state.documents.find((item) => item.path === resolved);
if (localDocument) return `<a href="?doc=${localDocument.id}" data-doc="${localDocument.id}">${label}</a>`;
return `<a href="${repositoryUrl}/blob/main/${resolved}" target="_blank" rel="noreferrer">${label}</a>`;
}
return `<a href="${href}" target="_blank" rel="noreferrer"${titleAttribute}>${label}</a>`;
};
marked.use({ renderer, gfm: true, breaks: false });
}
function availableDocuments() {
return state.documents.filter(
(document) => document.language === state.language || document.language === "shared",
);
}
function documentTitle(document) {
return document.titles?.[state.language] || document.title;
}
function renderChrome() {
const labels = copy[state.language];
app.querySelector("[data-copy='docs']").textContent = labels.docs;
const quickStartLink = app.querySelector("[data-copy='quickStart']");
quickStartLink.textContent = `${labels.quickStart} →`;
quickStartLink.href = `?doc=${state.language}-quick_start`;
quickStartLink.dataset.doc = `${state.language}-quick_start`;
searchInput.placeholder = labels.search;
menuButton.textContent = labels.menu;
document.documentElement.lang = state.language === "zh" ? "zh-CN" : "en";
app.querySelectorAll("[data-language]").forEach((button) => {
button.classList.toggle("active", button.dataset.language === state.language);
});
}
function renderNavigation() {
const labels = copy[state.language];
const query = state.query.trim().toLocaleLowerCase();
const filtered = availableDocuments().filter((document) =>
`${documentTitle(document)} ${document.title || ""} ${document.description}`.toLocaleLowerCase().includes(query),
);
const groups = [...new Set(filtered.map((document) => document.group))];
if (!filtered.length) {
documentNav.innerHTML = `
<a href="${baseUrl}" data-home class="home-link ${state.activeDocument ? "" : "active"}">${labels.home}</a>
<p class="empty-state">${labels.noResults}</p>
`;
return;
}
documentNav.innerHTML = `
<a href="${baseUrl}" data-home class="home-link ${state.activeDocument ? "" : "active"}">${labels.home}</a>
` + groups
.map(
(group) => `
<section class="nav-group">
<h2>${labels.groups[group]}</h2>
${filtered
.filter((document) => document.group === group)
.map(
(document) => `
<a href="?doc=${document.id}" data-doc="${document.id}" class="${state.activeDocument?.id === document.id ? "active" : ""}">
<span>${documentTitle(document)}</span>
</a>`,
)
.join("")}
</section>`,
)
.join("");
}
function renderHome(pushHistory = true) {
const labels = homeCopy[state.language];
state.activeDocument = null;
docsShell.classList.add("home-view");
article.dataset.group = "home";
renderNavigation();
article.innerHTML = `
<section class="home-hero">
<p class="home-eyebrow">${labels.eyebrow}</p>
<h1>${labels.title.replace("\n", "<br>")}</h1>
<p class="home-lead">${labels.description}</p>
<div class="home-actions">
<a href="${baseUrl}?doc=${state.language}-quick_start" class="primary-action">${labels.start} →</a>
<a href="${baseUrl}?doc=readme-${state.language}" class="secondary-action">${labels.project}</a>
</div>
</section>
<section class="home-explore">
<p class="section-kicker">01 / PRODUCT & PLUGINS</p>
<h2>${labels.explore}</h2>
<p class="section-lead">${labels.exploreDescription}</p>
<div class="feature-grid">
${labels.cards.map((card) => `
<a href="${baseUrl}?doc=${card.id}" class="feature-card ${card.tone}">
<span class="feature-icon">${card.icon}</span>
<span class="feature-label">${card.label}</span>
<strong>${card.title}</strong>
<span class="feature-description">${card.description}</span>
<span class="feature-arrow">→</span>
</a>`).join("")}
</div>
</section>
<section class="benchmark-callout">
<div>
<p class="section-kicker">02 / BENCHMARKS</p>
<h2>${labels.benchmark}</h2>
<p>${labels.benchmarkDescription}</p>
</div>
<a href="${baseUrl}?doc=beam-${state.language}">${labels.benchmarkAction} →</a>
</section>
`;
toc.innerHTML = "";
closeMenu();
if (pushHistory) history.pushState({ home: true }, "", baseUrl);
window.scrollTo({ top: 0, behavior: "instant" });
}
function renderToc() {
const headings = [...article.querySelectorAll("h2, h3")];
if (!headings.length) {
toc.innerHTML = "";
return;
}
toc.innerHTML = `
<h2>${copy[state.language].toc}</h2>
${headings
.map(
(heading) => `<a class="toc-${heading.tagName.toLowerCase()}" href="#${heading.id}">${heading.childNodes[0]?.textContent || heading.textContent}</a>`,
)
.join("")}
`;
}
function rewriteRenderedUrls(document) {
article.querySelectorAll("img[src]").forEach((image) => {
const source = image.getAttribute("src");
if (source && !/^(https?:|data:|\/)/.test(source)) {
image.src = `${baseUrl}content/${resolveDocumentPath(document.path, source)}`;
}
});
article.querySelectorAll("a[href]").forEach((link) => {
const href = link.getAttribute("href");
if (!href || /^(https?:|mailto:|#|\/)/.test(href) || link.dataset.doc) return;
const resolved = resolveDocumentPath(document.path, href);
const localDocument = state.documents.find((item) => item.path === resolved);
if (localDocument) {
link.href = `?doc=${localDocument.id}`;
link.dataset.doc = localDocument.id;
link.removeAttribute("target");
return;
}
link.href = `${repositoryUrl}/blob/main/${resolved}`;
link.target = "_blank";
link.rel = "noreferrer";
});
}
async function openDocument(id, pushHistory = true) {
const fallbackId = state.language === "zh" ? "readme-zh" : "readme-en";
const document = state.documents.find((item) => item.id === id) || state.documents.find((item) => item.id === fallbackId);
if (document.language !== "shared" && document.language !== state.language) {
state.language = document.language;
localStorage.setItem("reme-docs-language", state.language);
renderChrome();
}
state.activeDocument = document;
docsShell.classList.remove("home-view");
article.dataset.group = document.group;
renderNavigation();
article.innerHTML = `<div class="loading-line"></div>`;
const response = await fetch(`${baseUrl}content/${document.path}`);
if (!response.ok) throw new Error(`Unable to load ${document.path}`);
configureMarkdown(document);
const markdown = stripMarkdownFrontmatter(await response.text());
const body = DOMPurify.sanitize(await marked.parse(markdown), {
ADD_ATTR: ["target"],
});
article.innerHTML = `
<div class="article-meta">
<span>${copy[state.language].groups[document.group]}</span>
<span>·</span>
<span>${document.sourcePath}</span>
</div>
<div class="markdown-body">${body}</div>
<footer class="article-footer">
<a href="${repositoryUrl}/blob/main/${document.sourcePath}" target="_blank" rel="noreferrer">${copy[state.language].edit} ↗</a>
</footer>
`;
rewriteRenderedUrls(document);
renderToc();
closeMenu();
if (pushHistory) history.pushState({ doc: document.id }, "", `?doc=${document.id}`);
window.scrollTo({ top: 0, behavior: "instant" });
}
function closeMenu() {
sidebar.classList.remove("open");
backdrop.classList.remove("visible");
menuButton.setAttribute("aria-expanded", "false");
}
function toggleMenu() {
const open = !sidebar.classList.contains("open");
sidebar.classList.toggle("open", open);
backdrop.classList.toggle("visible", open);
menuButton.setAttribute("aria-expanded", String(open));
}
app.addEventListener("click", (event) => {
const homeLink = event.target.closest("[data-home]");
if (homeLink) {
event.preventDefault();
renderHome();
return;
}
const documentLink = event.target.closest("[data-doc]");
if (documentLink) {
event.preventDefault();
openDocument(documentLink.dataset.doc);
}
if (event.target.closest("[data-action='menu']")) toggleMenu();
const languageButton = event.target.closest("[data-language]");
if (languageButton && languageButton.dataset.language !== state.language) {
state.language = languageButton.dataset.language;
localStorage.setItem("reme-docs-language", state.language);
state.query = "";
searchInput.value = "";
renderChrome();
renderHome();
}
});
searchInput.addEventListener("input", () => {
state.query = searchInput.value;
renderNavigation();
});
document.addEventListener("keydown", (event) => {
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
event.preventDefault();
searchInput.focus();
}
if (event.key === "Escape") closeMenu();
});
backdrop.addEventListener("click", closeMenu);
window.addEventListener("popstate", (event) => {
const id = event.state?.doc || new URLSearchParams(location.search).get("doc");
if (id) openDocument(id, false);
else renderHome(false);
});
const manifest = await fetch(`${baseUrl}content/manifest.json`).then((response) => response.json());
state.documents = manifest.documents;
renderChrome();
const initialDocument = new URLSearchParams(location.search).get("doc");
if (initialDocument) await openDocument(initialDocument, false);
else renderHome(false);

View file

@ -1,6 +0,0 @@
const FRONTMATTER_PATTERN = /^\uFEFF?---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/;
/** Remove a leading YAML frontmatter block before rendering Markdown. */
export function stripMarkdownFrontmatter(markdown) {
return markdown.replace(FRONTMATTER_PATTERN, "");
}

View file

@ -1,200 +0,0 @@
:root {
color: #17221d;
background: #f4f7f5;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-synthesis: none;
text-rendering: optimizeLegibility;
--ink: #17221d;
--muted: #65716a;
--line: #dce5e0;
--paper: #ffffff;
--green: #087f6a;
--blue: #3156d9;
--green-soft: #e5f5ef;
--code: #f3f5f2;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; scroll-padding-top: 92px; }
body {
margin: 0;
min-width: 320px;
background:
radial-gradient(circle at 8% 9%, rgba(27, 193, 164, 0.06), transparent 26rem),
#f4f7f5;
}
button, input { font: inherit; }
a { color: inherit; }
.topbar {
position: fixed;
inset: 0 0 auto 0;
z-index: 30;
height: 66px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 28px;
border-bottom: 1px solid rgba(213, 225, 219, 0.85);
background: rgba(250, 252, 251, 0.88);
backdrop-filter: blur(18px) saturate(140%);
}
.brand { display: flex; align-items: center; gap: 11px; color: var(--ink); text-decoration: none; font-weight: 760; }
.brand-mark {
display: grid;
place-items: center;
width: 34px;
height: 34px;
border-radius: 11px;
color: white;
background: linear-gradient(145deg, #19c9b0, #3156d9 82%);
box-shadow: 0 7px 18px rgba(24, 123, 114, 0.2);
font-family: Georgia, serif;
font-size: 21px;
}
.brand-divider { width: 1px; height: 20px; background: var(--line); margin-left: 2px; }
.brand-section { color: var(--muted); font-weight: 520; }
.top-actions { display: flex; align-items: center; gap: 18px; }
.github-link, .quick-start-link { color: #37443d; text-decoration: none; font-size: 13px; font-weight: 650; }
.github-link:hover, .quick-start-link:hover { color: var(--green); }
.quick-start-link { padding: 8px 12px; border: 1px solid #d7e5df; border-radius: 9px; background: rgba(255, 255, 255, 0.72); }
.language-switch { display: flex; padding: 3px; border: 1px solid var(--line); border-radius: 9px; background: #f5f7f4; }
.language-switch button { padding: 5px 9px; border: 0; border-radius: 6px; color: var(--muted); background: transparent; cursor: pointer; font-size: 12px; font-weight: 700; }
.language-switch button.active { color: var(--ink); background: white; box-shadow: 0 1px 3px rgba(20, 40, 30, 0.1); }
.menu-button { display: none; border: 1px solid var(--line); border-radius: 8px; background: white; padding: 7px 10px; cursor: pointer; }
.docs-shell { display: grid; grid-template-columns: 276px minmax(0, 1fr) 224px; max-width: 1540px; min-height: 100vh; margin: 0 auto; padding-top: 66px; }
.docs-shell.home-view { grid-template-columns: 276px minmax(0, 1fr); }
.docs-shell.home-view .toc-panel { display: none; }
.sidebar { position: sticky; top: 66px; height: calc(100vh - 66px); padding: 25px 20px 18px; overflow-y: auto; border-right: 1px solid var(--line); background: rgba(247, 250, 248, 0.78); }
.search-box { display: flex; align-items: center; gap: 8px; height: 41px; padding: 0 11px; border: 1px solid #d8e3dd; border-radius: 11px; color: #7a857e; background: rgba(255, 255, 255, 0.84); box-shadow: 0 5px 18px rgba(29, 65, 48, 0.035); }
.search-box:focus-within { border-color: #78aa8e; box-shadow: 0 0 0 3px rgba(22, 120, 76, 0.1); }
.search-box input { width: 100%; border: 0; outline: 0; color: var(--ink); background: transparent; font-size: 13px; }
.search-box kbd { padding: 2px 5px; border: 1px solid var(--line); border-radius: 4px; background: #f7f8f6; font-size: 10px; }
.document-nav { padding: 12px 0 52px; }
.home-link { position: relative; display: block; margin-top: 9px; padding: 9px 10px 9px 13px; border-radius: 8px; color: #536058; text-decoration: none; font-size: 13px; }
.home-link:hover { color: var(--ink); background: #f0f3ef; }
.home-link.active { color: #086b5a; background: linear-gradient(90deg, #dff3ec, #eaf6f2); font-weight: 700; }
.nav-group { margin-top: 21px; }
.nav-group h2 { margin: 0 10px 7px; color: #8a948e; font-size: 10px; font-weight: 800; letter-spacing: 0.11em; text-transform: uppercase; }
.nav-group a { position: relative; display: block; padding: 7px 10px 7px 13px; border-radius: 8px; color: #536058; text-decoration: none; font-size: 13px; line-height: 1.4; }
.nav-group a:hover { color: var(--ink); background: #f0f3ef; }
.nav-group a.active { color: #086b5a; background: linear-gradient(90deg, #dff3ec, #eaf6f2); font-weight: 700; }
.nav-group a.active::before { position: absolute; top: 9px; bottom: 9px; left: 0; width: 3px; border-radius: 3px; background: linear-gradient(#17b79d, #3470d8); content: ""; }
.empty-state { padding: 24px 10px; color: var(--muted); font-size: 13px; }
.sidebar-footer { position: sticky; bottom: -18px; display: flex; align-items: center; gap: 8px; margin: 0 -20px; padding: 14px 22px 18px; border-top: 1px solid var(--line); color: #7c8880; background: #f7faf8; font: 600 10px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.04em; text-transform: uppercase; }
.status-dot { width: 6px; height: 6px; border-radius: 50%; background: #29a869; box-shadow: 0 0 0 3px #dff3e8; }
.article-wrap { min-width: 0; padding: 58px clamp(32px, 5.8vw, 86px) 100px; background: rgba(255, 255, 255, 0.94); }
.article { width: 100%; max-width: 820px; margin: 0 auto; }
.article[data-group="home"] { max-width: 980px; }
.home-hero { padding: 34px 0 76px; }
.home-eyebrow, .section-kicker { margin: 0 0 17px; color: #12806d; font: 750 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.13em; }
.home-hero h1 { margin: 0; color: #102019; font-size: clamp(46px, 6.3vw, 76px); line-height: 1.15; letter-spacing: -0.055em; }
.home-lead { margin: 27px 0 0; color: #526159; font-size: 18px; line-height: 1.72; }
.home-actions { display: flex; flex-wrap: wrap; gap: 11px; margin-top: 31px; }
.home-actions a { padding: 11px 17px; border-radius: 10px; text-decoration: none; font-size: 14px; font-weight: 720; }
.primary-action { color: white; background: #087f6a; box-shadow: 0 8px 22px rgba(8, 127, 106, 0.2); }
.secondary-action { border: 1px solid #d5e2dc; color: #34443c; background: white; }
.home-explore { padding-top: 58px; border-top: 1px solid var(--line); }
.home-explore h2, .benchmark-callout h2 { margin: 0; color: #15251d; font-size: 30px; letter-spacing: -0.025em; }
.section-lead { max-width: 620px; margin: 10px 0 25px; color: var(--muted); line-height: 1.65; }
.feature-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.feature-card { position: relative; display: flex; min-height: 260px; flex-direction: column; padding: 23px; overflow: hidden; border: 1px solid #dce8e2; border-radius: 16px; color: var(--ink); background: linear-gradient(155deg, #fff, #f4faf7); text-decoration: none; transition: transform 160ms ease, box-shadow 160ms ease; }
.feature-card:hover { transform: translateY(-3px); box-shadow: 0 16px 34px rgba(29, 68, 49, 0.1); }
.feature-card.blue { background: linear-gradient(155deg, #fff, #f1f5ff); }
.feature-card.amber { background: linear-gradient(155deg, #fff, #fbf7ec); }
.feature-icon { display: grid; place-items: center; width: 42px; height: 42px; margin-bottom: 32px; border-radius: 12px; color: #08705e; background: #dbf2ea; font-size: 22px; }
.blue .feature-icon { color: #3156b8; background: #e5ebff; }
.amber .feature-icon { color: #9b6818; background: #f8eac8; }
.feature-label { margin-bottom: 7px; color: #718078; font: 700 10px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.08em; text-transform: uppercase; }
.feature-card strong { font-size: 20px; }
.feature-description { margin-top: 10px; color: #66736c; font-size: 13px; line-height: 1.6; }
.feature-arrow { position: absolute; right: 22px; bottom: 18px; color: #6a7870; font-size: 19px; }
.benchmark-callout { display: flex; align-items: end; justify-content: space-between; gap: 35px; margin-top: 62px; padding: 36px; border-radius: 17px; color: white; background: linear-gradient(125deg, #142a22, #1c473b); }
.benchmark-callout .section-kicker { color: #74d4bb; }
.benchmark-callout h2 { color: white; }
.benchmark-callout p:not(.section-kicker) { max-width: 610px; margin: 10px 0 0; color: #c3d4cd; line-height: 1.65; }
.benchmark-callout > a { flex: none; padding: 10px 14px; border: 1px solid rgba(255,255,255,.25); border-radius: 9px; color: white; text-decoration: none; font-size: 13px; font-weight: 700; }
.article-meta { display: flex; gap: 8px; margin-bottom: 22px; color: #7d8d84; font: 600 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; }
.article-meta span:first-child { padding: 3px 8px; border-radius: 999px; color: #08705e; background: #e6f5ef; }
.markdown-body { color: #27332d; font-size: 16px; line-height: 1.78; }
.markdown-body > :first-child { margin-top: 0; }
.markdown-body h1 { margin: 0 0 30px; color: #112019; font-size: clamp(36px, 5vw, 54px); line-height: 1.06; letter-spacing: -0.04em; }
.markdown-body h2 { margin: 58px 0 18px; padding-top: 4px; color: #14241c; font-size: 27px; line-height: 1.25; letter-spacing: -0.02em; }
.markdown-body h3 { margin: 35px 0 12px; color: #1f3027; font-size: 20px; line-height: 1.35; }
.markdown-body p, .markdown-body ul, .markdown-body ol { margin: 14px 0; }
.markdown-body li { margin: 5px 0; }
.markdown-body a { color: var(--green); text-decoration-color: #9cc8ae; text-underline-offset: 3px; }
.markdown-body a:hover { text-decoration-color: var(--green); }
.markdown-body img { display: block; max-width: 100%; height: auto; margin: 30px auto; border-radius: 14px; }
/* README badges are linked images. Keep them in a compact row instead of applying the figure layout above. */
.article[data-group="overview"] .markdown-body > p:has(> a > img[src*="img.shields.io"]) {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 7px;
max-width: 690px;
margin: 20px auto;
}
.article[data-group="overview"] .markdown-body > p > a > img[src*="img.shields.io"] {
display: inline-block;
width: auto;
max-height: 24px;
margin: 0;
border-radius: 4px;
vertical-align: middle;
}
.markdown-body p[align="center"] { text-align: center; }
.article[data-group="overview"] .markdown-body > p:first-child img { max-width: min(470px, 72%); margin-top: 8px; filter: drop-shadow(0 18px 25px rgba(44, 85, 164, 0.1)); }
.markdown-body code { padding: 2px 5px; border-radius: 5px; background: var(--code); color: #315844; font: 0.88em/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.markdown-body pre { margin: 24px 0; padding: 19px 21px; overflow-x: auto; border: 1px solid #dce5e0; border-radius: 12px; background: linear-gradient(145deg, #f5f8f6, #f8faf9); box-shadow: inset 3px 0 0 #7bd0b6; }
.markdown-body pre code { padding: 0; color: #263b30; background: none; font-size: 13px; }
.markdown-body blockquote { margin: 24px 0; padding: 4px 20px; border-left: 3px solid #68aa85; color: #53635a; background: #f5faf7; }
.markdown-body table { display: block; width: 100%; margin: 24px 0; overflow-x: auto; border-collapse: collapse; font-size: 14px; }
.markdown-body th, .markdown-body td { padding: 10px 13px; border: 1px solid var(--line); text-align: left; }
.markdown-body th { background: #f4f6f3; }
.markdown-body hr { margin: 44px 0; border: 0; border-top: 1px solid var(--line); }
.article-footer { margin-top: 70px; padding-top: 20px; border-top: 1px solid var(--line); }
.article-footer a { color: var(--muted); text-decoration: none; font-size: 13px; }
.article-footer a:hover { color: var(--green); }
.loading-line { width: 55%; height: 12px; margin-top: 40px; border-radius: 9px; background: linear-gradient(90deg, #edf0ec, #f8faf7, #edf0ec); background-size: 200% 100%; animation: loading 1.2s infinite; }
@keyframes loading { to { background-position: -200% 0; } }
.toc-panel { position: sticky; top: 66px; height: calc(100vh - 66px); padding: 58px 24px; overflow-y: auto; border-left: 1px solid #e9efeb; background: rgba(252, 253, 252, 0.92); }
.toc h2 { margin: 0 0 12px; color: #8a948e; font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; }
.toc a { display: block; padding: 5px 0; color: #7a857e; text-decoration: none; font-size: 12px; line-height: 1.45; }
.toc a:hover { color: var(--green); }
.toc .toc-h3 { padding-left: 12px; }
.sidebar-backdrop { display: none; }
@media (max-width: 1120px) {
.docs-shell { grid-template-columns: 250px minmax(0, 1fr); }
.toc-panel { display: none; }
}
@media (max-width: 760px) {
.topbar { height: 60px; padding: 0 16px; }
.brand-section, .brand-divider, .github-link { display: none; }
.quick-start-link { font-size: 12px; }
.menu-button { display: block; max-width: 112px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.top-actions { gap: 9px; }
.docs-shell { display: block; padding-top: 60px; }
.sidebar { position: fixed; z-index: 25; top: 60px; bottom: 0; left: 0; width: min(310px, 86vw); height: auto; transform: translateX(-105%); transition: transform 180ms ease; box-shadow: 16px 0 35px rgba(20, 40, 30, 0.12); }
.sidebar.open { transform: translateX(0); }
.sidebar-backdrop { position: fixed; z-index: 20; inset: 60px 0 0; width: 100%; border: 0; background: rgba(18, 30, 23, 0.35); }
.sidebar-backdrop.visible { display: block; }
.article-wrap { padding: 38px 20px 72px; }
.article-meta { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.markdown-body { font-size: 15px; }
.markdown-body h1 { font-size: 34px; }
.markdown-body h2 { margin-top: 46px; font-size: 24px; }
.article[data-group="overview"] .markdown-body > p:first-child img { max-width: 84%; }
.home-hero { padding: 20px 0 54px; }
.home-hero h1 { font-size: 43px; }
.home-lead { font-size: 16px; }
.feature-grid { grid-template-columns: 1fr; }
.feature-card { min-height: 225px; }
.benchmark-callout { align-items: flex-start; flex-direction: column; padding: 28px 24px; }
}

View file

@ -1,23 +0,0 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
const manifestUrl = new URL("../.generated/content/manifest.json", import.meta.url);
test("omits retired Agent documents and places Agent integration after getting started", async () => {
const manifest = JSON.parse(await readFile(manifestUrl, "utf8"));
const documents = manifest.documents;
const groups = [...new Set(documents.map((document) => document.group))];
assert.equal(documents.some((document) => document.id === "reme-memory-skill"), false);
assert.equal(documents.some((document) => document.sourcePath.endsWith("agent_integration_plan.md")), false);
assert.deepEqual(
documents
.filter((document) => document.group === "plugins")
.map((document) => document.title || document.titles?.en),
["每日论文插件", "Auto Fin 插件", "Daily Paper Plugin", "Auto Fin Plugin"],
);
assert.equal(documents.some((document) => document.group === "cookbooks"), false);
assert.ok(groups.indexOf("integration") > groups.indexOf("start"));
assert.ok(groups.indexOf("integration") < groups.indexOf("fundamentals"));
});

View file

@ -0,0 +1,109 @@
import assert from "node:assert/strict";
import { access, readFile } from "node:fs/promises";
import path from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";
import { parse as parseYaml } from "yaml";
import { legacyRoutes } from "../../docs/.vitepress/legacy-routes.mjs";
const siteDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const repoDir = path.resolve(siteDir, "..");
const generatedDir = path.join(siteDir, ".generated", "site");
test("generates every required bilingual guide", async () => {
const names = [
"configuration.md",
"services.md",
"operations.md",
"integrations.md",
"plugin_development.md",
"faq.md",
"reference/cli.md",
"reference/jobs.md",
];
for (const language of ["zh", "en"]) {
for (const name of names) await access(path.join(generatedDir, language, name));
}
await access(path.join(generatedDir, "zh/integrations/claude-code.md"));
await access(path.join(generatedDir, "en/integrations/claude-code.md"));
await access(path.join(generatedDir, "zh/integrations/hermes.md"));
await access(path.join(generatedDir, "en/integrations/hermes.md"));
await access(path.join(generatedDir, "zh/integrations/dsh.md"));
await access(path.join(generatedDir, "en/integrations/dsh.md"));
await access(path.join(generatedDir, "zh/integrations/openclaw.md"));
await access(path.join(generatedDir, "en/integrations/openclaw.md"));
await access(path.join(generatedDir, "public/figures/dsh/reme-status-overview.png"));
});
test("maps mirrored pages back to their canonical repository sources", async () => {
const sourceMap = JSON.parse(await readFile(path.join(generatedDir, ".source-map.json"), "utf8"));
assert.equal(sourceMap["zh/integrations/typescript.md"], "typescript/README_ZH.md");
assert.equal(sourceMap["en/integrations/dsh.md"], "typescript/docs/dsh.md");
assert.equal(sourceMap["zh/integrations/openclaw.md"], "typescript/docs/openclaw.zh-CN.md");
assert.equal(sourceMap["en/integrations/claude-code.md"], "integrations/claude_code/README.md");
assert.equal(sourceMap["en/integrations/hermes.md"], "integrations/hermes_agent/README.md");
assert.equal(sourceMap["en/workspace/studio.md"], "reme_studio/README.md");
assert.equal(sourceMap["zh/plugins/lme.md"], "plugins/lme/README_ZH.md");
assert.equal(sourceMap["en/reference/jobs.md"], "reme/config/default.yaml");
});
test("publishes portable and accurate DSH instructions", async () => {
const english = await readFile(path.join(generatedDir, "en/integrations/dsh.md"), "utf8");
const chinese = await readFile(path.join(generatedDir, "zh/integrations/dsh.md"), "utf8");
assert.doesNotMatch(english, /\/Users\//);
assert.doesNotMatch(chinese, /\/Users\//);
assert.match(english, /runtime counters refresh every 5 seconds/);
assert.match(chinese, /每 5 秒仅刷新 DSH 插件的运行时计数/);
});
test("generates the callable Job reference from default.yaml", async () => {
const config = parseYaml(await readFile(path.join(repoDir, "reme/config/default.yaml"), "utf8"));
const callableJobs = Object.entries(config.jobs)
.filter(([, job]) => !["background", "cron"].includes(job.backend))
.map(([name]) => name);
for (const language of ["zh", "en"]) {
const reference = await readFile(path.join(generatedDir, language, "reference", "jobs.md"), "utf8");
for (const job of callableJobs) assert.ok(reference.includes(`### ${"`"}${job}${"`"}`), job);
}
});
test("keeps generated content disposable and excludes internal plans", async () => {
await assert.rejects(access(path.join(generatedDir, "plans")));
await access(path.join(generatedDir, ".vitepress", "config.mts"));
await access(path.join(generatedDir, "public", "reme-icon.svg"));
await access(path.join(generatedDir, "public", "reme-logo.svg"));
assert.equal(
(await readFile(path.join(generatedDir, "public", "CNAME"), "utf8")).trim(),
"reme.agentscope.io",
);
});
test("maps every legacy query-string document ID to a generated page", async () => {
assert.equal(Object.keys(legacyRoutes).length, 45);
assert.equal(legacyRoutes["studio-en"], "/en/workspace/studio");
assert.equal(legacyRoutes["en-quick_start"], "/en/quick_start");
assert.equal(legacyRoutes["agents-guide"], "https://github.com/agentscope-ai/ReMe/blob/main/AGENTS.md");
for (const [id, route] of Object.entries(legacyRoutes)) {
if (route.startsWith("https://")) continue;
assert.match(route, /^\/(?:zh|en)\//, id);
const relative = route.endsWith("/") ? `${route.slice(1)}index.md` : `${route.slice(1)}.md`;
await access(path.join(generatedDir, relative));
}
});
test("tracks every generated input in documentation CI and deployment", async () => {
const requiredPaths = [
"reme/config/default.yaml",
"integrations/claude_code/README.md",
"integrations/hermes_agent/README.md",
"typescript/docs/**",
"typescript/figures/**",
"benchmark/toolmemory/gitcha.png",
];
for (const workflow of ["ci-docs.yml", "deploy-docs.yml"]) {
const source = await readFile(path.join(repoDir, ".github/workflows", workflow), "utf8");
for (const requiredPath of requiredPaths) assert.ok(source.includes(requiredPath), `${workflow}: ${requiredPath}`);
}
});

View file

@ -1,15 +0,0 @@
import assert from "node:assert/strict";
import test from "node:test";
import { stripMarkdownFrontmatter } from "../src/markdown.js";
test("strips leading YAML frontmatter before rendering", () => {
assert.equal(
stripMarkdownFrontmatter("---\nname: reme_memory\ndescription: Memory skill\n---\n\n# ReMe Memory\n"),
"\n# ReMe Memory\n",
);
});
test("preserves Markdown without frontmatter", () => {
const markdown = "# ReMe Memory\n\nContent\n";
assert.equal(stripMarkdownFrontmatter(markdown), markdown);
});

View file

@ -1,10 +0,0 @@
import { defineConfig } from "vite";
export default defineConfig({
base: "./",
publicDir: ".generated",
build: {
outDir: "dist",
emptyOutDir: true,
},
});

201
plugins/beam/LICENSE Normal file
View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2025 Alibaba Group
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

38
plugins/beam/README.md Normal file
View file

@ -0,0 +1,38 @@
# BEAM plugin
[中文说明](./README_ZH.md)
This plugin owns the BEAM memory, agentic-answer and judge Steps, their prompts,
and their Job defaults in `plugin.yaml`. ReMe's built-in `benchmark.yaml` owns the
shared evaluation Jobs and components. Dataset handling, the runner and results
remain in [`benchmark/beam`](../../benchmark/beam/README.md).
From the repository root, install ReMe and this plugin in editable mode before running the benchmark:
```bash
python -m pip install -e ".[as]"
reme plugins install ./plugins/beam --editable
reme plugins validate beam
python benchmark/beam/run.py
```
Editable installation registers the `beam` entry point while keeping source changes immediately
visible. The runner selects the built-in `benchmark` preset and explicitly enables `beam` for
each Application. Installing the plugin makes it discoverable but does not enable it globally.
`plugin.yaml` registers backends and contributes the plugin-owned `auto_memory`,
`agentic_answer` and `answer_judge` Job defaults. Start the installed plugin with
`reme start config=benchmark plugins='["beam"]'`. The shared preset does not inherit
`default`: only declared Jobs run, indexing is manual, and neither scheduled dream
nor the optional `auto_dream` Job is enabled.
The existing `auto_memory`, `agentic_answer`, `answer_judge`, `bench` and `judge`
names and model environment variables are unchanged. Explicit application/CLI overrides
still take precedence. Installing this plugin does not start an evaluation.
The shared answer base class lives in `reme.steps.benchmark.base_agentic_answer`.
The old core-owned `reme.steps.benchmark.beam` Python import path is removed.
Custom Python callers should import memory, search and answer Steps from `reme_beam`, and the
judge Step from `judge_beam`. After uninstalling,
Applications and CLI services must omit the plugin until it is installed again.
Uninstallation never removes datasets, workspaces or results.
Restart an existing service after changing plugins.

32
plugins/beam/README_ZH.md Normal file
View file

@ -0,0 +1,32 @@
# BEAM 插件
[English](./README.md)
插件包含 BEAM 的记忆、回答、评分 Step、提示词,以及 `plugin.yaml` 中对应的 Job 默认配置。
ReMe 内置的 `benchmark.yaml` 负责公共评测 Job 和 Component;数据集处理、runner 和结果仍留在
[`benchmark/beam`](../../benchmark/beam/README_ZH.md)。
在仓库根目录以 editable 模式安装 ReMe 和本插件,再运行评测:
```bash
python -m pip install -e ".[as]"
reme plugins install ./plugins/beam --editable
reme plugins validate beam
python benchmark/beam/run.py
```
editable 安装会注册 `beam` entry point,并让源码修改立即生效。runner 选择内置 `benchmark`
配置,并为每个 Application 显式启用 `beam`。安装只让插件可被发现,不会在所有应用中全局启用。
`plugin.yaml` 注册 backend,并通过 `application_defaults` 提供插件拥有的 `auto_memory`、
`agentic_answer` 和 `answer_judge` Job。安装后使用
`reme start config=benchmark plugins='["beam"]'`。公共评测配置不继承 `default`,只运行声明的 Job:
索引手动更新,dream 定时任务和可选的 `auto_dream`
均保持关闭。原有 `auto_memory`、`agentic_answer`、`answer_judge`、`bench`、`judge` 名称及模型环境变量
保持不变,显式应用参数和 CLI 覆盖仍优先。安装或启用插件不会自动开始评测。
共享回答基类位于 `reme.steps.benchmark.base_agentic_answer`。
原 `reme.steps.benchmark.beam` Python 导入路径已移除。自定义 Python 调用应从 `reme_beam`
导入记忆、搜索和回答 Step,并从 `judge_beam` 导入评判 Step。
卸载插件后,Application 和 CLI 服务必须移除插件选择,直到再次安装。
卸载不会删除数据集、工作区或结果。修改插件后需重启已有服务。

View file

@ -0,0 +1,28 @@
[project]
name = "reme-beam"
version = "0.1.0"
description = "BEAM benchmark plugin for ReMe."
readme = "README.md"
license = "Apache-2.0"
license-files = ["LICENSE"]
requires-python = ">=3.11"
dependencies = [
"reme-ai[as]>=0.4.1.11",
"json-repair",
"numpy>=2.2.6",
]
[project.entry-points."reme.plugins"]
beam = "reme_beam"
[tool.setuptools.packages.find]
where = ["src"]
include = ["reme_beam*", "judge_beam*"]
[tool.setuptools.package-data]
reme_beam = ["plugin.yaml", "*.yaml"]
judge_beam = ["*.yaml"]
[build-system]
requires = ["setuptools>=77", "wheel"]
build-backend = "setuptools.build_meta"

View file

@ -0,0 +1,5 @@
"""BEAM benchmark judge backend."""
from .llm_judge import BeamRubricJudgeStep
__all__ = ["BeamRubricJudgeStep"]

View file

@ -24,10 +24,9 @@ from typing import List, Tuple
import numpy as np
from json_repair import repair_json
from ...base_step import BaseStep, Ref
from ....components import R
from ....components.as_embedding import BaseAsEmbedding
from ....enumeration import ComponentEnum
from reme.steps.base_step import BaseStep, Ref
from reme.components.as_embedding import BaseAsEmbedding
from reme.enumeration import ComponentEnum
# ---------------------------------------------------------------------------
@ -231,7 +230,6 @@ def _event_ordering_score(
}
@R.register("beam_rubric_judge_step")
class BeamRubricJudgeStep(BaseStep):
"""Judge an LLM response against a list of rubric criteria.

View file

@ -1,11 +1,11 @@
"""BEAM benchmark steps."""
"""BEAM benchmark backends and application configuration for ReMe."""
from .agentic_answer import BeamAgenticAnswerStep
from .llm_judge import BeamRubricJudgeStep
from .auto_memory import BeamAutoMemoryStep
from .search_v2 import SearchV2Step
__all__ = [
"BeamAgenticAnswerStep",
"BeamRubricJudgeStep",
"BeamAutoMemoryStep",
"SearchV2Step",
]

View file

@ -1,10 +1,8 @@
"""BEAM agentic answer step – ReAct agent that answers questions using the search tool."""
from ....components import R
from ..base import BaseAgenticAnswerStep
from reme.steps.benchmark import BaseAgenticAnswerStep
@R.register("beam_agentic_answer_step")
class BeamAgenticAnswerStep(BaseAgenticAnswerStep):
"""Answer a BEAM probing question via ReAct agent with access to the search tool.

View file

@ -4,9 +4,8 @@ from datetime import datetime, timedelta
from agentscope.message import Msg
from ...evolve.auto_memory import AutoMemoryStep, _normalize_msg_timestamp
from ...file_io import validate_session_id
from ....components import R
from reme.steps.evolve.auto_memory import AutoMemoryStep, _normalize_msg_timestamp
from reme.steps.file_io import validate_session_id
# Runtime-context key carrying the 0-based line offset of the current segment
# inside the full session file (segmented ingestion of long sessions).
@ -168,7 +167,6 @@ def _interpolate_timestamps(items: list[dict]) -> list[dict]:
return result
@R.register("beam_auto_memory_step")
class BeamAutoMemoryStep(AutoMemoryStep):
"""AutoMemoryStep variant that interpolates timestamps for BEAM sessions.

View file

@ -0,0 +1,120 @@
backends:
beam_auto_memory_step: reme_beam.auto_memory:BeamAutoMemoryStep
beam_agentic_answer_step: reme_beam.agentic_answer:BeamAgenticAnswerStep
beam_rubric_judge_step: judge_beam.llm_judge:BeamRubricJudgeStep
beam_search_v2_step: reme_beam.search_v2:SearchV2Step
application_defaults:
jobs:
search:
backend: base
description: "Hybrid workspace search (vector + BM25, RRF-fused) with deduplication."
parameters:
type: object
properties:
query:
type: string
description: "search query"
start_date:
type: string
description: "optional inclusive start date filter (YYYY-MM-DD); results earlier than this date are excluded"
end_date:
type: string
description: "optional inclusive end date filter (YYYY-MM-DD); results later than this date are excluded"
required:
- query
steps:
- backend: beam_search_v2_step
vector_weight: 0.7
candidate_multiplier: 5.0
expand_links: false
max_links_per_direction: 10
agentic_answer:
backend: base
description: "BEAM agentic answer job (ReAct agent with search tool)"
watch_dirs: []
watch_suffixes: []
parameters:
type: object
properties:
query:
type: string
description: "The query to ask"
query_time:
type: string
description: "ISO timestamp representing the query time"
default: ""
required:
- query
steps:
- backend: beam_agentic_answer_step
agent_wrapper: bench
answer_judge:
backend: base
description: "BEAM rubric-based LLM-as-Judge: evaluate response against rubric criteria"
watch_dirs: []
watch_suffixes: []
parameters:
type: object
properties:
llm_response:
type: string
description: "The model's response to evaluate"
rubric:
type: array
description: "List of rubric criteria to check"
items:
type: string
probing_question:
type: string
description: "The original probing question"
default: ""
question_type:
type: string
description: "BEAM question type (e.g. event_ordering)"
default: ""
required:
- llm_response
- rubric
steps:
- backend: beam_rubric_judge_step
agent_wrapper: judge
auto_memory:
backend: base
description: "Auto-memory: record conversation facts into a daily note"
parameters:
type: object
properties:
messages:
type: array
description: "messages"
items:
type: object
session_id:
type: string
description: "source conversation session identifier"
default: ""
memory_hint:
type: string
description: "optional hint"
date:
type: string
description: "YYYY-MM-DD daily note date; empty = infer from message timestamps or today"
default: ""
required:
- messages
steps:
- backend: beam_auto_memory_step
# Long sessions are split into turn-aligned segments and fed to the
# agent incrementally; <= 0 disables splitting.
max_segment_words: 10000
components:
as_llm:
default:
max_retries: 5
judge:
retry_delay: 5.0

View file

@ -12,14 +12,18 @@ import datetime
import os
from typing import Final
from ._dedup import _ToolContextDedupMixin
from ._source_format import ALL_RETURNED_MESSAGE, NO_RESULTS_MESSAGE, is_session_path, join_chunk_entries
from ._source_format import merge_session_chunk_intervals, render_chunk_entries
from ..base_step import BaseStep
from ..file_io import extract_daily_date
from ...components import R
from ...schema import FileChunk
from ...utils import expand_links
from reme.schema import FileChunk
from reme.steps.base_step import BaseStep
from reme.steps.file_io import extract_daily_date
from reme.steps.index._dedup import _ToolContextDedupMixin
from reme.steps.index._source_format import (
ALL_RETURNED_MESSAGE,
NO_RESULTS_MESSAGE,
is_session_path,
join_chunk_entries,
)
from reme.steps.index._source_format import merge_session_chunk_intervals, render_chunk_entries
from reme.utils import expand_links
_RRF_K: Final = 60
_MAX_CANDIDATES: Final = 200
@ -37,7 +41,6 @@ def _default_limit() -> int:
return _DEFAULT_LIMIT
@R.register("search_v2_step")
class SearchV2Step(_ToolContextDedupMixin, BaseStep):
"""Hybrid search: run vector + keyword in parallel, fuse via RRF, filter, truncate."""

201
plugins/lme/LICENSE Normal file
View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2025 Alibaba Group
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

38
plugins/lme/README.md Normal file
View file

@ -0,0 +1,38 @@
# LongMemEval plugin
[中文说明](./README_ZH.md)
This plugin owns the LongMemEval memory, agentic-answer and judge Steps, their prompts,
and their Job defaults in `plugin.yaml`. ReMe's built-in `benchmark.yaml` owns the
shared evaluation Jobs and components. Dataset handling, the runner and results remain
in [`benchmark/longmemeval`](../../benchmark/longmemeval/README.md).
From the repository root, install ReMe and this plugin in editable mode before running the benchmark:
```bash
python -m pip install -e ".[as]"
reme plugins install ./plugins/lme --editable
reme plugins validate lme
python benchmark/longmemeval/run.py
```
Editable installation registers the `lme` entry point while keeping source changes immediately
visible. The runner selects the built-in `benchmark` preset and explicitly enables `lme` for
each Application. Installing the plugin makes it discoverable but does not enable it globally.
`plugin.yaml` registers backends and contributes the plugin-owned `auto_memory`,
`agentic_answer` and `answer_judge` Job defaults. Start the installed plugin with
`reme start config=benchmark plugins='["lme"]'`. The shared preset does not inherit
`default`: only declared Jobs run, indexing is manual, and neither scheduled dream
nor the optional `auto_dream` Job is enabled.
The existing `auto_memory`, `agentic_answer`, `answer_judge`, `bench` and `judge`
names and model environment variables are unchanged. Explicit application/CLI overrides
still take precedence. Installing this plugin does not start an evaluation.
The shared answer base class lives in `reme.steps.benchmark.base_agentic_answer`.
The old core-owned `reme.steps.benchmark.lme` Python import path is removed.
Custom Python callers should import memory, search and answer Steps from `reme_lme`, and the
judge Step from `judge_lme`. After uninstalling,
Applications and CLI services must omit the plugin until it is installed again.
Uninstallation never removes datasets, workspaces or results.
Restart an existing service after changing plugins.

32
plugins/lme/README_ZH.md Normal file
View file

@ -0,0 +1,32 @@
# LongMemEval 插件
[English](./README.md)
插件包含 LongMemEval 的记忆、回答、评分 Step、提示词,以及 `plugin.yaml` 中对应的 Job 默认配置。
ReMe 内置的 `benchmark.yaml` 负责公共评测 Job 和 Component;数据集处理、runner 和结果仍留在
[`benchmark/longmemeval`](../../benchmark/longmemeval/README_ZH.md)。
在仓库根目录以 editable 模式安装 ReMe 和本插件,再运行评测:
```bash
python -m pip install -e ".[as]"
reme plugins install ./plugins/lme --editable
reme plugins validate lme
python benchmark/longmemeval/run.py
```
editable 安装会注册 `lme` entry point,并让源码修改立即生效。runner 选择内置 `benchmark`
配置,并为每个 Application 显式启用 `lme`。安装只让插件可被发现,不会在所有应用中全局启用。
`plugin.yaml` 注册 backend,并通过 `application_defaults` 提供插件拥有的 `auto_memory`、
`agentic_answer` 和 `answer_judge` Job。安装后使用
`reme start config=benchmark plugins='["lme"]'`。公共评测配置不继承 `default`,只运行声明的 Job:
索引手动更新,dream 定时任务和可选的 `auto_dream`
均保持关闭。原有 `auto_memory`、`agentic_answer`、`answer_judge`、`bench`、`judge` 名称及模型环境变量
保持不变,显式应用参数和 CLI 覆盖仍优先。安装或启用插件不会自动开始评测。
共享回答基类位于 `reme.steps.benchmark.base_agentic_answer`。
原 `reme.steps.benchmark.lme` Python 导入路径已移除。自定义 Python 调用应从 `reme_lme`
导入记忆、搜索和回答 Step,并从 `judge_lme` 导入评判 Step。
卸载插件后,Application 和 CLI 服务必须移除插件选择,直到再次安装。
卸载不会删除数据集、工作区或结果。修改插件后需重启已有服务。

View file

@ -0,0 +1,26 @@
[project]
name = "reme-lme"
version = "0.1.0"
description = "LongMemEval benchmark plugin for ReMe."
readme = "README.md"
license = "Apache-2.0"
license-files = ["LICENSE"]
requires-python = ">=3.11"
dependencies = [
"reme-ai[as]>=0.4.1.11",
]
[project.entry-points."reme.plugins"]
lme = "reme_lme"
[tool.setuptools.packages.find]
where = ["src"]
include = ["reme_lme*", "judge_lme*"]
[tool.setuptools.package-data]
reme_lme = ["plugin.yaml", "*.yaml"]
judge_lme = ["*.yaml"]
[build-system]
requires = ["setuptools>=77", "wheel"]
build-backend = "setuptools.build_meta"

View file

@ -0,0 +1,5 @@
"""LongMemEval benchmark judge backend."""
from .llm_judge import LmeAnswerJudgeStep
__all__ = ["LmeAnswerJudgeStep"]

View file

@ -2,11 +2,9 @@
import re
from ...base_step import BaseStep
from ....components import R
from reme.steps.base_step import BaseStep
@R.register("lme_answer_judge_step")
class LmeAnswerJudgeStep(BaseStep):
"""Evaluate whether an agent answer is correct against a golden answer."""

View file

@ -1,11 +1,11 @@
"""LongMemEval benchmark steps."""
"""LongMemEval benchmark backends and application configuration for ReMe."""
from .agentic_answer import LmeAgenticAnswerStep
from .llm_judge import LmeAnswerJudgeStep
from .auto_memory import LmeAutoMemoryStep
from .search_v2 import SearchV2Step
__all__ = [
"LmeAgenticAnswerStep",
"LmeAnswerJudgeStep",
"LmeAutoMemoryStep",
"SearchV2Step",
]

View file

@ -1,10 +1,8 @@
"""LongMemEval agentic answer step – ReAct agent that answers questions using the search tool."""
from ....components import R
from ..base import BaseAgenticAnswerStep
from reme.steps.benchmark import BaseAgenticAnswerStep
@R.register("lme_agentic_answer_step")
class LmeAgenticAnswerStep(BaseAgenticAnswerStep):
"""Answer a LongMemEval query via ReAct agent with access to the search tool.
@ -12,7 +10,7 @@ class LmeAgenticAnswerStep(BaseAgenticAnswerStep):
``search`` job tool to retrieve relevant memory chunks before generating
a final answer.
Session-transcript compression in ``search_v2_step`` is controlled by the
Session-transcript compression in the plugin's search Step is controlled by the
``compress_session`` flag in the runtime context (set by the benchmark
runner from ``evaluation.compress_session``); it is off by default.
"""

View file

@ -4,8 +4,7 @@ from datetime import datetime, timedelta
from agentscope.message import Msg
from ...evolve.auto_memory import AutoMemoryStep, _normalize_msg_timestamp
from ....components import R
from reme.steps.evolve.auto_memory import AutoMemoryStep, _normalize_msg_timestamp
def _parse_iso_seconds(value: str) -> datetime | None:
@ -115,7 +114,6 @@ def _interpolate_timestamps(items: list[dict]) -> list[dict]:
return result
@R.register("lme_auto_memory_step")
class LmeAutoMemoryStep(AutoMemoryStep):
"""AutoMemoryStep variant that interpolates timestamps for LongMemEval sessions.

View file

@ -0,0 +1,113 @@
backends:
lme_auto_memory_step: reme_lme.auto_memory:LmeAutoMemoryStep
lme_agentic_answer_step: reme_lme.agentic_answer:LmeAgenticAnswerStep
lme_answer_judge_step: judge_lme.llm_judge:LmeAnswerJudgeStep
lme_search_v2_step: reme_lme.search_v2:SearchV2Step
application_defaults:
jobs:
search:
backend: base
description: "Hybrid workspace search (vector + BM25, RRF-fused) with deduplication."
parameters:
type: object
properties:
query:
type: string
description: "search query"
start_date:
type: string
description: "optional inclusive start date filter (YYYY-MM-DD); results earlier than this date are excluded"
end_date:
type: string
description: "optional inclusive end date filter (YYYY-MM-DD); results later than this date are excluded"
required:
- query
steps:
- backend: lme_search_v2_step
vector_weight: 0.7
candidate_multiplier: 5.0
expand_links: false
max_links_per_direction: 10
agentic_answer:
backend: base
description: "LongMemEval agentic answer job (ReAct agent with search tool)"
watch_dirs: []
watch_suffixes: []
parameters:
type: object
properties:
query:
type: string
description: "The query to ask"
query_time:
type: string
description: "ISO timestamp representing the query time"
default: ""
required:
- query
steps:
- backend: lme_agentic_answer_step
agent_wrapper: bench
answer_judge:
backend: base
description: "LLM-as-Judge: evaluate agent answer against golden answer"
watch_dirs: []
watch_suffixes: []
parameters:
type: object
properties:
query:
type: string
description: "The question being asked"
agent_answer:
type: string
description: "The model's answer to evaluate"
golden_answer:
type: string
description: "The correct/golden answer"
question_type:
type: string
description: "The question type for prompt selection"
default: ""
required:
- query
- agent_answer
- golden_answer
steps:
- backend: lme_answer_judge_step
agent_wrapper: judge
auto_memory:
backend: base
description: "Auto-memory: record conversation facts into a daily note"
parameters:
type: object
properties:
messages:
type: array
description: "messages"
items:
type: object
session_id:
type: string
description: "source conversation session identifier"
default: ""
memory_hint:
type: string
description: "optional hint"
date:
type: string
description: "YYYY-MM-DD daily note date; empty = infer from message timestamps or today"
default: ""
required:
- messages
steps:
- backend: lme_auto_memory_step
components:
as_llm:
default:
max_retries: 3

View file

@ -0,0 +1,333 @@
"""Hybrid search (v2) over file_store using RRF fusion of vector + keyword results.
This is the local fork of the upstream search step. It uses
:class:`_ToolContextDedupMixin` for subset-aware interval-merging dedup and
:func:`render_chunk_entries` / :func:`join_chunk_entries` for session-aware
chunk formatting with
:data:`ALL_RETURNED_MESSAGE` / :data:`NO_RESULTS_MESSAGE` notices.
"""
import asyncio
import datetime
import os
from typing import Final
from reme.schema import FileChunk
from reme.steps.base_step import BaseStep
from reme.steps.file_io import extract_daily_date
from reme.steps.index._dedup import _ToolContextDedupMixin
from reme.steps.index._source_format import (
ALL_RETURNED_MESSAGE,
NO_RESULTS_MESSAGE,
is_session_path,
join_chunk_entries,
)
from reme.steps.index._source_format import merge_session_chunk_intervals, render_chunk_entries
from reme.utils import expand_links
_RRF_K: Final = 60
_MAX_CANDIDATES: Final = 200
_DEFAULT_LIMIT_ENV: Final = "REME_SEARCH_LIMIT"
_DEFAULT_LIMIT: Final = 5
def _default_limit() -> int:
value = os.getenv(_DEFAULT_LIMIT_ENV)
if value is None:
return _DEFAULT_LIMIT
try:
return int(value)
except ValueError:
return _DEFAULT_LIMIT
class SearchV2Step(_ToolContextDedupMixin, BaseStep):
"""Hybrid search: run vector + keyword in parallel, fuse via RRF, filter, truncate."""
def __init__(
self,
*args,
seen_ttl_hours: float = 24,
**kwargs,
):
super().__init__(*args, **kwargs)
self.seen_ttl_hours = seen_ttl_hours
@staticmethod
def _rrf_merge(
vector: list[FileChunk],
keyword: list[FileChunk],
vector_weight: float,
) -> list[FileChunk]:
"""Fuse two ranked lists with Reciprocal Rank Fusion, keyed by chunk.id."""
text_weight = 1.0 - vector_weight
merged: dict[str, FileChunk] = {}
for rank, chunk in enumerate(vector, start=1):
contrib = vector_weight / (_RRF_K + rank)
c = chunk.model_copy(deep=False)
c.scores = {**chunk.scores, "vector": chunk.scores.get("vector", chunk.score), "score": contrib}
merged[c.id] = c
for rank, chunk in enumerate(keyword, start=1):
contrib = text_weight / (_RRF_K + rank)
existing = merged.get(chunk.id)
if existing is not None:
existing.scores = {
**existing.scores,
"keyword": chunk.scores.get("keyword", chunk.score),
"score": existing.scores["score"] + contrib,
}
else:
c = chunk.model_copy(deep=False)
c.scores = {**chunk.scores, "keyword": chunk.scores.get("keyword", chunk.score), "score": contrib}
merged[c.id] = c
results = list(merged.values())
results.sort(key=lambda r: r.score, reverse=True)
return results
@staticmethod
def _format_scores(scores: dict[str, float], hybrid: bool) -> str:
"""Format scores for the answer line: always show fused; show per-branch when hybrid."""
parts = [f"score={scores.get('score', 0.0):.4f}"]
if hybrid:
for k in ("vector", "keyword"):
v = scores.get(k)
parts.append(f"{k}={v:.4f}" if v is not None else f"{k}=-")
return " ".join(parts)
async def execute(self):
assert self.context is not None
query: str = (self.context.get("query", "") or "").strip()
limit: int = int(self.context.get("limit") or _default_limit())
min_score: float = float(self.context.get("min_score") or 0.0)
# vector_weight: prefer agent-supplied context value; fallback to YAML kwargs / default 0.7.
# Convertible numeric inputs are clipped to [0.0, 1.0]; non-numeric inputs are silently ignored.
raw_vw = self.context.get("vector_weight")
vector_weight: float | None = None
if raw_vw is not None:
try:
vector_weight = float(raw_vw)
except (TypeError, ValueError):
self.logger.warning(
f"[{self.name}] non-numeric vector_weight={raw_vw!r}; ignoring and using default 0.7",
)
vector_weight = None
if vector_weight is None:
vector_weight = float(self.kwargs.get("vector_weight", 0.7))
vector_weight = max(0.0, min(1.0, vector_weight))
candidate_multiplier: float = float(self.kwargs.get("candidate_multiplier", 5.0))
expand_links_enabled: bool = bool(self.kwargs.get("expand_links", True))
max_links_per_direction: int = int(self.kwargs.get("max_links_per_direction", 10))
tool_context_id: str = (self.context.get("tool_context_id", "") or "").strip()
# Injected value takes precedence over YAML kwargs; check existence
# (not truthiness) so an explicit False can disable a YAML-true flag.
_strict_date_filter = self.context.get("strict_date_filter")
if _strict_date_filter is None:
_strict_date_filter = self.kwargs.get("strict_date_filter", False)
strict_date_filter: bool = bool(_strict_date_filter)
if not query:
self.context.response.success = False
self.context.response.answer = "Error: query cannot be empty"
return self.context.response
assert limit > 0, f"limit must be positive, got {limit}"
candidates = min(_MAX_CANDIDATES, max(1, int(limit * candidate_multiplier)))
search_filter: dict = dict(self.context.get("search_filter", {}) or {})
# Promote top-level date parameters into search_filter for file_store.
for date_key in ("start_date", "end_date"):
value = self.context.get(date_key)
if value and date_key not in search_filter:
search_filter[date_key] = value
# Validate and normalize date filters before they reach file_store.
# _matches_search_filter does lexicographic string comparison against
# path_date (always a canonical YYYY-MM-DD), so raw caller values like
# "2026-2-28" or "abc" would produce silently wrong results.
for date_key in ("start_date", "end_date"):
raw = search_filter.get(date_key)
if raw is None:
continue
normalized = extract_daily_date(raw)
if normalized is None:
# Fallback: accept non-zero-padded dates like "2024-1-5".
try:
normalized = (
datetime.datetime.strptime(
str(raw).strip(),
"%Y-%m-%d",
)
.date()
.isoformat()
)
except ValueError:
self.logger.warning(
f"Ignoring invalid {date_key}={raw!r}; " f"expected a valid YYYY-MM-DD date.",
)
del search_filter[date_key]
continue
search_filter[date_key] = normalized
if strict_date_filter:
search_filter["strict_date_filter"] = True
vector_results, keyword_results = await asyncio.gather(
self.file_store.vector_search(query, candidates, search_filter),
self.file_store.keyword_search(query, candidates, search_filter),
)
self.logger.info(
f"[{self.name}] query={query!r} candidates={candidates} "
f"vector_hits={len(vector_results)} keyword_hits={len(keyword_results)}",
)
hybrid = bool(vector_results) and bool(keyword_results)
if not vector_results and not keyword_results:
fused: list[FileChunk] = []
elif not keyword_results:
fused = vector_results
elif not vector_results:
fused = keyword_results
else:
fused = self._rrf_merge(vector_results, keyword_results, vector_weight)
if min_score > 0.0:
fused = [c for c in fused if c.score >= min_score]
pre_dedup_count = 0
dedup: dict | None = None
if tool_context_id:
pre_dedup_count = len(fused)
fused, dedup = self._dedupe_tool_context(
fused,
tool_context_id,
limit,
clock=self.kwargs.get("clock"),
ttl_override=self.kwargs.get("tool_context_chunk_ttl_seconds"),
)
else:
fused = fused[:limit]
unique_paths = list(dict.fromkeys(c.path for c in fused))
link_expansion: dict[str, dict] = (
await expand_links(self.file_store, unique_paths, max_links_per_direction) if expand_links_enabled else {}
)
session_dir = self.config_value("session_dir")
entries = render_chunk_entries(
merge_session_chunk_intervals(fused, session_dir),
session_dir,
score_fn=lambda c: self._format_scores(c.scores, hybrid),
link_expansion=link_expansion,
)
if self._session_compress_enabled():
await self._compress_session_entries(entries, query, session_dir)
self.context.response.answer = join_chunk_entries(entries)
if not fused:
self.context.response.answer = ALL_RETURNED_MESSAGE if pre_dedup_count > 0 else NO_RESULTS_MESSAGE
self.context.response.metadata["results"] = [
c.model_dump(exclude_none=True, exclude={"embedding"}) for c in fused
]
self.context.response.metadata["link_expansion"] = link_expansion
self.context.response.metadata["counts"] = {
"vector": len(vector_results),
"keyword": len(keyword_results),
"returned": len(fused),
"hybrid": hybrid,
}
if dedup is not None:
self.context.response.metadata["dedup"] = dedup
return self.context.response
def _session_compress_enabled(self) -> bool:
"""True when the injected ``_search._compress.session`` flag is truthy."""
assert self.context is not None
search_cfg: dict = self.context.get("_search") or {}
value = (search_cfg.get("_compress") or {}).get("session")
return value is True or str(value).strip().lower() == "true"
async def _compress_session_entries(self, entries: list[dict[str, str]], query: str, session_dir: str) -> None:
"""Compress session-transcript entry bodies in place via the ``compressor`` job.
Only entries whose ``path`` points at a raw session transcript are
compressed; other entries and all non-``body`` fields stay untouched.
When ``_search.type`` is ``query-independent`` the compressor runs
without queries (generic compression); otherwise (``query-aware``,
the default) it receives the injected ``_search.queries`` plus the
current search query.
The compressor receives the already-rendered body (one message per
line) stripped. Its output is adopted whenever the compressor
succeeded and the result is not longer than the input; adopted bodies
get a leading ``compressed session chunk:`` marker so downstream
consumers can tell them from verbatim transcripts.
Degrades gracefully when the ``compressor`` job is missing from the
active config: the whole method becomes a no-op and a warning is
logged, so search behaves as if compression were disabled. This
avoids a hard ``Job compressor not found`` failure when a benchmark
config forgets to define the compressor job/component.
Per-entry exceptions raised by the compressor job (e.g. a temporary
LLM outage) are caught inside ``compress`` so they never propagate
through ``asyncio.gather``: the failing entry keeps its original body
while the remaining entries are still compressed, preserving already
retrieved search results.
"""
assert self.context is not None
# Guard: when the compressor job is missing from the active config
# (e.g. a benchmark config that forgot to define it), degrade
# gracefully to the no-compression behavior instead of raising
# "Job compressor not found" from run_job below.
# Skipped when there is no app_context (e.g. unit tests that mock
# run_job directly), so the mock can still drive compression.
if self.app_context is not None and self.get_job("compressor") is None:
self.logger.warning(
f"[{self.name}] compressor job not found in config; "
"skipping session chunk compression (degrading to uncompressed behavior)",
)
return
search_cfg: dict = self.context.get("_search") or {}
query_type = str(search_cfg.get("type") or "query-aware").strip().lower()
if query_type == "query-independent":
queries: list[str] = []
else:
queries = [str(q).strip() for q in (search_cfg.get("queries") or []) if str(q).strip()]
if query and query not in queries:
queries.append(query)
async def compress(entry: dict[str, str]) -> None:
path = entry.get("path", "")
body = (entry.get("body", "") or "").strip()
if not body:
return
try:
response = await self.run_job("compressor", text=body, queries=queries)
except Exception as exc: # pylint: disable=broad-except
self.logger.warning(
f"[{self.name}] session body compression raised path={path!r} " f"error={exc!r}; keeping original",
)
return
compressed = str(response.answer or "").strip()
if not response.success or not compressed:
self.logger.warning(
f"[{self.name}] session body compression failed path={path!r} "
f"success={response.success} answer={compressed[:100]!r}; keeping original",
)
return
if len(compressed) > len(body):
self.logger.info(
f"[{self.name}] compressed body longer than original "
f"({len(compressed)} > {len(body)}) path={path!r}; keeping original",
)
return
entry["body"] = f"compressed session chunk:\n{compressed}"
targets = [e for e in entries if is_session_path(e.get("path", ""), session_dir)]
if not targets:
return
self.logger.info(f"[{self.name}] compressing {len(targets)} session entries with {len(queries)} queries")
await asyncio.gather(*(compress(entry) for entry in targets))

View file

@ -1,635 +0,0 @@
# BEAM benchmark config — based on longmemeval.yaml (split)
# All background/cron jobs are converted to base (manually callable).
# Use with: resolve_app_config(config="beam.yaml", ...)
service:
backend: http
jobs:
# ── BEAM agentic answer (ReAct agent + search tool) ──
agentic_answer:
backend: base
description: "BEAM agentic answer job (ReAct agent with search tool)"
watch_dirs: []
watch_suffixes: []
parameters:
type: object
properties:
query:
type: string
description: "The query to ask"
query_time:
type: string
description: "ISO timestamp representing the query time"
default: ""
required:
- query
steps:
- backend: beam_agentic_answer_step
agent_wrapper: bench
# ── BEAM rubric-based LLM-as-Judge ──
answer_judge:
backend: base
description: "BEAM rubric-based LLM-as-Judge: evaluate response against rubric criteria"
watch_dirs: []
watch_suffixes: []
parameters:
type: object
properties:
llm_response:
type: string
description: "The model's response to evaluate"
rubric:
type: array
description: "List of rubric criteria to check"
items:
type: string
probing_question:
type: string
description: "The original probing question"
default: ""
question_type:
type: string
description: "BEAM question type (e.g. event_ordering)"
default: ""
required:
- llm_response
- rubric
steps:
- backend: beam_rubric_judge_step
agent_wrapper: judge
# ── Manual index update (replaces index_update_loop background) ──
index_update:
backend: base
description: "Manually trigger incremental index update for watched dirs."
watch_dirs: [daily_dir, digest_dir, session_dir/dialog]
watch_suffixes: [md, jsonl]
parameters:
type: object
properties: {}
steps:
- backend: init_changes_step
monitor_type: file_store
monitor_name: default
dispatch_steps: [update_index_step]
# ── Manual digest catalog update (replaces digest_watch_loop background) ──
digest_update:
backend: base
description: "Manually trigger digest catalog update."
watch_dirs: [daily_dir, digest_dir]
watch_suffixes: [md]
parameters:
type: object
properties: {}
steps:
- backend: init_changes_step
monitor_type: file_catalog
monitor_name: digest
dispatch_steps:
- backend: update_catalog_step
file_catalog: digest
- backend: log_changes_step
# ── Auto dream (same as default.yaml auto_dream, base mode) ──
# auto_dream:
# backend: base
# description: "Auto-dream: scan today's day-index and daily notes, globally extract merged units/topics, integrate digest units, write interests.yaml, and persist the dream catalog."
# parameters:
# type: object
# properties:
# date:
# type: string
# description: "YYYY-MM-DD to scan; defaults to today in the dreamer's timezone"
# default: ""
# hint:
# type: string
# description: "caller guidance passed through to dream extract/integrate"
# default: ""
# scan_days:
# type: integer
# description: "number of recent daily directories to scan, ending at date"
# default: 2
# max_units:
# type: integer
# description: "maximum number of extracted memory units"
# default: 5
# topic_count:
# type: integer
# description: "maximum number of final daily interest topics"
# default: 3
# topic_diversity_days:
# type: integer
# description: "number of previous interests.yaml days to avoid repeating"
# default: 7
# steps:
# - backend: dream_extract_step
# file_catalog: dream
# topic_session_id: interests
# scan_days: 2
# max_units: 5
# - backend: dream_integrate_step
# - backend: dream_topics_step
# topic_count: 3
# topic_diversity_days: 7
# - backend: dream_finish_step
# file_catalog: dream
# ── Auto memory (same as default.yaml) ──
auto_memory:
backend: base
description: "Auto-memory: record conversation facts into a daily note"
parameters:
type: object
properties:
messages:
type: array
description: "messages"
items:
type: object
session_id:
type: string
description: "source conversation session identifier"
default: ""
memory_hint:
type: string
description: "optional hint"
date:
type: string
description: "YYYY-MM-DD daily note date; empty = infer from message timestamps or today"
default: ""
required:
- messages
steps:
- backend: beam_auto_memory_step
# Long sessions are split into turn-aligned segments and fed to the
# agent incrementally; each segment holds at most this many words.
# ("segment" here is a slice of the message list, unrelated to file
# chunking in the index.) <= 0 disables splitting.
max_segment_words: 10000
# ── Text compression (direct LLM call, no agent) ──
compressor:
backend: base
description: "Compress text via a direct LLM call, optionally guided by queries as relevance filter"
parameters:
type: object
properties:
text:
type: string
description: "the text to compress"
queries:
type: array
description: "optional list of queries; content potentially relevant to any query is kept, content certainly irrelevant to all queries may be dropped"
items:
type: string
default: []
required:
- text
steps:
- backend: compressor_step
as_llm: compressor
# ── Reindex (derived search indexes only) ──
reindex:
backend: base
description: "rebuild BM25 and/or embedding indexes from current file_chunks"
parameters:
type: object
properties:
scope:
type: string
enum: [all, bm25, embedding]
default: all
steps:
- backend: reindex_step
# ── Search ──
# start_date:
# type: string
# description: "optional inclusive start date filter (YYYY-MM-DD); results earlier than this date are excluded"
# end_date:
# type: string
# description: "optional inclusive end date filter (YYYY-MM-DD); results later than this date are excluded"
search:
backend: base
description: "Hybrid workspace search (vector + BM25, RRF-fused) with deduplication."
parameters:
type: object
properties:
query:
type: string
description: "search query"
start_date:
type: string
description: "optional inclusive start date filter (YYYY-MM-DD); results earlier than this date are excluded"
end_date:
type: string
description: "optional inclusive end date filter (YYYY-MM-DD); results later than this date are excluded"
# vector_weight:
# type: number
# description: >-
# Optional weight balancing vector similarity vs BM25 keyword matching in the
# RRF fusion. Recommended value is 0.7, which provides a good balance between
# semantic (vector) similarity and lexical (BM25) matching. Values close to 0
# emphasize BM25 keyword matching, values close to 1 emphasize vector semantic
# similarity.
required:
- query
steps:
- backend: search_v2_step
vector_weight: 0.7
candidate_multiplier: 5.0
expand_links: false
max_links_per_direction: 10
add_draft:
backend: base
description: "Append text to the current draft list."
parameters:
type: object
properties:
text:
type: string
description: "draft text to append"
required:
- text
steps:
- backend: add_draft_step
read_all_draft:
backend: base
description: "Read all draft text previously appended in the current tool context."
parameters:
type: object
properties: { }
steps:
- backend: read_all_draft_step
python_execute:
backend: base
description: "Execute Python code and return printed stdout."
parameters:
type: object
properties:
code:
type: string
description: "Python code to execute. Print the final result to stdout."
timeout:
type: number
description: "Execution timeout in seconds; defaults to 60."
required:
- code
steps:
- backend: python_execute_step
# ── File I/O jobs (needed by auto_memory agent tools) ──
daily_list:
backend: base
description: "List notes under a single day."
parameters:
type: object
properties:
date:
type: string
description: "YYYY-MM-DD; empty = today"
default: ""
steps:
- backend: daily_list_step
daily_reindex:
backend: base
description: "Rebuild the day-index page daily/<date>.md."
parameters:
type: object
properties:
date:
type: string
description: "YYYY-MM-DD; empty = today"
default: ""
steps:
- backend: daily_reindex_step
frontmatter_update:
backend: base
description: "Merge key-values into a file's frontmatter."
parameters:
type: object
properties:
path:
type: string
description: "workspace-relative path"
metadata:
type: object
description: "key-values to merge"
required:
- path
- metadata
steps:
- backend: frontmatter_update_step
move:
backend: base
description: "Move / rename a workspace file."
parameters:
type: object
properties:
src_path:
type: string
description: "workspace-relative source"
dst_path:
type: string
description: "workspace-relative destination"
overwrite:
type: boolean
default: false
retarget:
type: boolean
default: true
required:
- src_path
- dst_path
steps:
- backend: move_step
read:
backend: base
description: "Read a markdown file under the workspace."
parameters:
type: object
properties:
path:
type: string
description: "workspace-relative path; markdown only"
start_line:
type: integer
end_line:
type: integer
required:
- path
steps:
- backend: read_step
with_neighbors: false
max_neighbors_per_direction: 10
write:
backend: base
description: "Write a markdown file."
parameters:
type: object
properties:
path:
type: string
name:
type: string
description:
type: string
content:
type: string
metadata:
type: object
required:
- path
- name
- description
- content
steps:
- backend: write_step
daily_write:
backend: base
description: "Write a daily markdown note."
parameters:
type: object
properties:
name:
type: string
description:
type: string
session_id:
type: string
content:
type: string
date:
type: string
default: ""
metadata:
type: object
required:
- name
- description
- session_id
- content
steps:
- backend: daily_write_step
edit:
backend: base
description: "Find-and-replace in a markdown file."
parameters:
type: object
properties:
path:
type: string
old:
type: string
new:
type: string
default: ""
required:
- path
- old
- new
steps:
- backend: edit_step
frontmatter_read:
backend: base
description: "Read a file's frontmatter as a dict."
parameters:
type: object
properties:
path:
type: string
required:
- path
steps:
- backend: frontmatter_read_step
node_search:
backend: base
description: "Digest node recall."
parameters:
type: object
properties:
query:
type: string
limit:
type: integer
default: 20
required:
- query
steps:
- backend: node_search_step
vector_weight: 0.7
candidate_multiplier: 5.0
components:
tokenizer:
default:
backend: regex
as_embedding:
default:
backend: ${EMBEDDING_BACKEND:-openai}
model: ${EMBEDDING_MODEL_NAME:-text-embedding-v4}
credential:
api_key: ${EMBEDDING_API_KEY:-}
base_url: ${EMBEDDING_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1}
dimensions: 1024
embedding_store:
default:
backend: local
as_embedding: default
as_llm:
default:
backend: ${LLM_BACKEND:-openai}
model: ${LLM_MODEL_NAME:-qwen3.6-flash}
stream: true
context_size: 200000
max_retries: 5
retry_delay: 5.0
credential:
api_key: ${LLM_API_KEY:-}
base_url: ${LLM_BASE_URL:-}
parameters:
max_tokens: 65536
thinking_enable: false
judge:
backend: ${LLM_BACKEND:-openai}
model: ${JUDGE_MODEL_NAME:-qwen3.7-max}
stream: false
context_size: 200000
max_retries: 5
retry_delay: 5.0
credential:
api_key: ${LLM_API_KEY:-}
base_url: ${LLM_BASE_URL:-}
parameters:
max_tokens: 65536
thinking_enable: false
bench:
backend: ${LLM_BACKEND:-openai}
model: ${BENCH_MODEL_NAME:-qwen3.7-max}
stream: true
context_size: 400000
max_retries: 5
retry_delay: 5.0
credential:
api_key: ${LLM_API_KEY:-}
base_url: ${LLM_BASE_URL:-}
parameters:
max_tokens: 65536
thinking_enable: true
compressor:
backend: ${LLM_BACKEND:-openai}
model: ${LLM_MODEL_NAME:-qwen3.6-flash}
stream: false
context_size: 200000
max_retries: 5
retry_delay: 5.0
credential:
api_key: ${LLM_API_KEY:-}
base_url: ${LLM_BASE_URL:-}
parameters:
max_tokens: 65536
thinking_enable: false
agent_wrapper:
default:
backend: agentscope
as_llm: default
permission_mode: bypass
react_config:
max_iters: 30
context_config:
trigger_ratio: 0.8
reserve_ratio: 0.1
tool_result_limit: 50000
model_config:
max_retries: 1
judge:
backend: agentscope
as_llm: judge
permission_mode: bypass
react_config:
max_iters: 1
context_config:
trigger_ratio: 0.8
reserve_ratio: 0.1
tool_result_limit: 50000
model_config:
max_retries: 1
bench:
backend: agentscope
as_llm: bench
permission_mode: bypass
react_config:
max_iters: 30
context_config:
trigger_ratio: 0.8
reserve_ratio: 0.1
tool_result_limit: 50000
model_config:
max_retries: 1
file_graph:
default:
backend: local
file_catalog:
default:
backend: local
resource:
backend: local
digest:
backend: local
dream:
backend: local
file_chunker:
markdown:
backend: markdown
supported_extensions: [ "md" ]
embed_toc: true
max_ast_sections: 100
include_frontmatter_in_metadata: false
include_frontmatter_keys_in_metadata: [] # empty = all non-empty frontmatter keys
json:
backend: json
supported_extensions: [ "json" ]
jsonl:
backend: jsonl
supported_extensions: [ "jsonl" ] # noqa: keep #314 chunker scope intact after #325
max_chars: 4000
default:
backend: default
supported_extensions: ["txt","log"]
keyword_index:
default:
backend: bm25
tokenizer: default
file_store:
default:
backend: local
store_name: local
embedding_store: default
keyword_index: default
file_graph: default

View file

@ -1,63 +1,11 @@
# LongMemEval benchmark config — based on longmemeval.yaml (split)
# All background/cron jobs are converted to base (manually callable).
# Use with: resolve_app_config(config="lme.yaml", ...)
# Shared benchmark application preset; independent of the default service config.
# Background and cron jobs are omitted in favor of manually callable base jobs.
# Benchmark plugins contribute their own jobs through plugin.yaml.
service:
backend: http
jobs:
# ── LongMemEval agentic answer (ReAct agent + search tool) ──
agentic_answer:
backend: base
description: "LongMemEval agentic answer job (ReAct agent with search tool)"
watch_dirs: []
watch_suffixes: []
parameters:
type: object
properties:
query:
type: string
description: "The query to ask"
query_time:
type: string
description: "ISO timestamp representing the query time"
default: ""
required:
- query
steps:
- backend: lme_agentic_answer_step
agent_wrapper: bench
# ── LLM-as-Judge for evaluating answers ──
answer_judge:
backend: base
description: "LLM-as-Judge: evaluate agent answer against golden answer"
watch_dirs: []
watch_suffixes: []
parameters:
type: object
properties:
query:
type: string
description: "The question being asked"
agent_answer:
type: string
description: "The model's answer to evaluate"
golden_answer:
type: string
description: "The correct/golden answer"
question_type:
type: string
description: "The question type for prompt selection"
default: ""
required:
- query
- agent_answer
- golden_answer
steps:
- backend: lme_answer_judge_step
agent_wrapper: judge
# ── Manual index update (replaces index_update_loop background) ──
index_update:
backend: base
@ -135,34 +83,6 @@ jobs:
# - backend: dream_finish_step
# file_catalog: dream
# ── Auto memory (same as default.yaml) ──
auto_memory:
backend: base
description: "Auto-memory: record conversation facts into a daily note"
parameters:
type: object
properties:
messages:
type: array
description: "messages"
items:
type: object
session_id:
type: string
description: "source conversation session identifier"
default: ""
memory_hint:
type: string
description: "optional hint"
date:
type: string
description: "YYYY-MM-DD daily note date; empty = infer from message timestamps or today"
default: ""
required:
- messages
steps:
- backend: lme_auto_memory_step
# ── Text compression (direct LLM call, no agent) ──
compressor:
backend: base
@ -199,46 +119,6 @@ jobs:
steps:
- backend: reindex_step
# ── Search ──
# start_date:
# type: string
# description: "optional inclusive start date filter (YYYY-MM-DD); results earlier than this date are excluded"
# end_date:
# type: string
# description: "optional inclusive end date filter (YYYY-MM-DD); results later than this date are excluded"
search:
backend: base
description: "Hybrid workspace search (vector + BM25, RRF-fused) with deduplication."
parameters:
type: object
properties:
query:
type: string
description: "search query"
start_date:
type: string
description: "optional inclusive start date filter (YYYY-MM-DD); results earlier than this date are excluded"
end_date:
type: string
description: "optional inclusive end date filter (YYYY-MM-DD); results later than this date are excluded"
# vector_weight:
# type: number
# description: >-
# Optional weight balancing vector similarity vs BM25 keyword matching in the
# RRF fusion. Recommended value is 0.7, which provides a good balance between
# semantic (vector) similarity and lexical (BM25) matching. Values close to 0
# emphasize BM25 keyword matching, values close to 1 emphasize vector semantic
# similarity.
required:
- query
steps:
- backend: search_v2_step
vector_weight: 0.7
candidate_multiplier: 5.0
expand_links: false
max_links_per_direction: 10
add_draft:
backend: base
description: "Append text to the current draft list."
@ -326,7 +206,7 @@ jobs:
move:
backend: base
description: "Move / rename a workspace file."
description: "Move / rename a workspace file; rewrites inbound wikilinks by default."
parameters:
type: object
properties:
@ -338,9 +218,11 @@ jobs:
description: "workspace-relative destination"
overwrite:
type: boolean
description: "overwrite if dst exists"
default: false
retarget:
type: boolean
description: "rewrite [[src]] → [[dst]] across the workspace"
default: true
required:
- src_path
@ -359,8 +241,10 @@ jobs:
description: "workspace-relative path; markdown only"
start_line:
type: integer
description: "first line (1-based, inclusive)"
end_line:
type: integer
description: "last line (1-based, inclusive)"
required:
- path
steps:
@ -370,20 +254,25 @@ jobs:
write:
backend: base
description: "Write a markdown file."
description: "Write a markdown file (create or overwrite) with name/description frontmatter."
parameters:
type: object
properties:
path:
type: string
description: "workspace-relative path; markdown only"
name:
type: string
description: "frontmatter name"
description:
type: string
description: "frontmatter description"
content:
type: string
description: "body"
metadata:
type: object
description: "Optional extra frontmatter fields (md only)."
required:
- path
- name
@ -394,23 +283,29 @@ jobs:
daily_write:
backend: base
description: "Write a daily markdown note."
description: "Write a daily markdown note with conversation source frontmatter."
parameters:
type: object
properties:
name:
type: string
description: "daily note filename stem and frontmatter name"
description:
type: string
description: "frontmatter description"
session_id:
type: string
description: "source conversation session identifier"
content:
type: string
description: "body"
date:
type: string
description: "YYYY-MM-DD daily note date; empty = today"
default: ""
metadata:
type: object
description: "Optional extra frontmatter fields."
required:
- name
- description
@ -421,16 +316,19 @@ jobs:
edit:
backend: base
description: "Find-and-replace in a markdown file."
description: "Find-and-replace in a markdown file (all occurrences)."
parameters:
type: object
properties:
path:
type: string
description: "workspace-relative path"
old:
type: string
description: "text to find"
new:
type: string
description: "replacement"
default: ""
required:
- path
@ -447,6 +345,7 @@ jobs:
properties:
path:
type: string
description: "workspace-relative path"
required:
- path
steps:
@ -454,14 +353,16 @@ jobs:
node_search:
backend: base
description: "Digest node recall."
description: "Digest node recall — given a candidate abstraction's name+description, surface existing digest nodes similar enough to either dedup against or link to as related."
parameters:
type: object
properties:
query:
type: string
description: "search query"
limit:
type: integer
description: "max digest nodes to return"
default: 20
required:
- query
@ -495,7 +396,6 @@ components:
model: ${LLM_MODEL_NAME:-qwen3.6-flash}
stream: true
context_size: 200000
max_retries: 3
retry_delay: 5.0
credential:
api_key: ${LLM_API_KEY:-}

View file

@ -1,17 +1,5 @@
"""Benchmark steps."""
"""Shared benchmark steps; concrete implementations live in plugins."""
from . import base, lme, beam
from .base import BaseAgenticAnswerStep
from .lme import LmeAgenticAnswerStep, LmeAnswerJudgeStep
from .beam import BeamAgenticAnswerStep, BeamRubricJudgeStep
from .base_agentic_answer import BaseAgenticAnswerStep
__all__ = [
"BaseAgenticAnswerStep",
"LmeAgenticAnswerStep",
"LmeAnswerJudgeStep",
"BeamAgenticAnswerStep",
"BeamRubricJudgeStep",
"base",
"lme",
"beam",
]
__all__ = ["BaseAgenticAnswerStep"]

View file

@ -1,7 +0,0 @@
"""Shared base classes for benchmark steps."""
from .agentic_answer import BaseAgenticAnswerStep
__all__ = [
"BaseAgenticAnswerStep",
]

View file

@ -2,14 +2,14 @@
import os
from ...base_step import BaseStep
from ...index._dedup import _ToolContextDedupMixin
from ....enumeration import ChunkEnum
from ....utils.counter import global_counter_inc
from ..base_step import BaseStep
from ..index._dedup import _ToolContextDedupMixin
from ...enumeration import ChunkEnum
from ...utils.counter import global_counter_inc
class BaseAgenticAnswerStep(BaseStep):
"""Base ReAct-agent answer step shared by BEAM and LongMemEval benchmarks.
"""ReAct-agent answer implementation shared by benchmark plugins.
Subclasses only need to set:
TOOL_CONTEXT_PREFIX (str): prefix used to build the unique tool_context_id.
@ -18,7 +18,7 @@ class BaseAgenticAnswerStep(BaseStep):
tool call via ``injected_job_kwargs``; override the attribute or the
``_injected_job_kwargs`` hook to customize.
And apply their own ``@R.register(...)`` decorator and docstring.
Concrete subclasses are registered by the plugin manifest.
Inputs (from RuntimeContext):
query (str, required): The question to answer.
@ -43,7 +43,7 @@ class BaseAgenticAnswerStep(BaseStep):
``INJECTED_JOB_KWARGS`` with per-request values derived from ``query``.
When the runtime context carries a truthy ``compress_session`` flag,
session-transcript compression is enabled in ``search_v2_step`` by
session-transcript compression is enabled in the benchmark plugin's search Step by
injecting a ``_search._compress.session`` marker plus the current
``query`` as the query-aware relevance filter. Default (falsy) leaves
session chunks uncompressed.

Some files were not shown because too many files have changed in this diff Show more