fix: harden the 0.4.1.7 release configuration (#456)
Some checks failed
NPM Format / Website checks (push) Has been cancelled
GitHub Pages Check / test-and-build (push) Has been cancelled
Package Check / distributions (push) Has been cancelled
Deploy ReMe documentation / build (push) Has been cancelled
Windows Smoke / CLI smoke - py3.11 (push) Has been cancelled
Pre-commit / run (ubuntu-latest) (push) Has been cancelled
Tests ReMe / Unit Tests - py3.11 (push) Has been cancelled
Tests ReMe / Unit Tests - py3.12 (push) Has been cancelled
Tests ReMe / Unit Tests - py3.13 (push) Has been cancelled
Deploy ReMe documentation / deploy (push) Has been cancelled

* fix(packaging): harden the Studio release workflow

* chore(daily-paper): tune scheduled discovery defaults

* fix(docs): link the ReMe blog to GitHub Pages

* fix(docs): increase Chinese hero title spacing

* refactor(docs): share hero title line spacing

* fix(docs): keep desktop hero copy on two lines

* fix(docs): widen the home hero description

* style(docs): loosen hero title line height

* fix(docs): hide Markdown frontmatter in rendered pages

* docs(readme): simplify installation and remove standalone ReMe Studio instructions

- Remove references to separate ReMe Studio package and static build steps
- Clarify that `core` extra includes common integrations including Studio
- Update installation instructions to use `pip install -e ".[core]"`
- Remove detailed Studio usage and frontend development instructions
- Note that Studio is included with `core` and optional via `web` extra
- Simplify Quick Start guide by removing Studio usage step
- Remove mentions of serving Studio with HTTP service when using extras
- Update both English and Chinese README files accordingly

* docs(readme): streamline and clarify memory design and operations

- Remove redundant explanations about core extra installation
- Simplify memory processing flow description for clarity
- Clarify memory workspace directory default and customization
- Condense automatic memory flow to emphasize rebuildable metadata
- Refine search functionality explanation with RRF fusion details
- Shorten and clarify agent integration description, removing redundancy
- Update and simplify the operations command list, removing less common commands
- Revise community and support section for conciseness and clarity
- Maintain parallel updates in both English and Chinese README files

* test(bump_version): add tests for version bumping and consistency checks

- Add dynamic loading of bump_version and package_studio scripts for testing
- Test that studio package and dependencies have matching versions
- Implement fixtures to write temporary version files for testing
- Add test ensuring bump_version updates all relevant files and dependencies
- Add test to reject inconsistent version sources before writing
- Refactor tests to use common REPOSITORY path variable
- Include imports and setup for pytest in test file

feat(bump_version): create script to update ReMe and Studio versions

- Implement version reading from __init__.py and pyproject.toml files
- Validate current versions are consistent across files before updating
- Update version strings atomically to avoid partial writes
- Ensure exact pinning of studio dependency in main package extras
- Validate new version format against a safe pattern
- Provide CLI interface to bump versions from command line
- Raise errors if expected version declarations or pins are missing or duplicated

* fix(release): validate split package publishing

* fix(release): improve validation diagnostics

* fix(release): sync docs and workflow inputs

* fix(release): split PyPI publish jobs
This commit is contained in:
jinliyl 2026-08-13 17:22:00 +08:00 committed by GitHub
parent 2a05914150
commit fd2894f939
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 822 additions and 226 deletions

View file

@ -0,0 +1,61 @@
name: GitHub Pages Check
on:
push:
branches: [main, master, dev, develop]
paths:
- '.github/workflows/github-pages-check.yml'
- 'AGENTS.md'
- 'README.md'
- 'README_ZH.md'
- 'docs/**'
- 'github-pages/**'
- 'website/README*.md'
- 'website/public/og.jpg'
- 'cookbook/*/README*.md'
- 'benchmark/*/README*.md'
- 'skills/reme_memory/SKILL.md'
pull_request:
branches: [main, master, dev, develop]
paths:
- '.github/workflows/github-pages-check.yml'
- 'AGENTS.md'
- 'README.md'
- 'README_ZH.md'
- 'docs/**'
- 'github-pages/**'
- 'website/README*.md'
- 'website/public/og.jpg'
- 'cookbook/*/README*.md'
- 'benchmark/*/README*.md'
- 'skills/reme_memory/SKILL.md'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
test-and-build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: github-pages
steps:
- uses: actions/checkout@v6
- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: '22.13'
cache: npm
cache-dependency-path: github-pages/package-lock.json
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build documentation
run: npm run build

97
.github/workflows/package-check.yml vendored Normal file
View file

@ -0,0 +1,97 @@
name: Package Check
on:
push:
branches: [main, master, dev, develop]
paths:
- '.github/workflows/package-check.yml'
- '.github/workflows/python-publish.yml'
- 'packages/reme_ai_studio/**'
- 'pyproject.toml'
- 'reme/__init__.py'
- 'reme/utils/web_static.py'
- 'scripts/bump_version.py'
- 'scripts/package_studio.py'
- 'tests/unit/test_package_versions.py'
- 'website/**'
- 'LICENSE'
pull_request:
branches: [main, master, dev, develop]
paths:
- '.github/workflows/package-check.yml'
- '.github/workflows/python-publish.yml'
- 'packages/reme_ai_studio/**'
- 'pyproject.toml'
- 'reme/__init__.py'
- 'reme/utils/web_static.py'
- 'scripts/bump_version.py'
- 'scripts/package_studio.py'
- 'tests/unit/test_package_versions.py'
- 'website/**'
- 'LICENSE'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
distributions:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '22.13'
cache: npm
cache-dependency-path: website/package-lock.json
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install build dependencies
run: |
python -m pip install --upgrade pip
python -m pip install build packaging pytest twine
- name: Validate release versions
run: python scripts/bump_version.py --check
- name: Run package tests
run: PYTHONPATH=. python -m pytest tests/unit/test_package_versions.py -q
- name: Build Studio static workspace
working-directory: website
run: |
npm ci
npm run build:static
- name: Build and check distributions
run: |
python scripts/package_studio.py
mkdir -p dist/reme dist/studio
python -m build --outdir dist/reme
python -m build packages/reme_ai_studio --outdir dist/studio
python -m twine check dist/reme/* dist/studio/*
- name: Verify distributions and isolated installation
run: |
REME_WHEEL="$(pwd)/$(ls dist/reme/reme_ai-[0-9]*.whl)"
STUDIO_WHEEL="$(pwd)/$(ls dist/studio/reme_ai_studio-*.whl)"
STUDIO_SDIST="$(pwd)/$(ls dist/studio/reme_ai_studio-*.tar.gz)"
python -m zipfile -l "${REME_WHEEL}" | (! grep 'reme/web/')
python -m zipfile -l "${STUDIO_WHEEL}" | grep 'reme_ai_studio/static/index.html'
python -m zipfile -l "${STUDIO_WHEEL}" | grep 'dist-info/licenses/LICENSE'
python -m tarfile -l "${STUDIO_SDIST}" | grep '/LICENSE'
python -m venv "${RUNNER_TEMP}/reme-package-smoke"
"${RUNNER_TEMP}/reme-package-smoke/bin/python" -m pip install \
--find-links "$(pwd)/dist/studio" "${REME_WHEEL}[core]"
cd "${RUNNER_TEMP}"
"${RUNNER_TEMP}/reme-package-smoke/bin/python" -c \
"import reme; from reme_ai_studio import static_dir; assert (static_dir() / 'index.html').is_file()"
"${RUNNER_TEMP}/reme-package-smoke/bin/python" -c \
"from reme.utils import resolve_web_static_dir; assert (resolve_web_static_dir() / 'index.html').is_file()"

View file

@ -8,6 +8,10 @@ on:
- "docs/**"
- "README.md"
- "README_ZH.md"
- "website/README*.md"
- "website/public/og.jpg"
- "cookbook/*/README*.md"
- "benchmark/*/README*.md"
- "skills/reme_memory/SKILL.md"
- "AGENTS.md"
- ".github/workflows/pages.yml"

View file

@ -1,15 +1,12 @@
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: Publish Python Package to Pypi
name: Publish Python packages to PyPI
on:
workflow_dispatch:
inputs:
version:
description: Release version
required: true
type: string
release:
types: [published]
@ -17,47 +14,110 @@ permissions:
contents: read
jobs:
deploy:
build:
runs-on: ubuntu-latest
env:
RELEASE_VERSION: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.version }}
steps:
- uses: actions/checkout@v6
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
cache-dependency-path: website/package-lock.json
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Build web workspace
working-directory: website
run: |
npm ci
npm run build:static
- name: Prepare Studio package
run: python scripts/package_studio.py
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel build
- name: Build package
run: |
python -m build --outdir dist
python -m build packages/reme_ai_studio --outdir dist
- name: Test installation
run: |
REME_WHEEL="$(pwd)/$(ls dist/reme_ai-[0-9]*.whl)"
python -m zipfile -l "${REME_WHEEL}" | (! grep 'reme/web/')
pip install --find-links "$(pwd)/dist" "${REME_WHEEL}[core]"
python -c "import reme; print(reme.__version__)"
python -c "from reme_ai_studio import static_dir; assert (static_dir() / 'index.html').is_file()"
python -c "from reme.utils import resolve_web_static_dir; assert (resolve_web_static_dir() / 'index.html').is_file()"
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
- uses: actions/checkout@v6
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '22.13'
cache: npm
cache-dependency-path: website/package-lock.json
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install build dependencies
run: |
python -m pip install --upgrade pip
python -m pip install build packaging twine
- name: Validate release version
run: python scripts/bump_version.py --check --expected-version "${RELEASE_VERSION}"
- name: Build Studio static workspace
working-directory: website
run: |
npm ci
npm run build:static
- name: Prepare and build distributions
run: |
python scripts/package_studio.py
mkdir -p dist/reme dist/studio
python -m build --outdir dist/reme
python -m build packages/reme_ai_studio --outdir dist/studio
python -m twine check dist/reme/* dist/studio/*
- name: Verify distributions and isolated installation
run: |
REME_WHEEL="$(pwd)/$(ls dist/reme/reme_ai-[0-9]*.whl)"
STUDIO_WHEEL="$(pwd)/$(ls dist/studio/reme_ai_studio-*.whl)"
STUDIO_SDIST="$(pwd)/$(ls dist/studio/reme_ai_studio-*.tar.gz)"
python -m zipfile -l "${REME_WHEEL}" | (! grep 'reme/web/')
python -m zipfile -l "${STUDIO_WHEEL}" | grep 'reme_ai_studio/static/index.html'
python -m zipfile -l "${STUDIO_WHEEL}" | grep 'dist-info/licenses/LICENSE'
python -m tarfile -l "${STUDIO_SDIST}" | grep '/LICENSE'
python -m venv "${RUNNER_TEMP}/reme-release-smoke"
"${RUNNER_TEMP}/reme-release-smoke/bin/python" -m pip install \
--find-links "$(pwd)/dist/studio" "${REME_WHEEL}[core]"
cd "${RUNNER_TEMP}"
"${RUNNER_TEMP}/reme-release-smoke/bin/python" -c \
"import reme; from reme_ai_studio import static_dir; assert (static_dir() / 'index.html').is_file()"
"${RUNNER_TEMP}/reme-release-smoke/bin/python" -c \
"from reme.utils import resolve_web_static_dir; assert (resolve_web_static_dir() / 'index.html').is_file()"
- name: Upload ReMe Studio distributions
uses: actions/upload-artifact@v4
with:
name: reme-studio-distributions
path: dist/studio/
- name: Upload ReMe distributions
uses: actions/upload-artifact@v4
with:
name: reme-distributions
path: dist/reme/
publish-studio:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download ReMe Studio distributions
uses: actions/download-artifact@v4
with:
name: reme-studio-distributions
path: dist/studio
- name: Publish ReMe Studio
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages-dir: dist/studio
skip-existing: true
publish-reme:
needs: publish-studio
runs-on: ubuntu-latest
steps:
- name: Download ReMe distributions
uses: actions/download-artifact@v4
with:
name: reme-distributions
path: dist/reme
- name: Publish ReMe
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages-dir: dist/reme
skip-existing: true

View file

@ -23,6 +23,13 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm
cache-dependency-path: website/package-lock.json
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
@ -34,6 +41,19 @@ jobs:
python -m pip install --upgrade pip setuptools wheel
pip install -e packages/reme_ai_studio -e ".[dev,core]"
- name: Build Studio static workspace
working-directory: website
run: |
npm ci
npm run build:static
- name: Verify editable source installation serves Studio
shell: pwsh
run: |
Push-Location $env:RUNNER_TEMP
python -c "from reme.utils import resolve_web_static_dir; assert (resolve_web_static_dir() / 'index.html').is_file()"
Pop-Location
- name: Run version job
run: reme start service.backend=cli job=version

View file

@ -64,7 +64,7 @@ and concise documentation together.
ReMe requires Python 3.11 or newer. Install the editable development environment with:
```bash
pip install -e ".[dev,core]"
pip install -e packages/reme_ai_studio -e ".[dev,core]"
```
Before changing behavior, inspect the adjacent implementation, schema, built-in config, and focused tests. Follow

View file

@ -61,10 +61,8 @@ keeping the files under the user's control.
## 📰 News
- [2026.08] - Published the [ReMe blog](docs/en/reme-blog.md), an end-to-end introduction to its local-first memory
- [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
architecture, self-evolving workflows, hybrid search, proactive discovery, and benchmark results.
- [2026.08] - Introduced [ReMe Studio](https://reme.agentscope.io/?doc=studio-en), a local web workspace for browsing, editing, and searching
memory files, chatting with the read-only ReMe Agent, inspecting the digest wikilink graph, and managing the local service.
- [2026.08] - [Experience-driven enhancement method](https://reme.agentscope.io/?doc=toolmemory-en) of agent tool-use execution built
on ReMe is available on [arXiv:2608.03403](https://arxiv.org/abs/2608.03403).
- [2026.07] - Introduced optional Cookbooks: [Daily Paper](https://reme.agentscope.io/?doc=daily-paper-en) for paper discovery and
@ -86,18 +84,20 @@ Install from pip:
pip install "reme-ai[core]"
```
The base `reme-ai` package contains only the Python service and library. ReMe Studio is distributed separately and is
installed by the `web` and `core` extras. Use `pip install reme-ai` for embedded or headless integrations that do not
need the frontend, or `pip install "reme-ai[web]"` when Studio is needed without the other `core` integrations.
Install from source:
```bash
git clone https://github.com/agentscope-ai/ReMe.git
cd ReMe
pip install -e packages/reme_ai_studio -e ".[core]"
cd website
npm ci
npm run build:static
cd ..
```
The static build requires Node.js 22.13 or newer and makes Studio available from the source tree.
### Environment Variables
Configure environment variables when you want LLM-powered memory evolution or embedding retrieval. Embeddings are
@ -136,14 +136,6 @@ reme start service.port=8181
# reme start workspace_dir=/tmp/reme-demo service.port=8181
```
After startup, check the service status. If you use a custom port, replace `2333` in the URL below with that port.
When the `web` or `core` extra is installed, the HTTP service also serves **ReMe Studio** at
<http://127.0.0.1:2333/>. Studio can
browse, edit, and search the workspace, chat with the read-only workspace agent, and inspect the digest wikilink graph.
Set `service.web_enabled=false` to disable it, or use `service.web_static_dir` / `REME_WEB_STATIC_DIR` to provide a
custom static build. The Job API remains available when no web build is found.
```bash
reme version
reme health_check
@ -151,32 +143,11 @@ reme help
curl -s http://127.0.0.1:2333/version -H 'Content-Type: application/json' -d '{}'
```
### Use ReMe Studio
### ReMe Studio (Optional)
Open <http://127.0.0.1:2333/> after starting the default HTTP service. Studio provides:
- **Files, Daily, and Knowledge views** for navigating the whole workspace or focusing on `daily/` and `digest/`.
- **Markdown tabs** with preview, split editing, optimistic save checks, and local download.
- **Memory Graph** for exploring indexed `personal`, `procedure`, and `wiki` nodes and opening their Markdown sources.
- **Read-only Agent chat** with streamed tool activity and usage; drag a workspace file into the composer to reference it.
- **Settings** for service/component status, redacted effective configuration, version information, and safe index rebuilding.
- English/Chinese language switching and light, dark, or system appearance.
For frontend development, run ReMe and Studio in separate terminals:
```bash
# Terminal 1, repository root
reme start
# Terminal 2
cd website
npm install
npm run dev
```
Then open <http://localhost:3000>. The development server uses `http://127.0.0.1:2333` by default; set
`NEXT_PUBLIC_REME_API_URL` to connect to another ReMe HTTP service. Static-build and frontend configuration instructions are
in the [ReMe Studio guide](https://reme.agentscope.io/?doc=studio-en).
The `core` installation above 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.
### 5-Minute Memory Demo
@ -218,7 +189,7 @@ These Markdown guides cover the main user workflows and the runtime contracts im
| Guide | What you will learn |
|-------|---------------------|
| [Quick Start](docs/en/quick_start.md) | Install ReMe, start the service, use Studio, and run the first file and memory operations. |
| [Quick Start](docs/en/quick_start.md) | Install ReMe, start the service, and run the first file and memory operations. |
| [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 resources and turn them into source-linked daily cards. |
@ -227,8 +198,7 @@ These Markdown guides cover the main user workflows and the runtime contracts im
| [Proactive](docs/en/proactive.md) | Read interest topics safely and integrate them into a host agent's decision flow. |
| [Agent Integration Scenarios](docs/en/reme_scene.md) | Choose among CLI/SKILL.md, HTTP, MCP, and embedded Python integration. |
| [Framework](docs/en/framework.md) | Understand Application, Job, Step, Component, service, configuration, and lifecycle boundaries. |
| [ReMe Studio](https://reme.agentscope.io/?doc=studio-en) | Use, configure, develop, test, and build the web frontend. |
| [ReMe Blog](docs/en/reme-blog.md) | Read the product story, design rationale, examples, and benchmark summary. |
| [ReMe Blog](https://agentscope-ai.github.io/ReMe/?doc=en-reme-blog) | Read the product story, design rationale, examples, and benchmark summary. |
## 🧑‍🍳 Cookbooks
@ -246,9 +216,8 @@ another row in this table.
> Memory as File, File as Memory.
ReMe treats **memory as files**, progressively processing filtered conversation source records and external resources
from `session/` and `resource/` into `daily/`, then consolidating them into reusable long-term memory nodes under
`digest/`. The default workspace is `.reme/` under the current directory; `workspace_dir=...` selects a different
user-owned location.
from `session/` and `resource/` into `daily/`, then `digest/`. The default workspace is `.reme/` under the current
directory; `workspace_dir=...` selects a different user-owned location.
### Directory Structure
@ -288,16 +257,8 @@ user-owned location.
## 🧭 Memory Design Philosophy
> Capture conversation source records and resources, refine them into long-term preferences, reusable experience, and
> valuable knowledge,
> while keeping the result editable by humans and agents.
### Automatic Memory Flow
ReMe follows a capture → index → consolidate → recall loop. Conversations and resources first become daily memory cards;
background jobs keep files searchable; `auto_dream` distills stable knowledge into `digest/`; agents recall memory
through search, wikilinks, or proactive topics. The files are the durable source of truth—indexes, graphs, catalogs, and
caches under `metadata/` can be rebuilt from them.
ReMe follows a capture → index → consolidate → recall loop. Workspace files remain the durable source of truth;
everything under `metadata/` is rebuildable.
| Capability | Entry point | What it does | Output |
|---------------------------------------------|-------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------|
@ -326,10 +287,8 @@ caches under `metadata/` can be rebuilt from them.
</tr>
</table>
Search returns the best matching chunks with file paths and line ranges, then lists bounded incoming and outgoing
wikilink neighbors by metadata. An agent can read a promising source or traverse the graph only when needed. With
embeddings enabled, BM25 and vector rankings are fused with reciprocal rank fusion (RRF); otherwise the default remains
BM25 plus wikilink expansion.
Search returns matching chunks with line ranges and bounded wikilink neighbors. Optional vector results are fused with
BM25 through reciprocal rank fusion (RRF).
> [!IMPORTANT]
> `proactive` only reads and exposes interest topics produced by Auto Dream. It does not independently browse the web,
@ -355,8 +314,7 @@ dependencies, and underspecified requests.
## 🤝 Agent-friendly Integration
ReMe can run as a local memory service accessed through the CLI, HTTP API, or MCP server, or it can be embedded in the
host process through its Python API. The default HTTP service can serve ReMe Studio at the same address. Agents can
choose the path that fits their runtime and share a local memory workspace when appropriate.
host process through its Python API.
| Agents | Recommended path | Available after integration |
|-----------------------------------------------|---------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
@ -395,28 +353,21 @@ choose the path that fits their runtime and share a local memory workspace when
## 🛠️ ReMe Operations
ReMe operates the workspace through a unified job interface exposed by the CLI. Agents usually only need retrieval,
reading, writing, editing, and automatic memory commands. Lower-level indexing, frontmatter, and file operation commands
are mainly for maintenance, debugging, or advanced integration. Run `reme help` for the full job list.
Run `reme help` for the full job list. Common workspace and maintenance commands are:
| Command | Purpose |
|-------------------------------------------|----------------------------------------------------------------------------------------|
| `reme start` | Start the local ReMe service. |
| `reme version` / `reme health_check` | Check package and component status. |
| `reme status` | Show stateful data-component memory estimates and process RSS. |
| [`reme search`](docs/en/memory_search.md) | Retrieve memory with BM25 and wikilinks by default, plus vectors when enabled. |
| `reme read` / `reme write` / `reme edit` | Inspect and maintain Markdown memory files. |
| `reme traverse` / `reme graph_snapshot` | Explore wikilink neighborhoods or the category-rooted digest graph. |
| `reme chat` | Stream a read-only, workspace-aware agent conversation. Requires LLM credentials. |
| `reme auto_memory` | Turn conversation messages into daily memory cards. Requires LLM credentials. |
| `reme auto_resource` | Interpret files under `resource/` into daily resource cards. Requires LLM credentials. |
| `reme auto_dream` / `reme proactive` | Consolidate daily memory into long-term digest and surface topics worth attention. |
| `reme reindex` | Rebuild search and wikilink indexes from existing files. |
## 🤝 Community and Support
- **Issues and requests**: Check [Open Issues](https://github.com/agentscope-ai/ReMe/issues) first. If there is no
related discussion, open a new issue with background, expected behavior, and impact scope.
- **Issues, requests, and help**: Check [Open Issues](https://github.com/agentscope-ai/ReMe/issues) first. If there is no
related discussion, open one with the background, expected behavior, and impact scope.
- **Code contributions**: Before making changes, read
the [contribution guide](https://docs.agentscope.io/reme/latest/en/contribution). Source, schemas, and tests are the
authoritative architecture and extension guide.
@ -426,8 +377,7 @@ are mainly for maintenance, debugging, or advanced integration. Run `reme help`
`docs(zh): update quick start`.
- **Pre-submit checks**: Before submitting a PR, try to run `pre-commit run --all-files` and `pytest`. If tests that
depend on LLMs, embeddings, or external services cannot run, explain that in the PR.
- **Get help**: Use [GitHub Issues](https://github.com/agentscope-ai/ReMe/issues) for bugs and feature requests. Project
documentation is available at [https://reme.agentscope.io](https://reme.agentscope.io).
- **Documentation**: Visit [reme.agentscope.io](https://reme.agentscope.io).
### Contributors

View file

@ -53,10 +53,8 @@ Code 等 Agent 协作,在持续整理知识的同时,始终把文件控制
## 📰 新闻
- [2026.08] - 发布 [ReMe 博客](docs/zh/reme-blog.md),系统介绍本地优先的记忆架构、自进化工作流、混合检索、
- [2026.08] - 发布 [ReMe 博客](https://agentscope-ai.github.io/ReMe/?doc=zh-reme-blog),系统介绍本地优先的记忆架构、自进化工作流、混合检索、
主动发现与评测结果。
- [2026.08] - 新增 [ReMe Studio](https://reme.agentscope.io/?doc=studio-zh):用于浏览、编辑和搜索记忆文件,与只读 ReMe Agent
对话,查看 digest wikilink 图,并管理本地服务。
- [2026.08] - 基于 ReMe 的智能体工具使用
[经验驱动增强方法](https://reme.agentscope.io/?doc=toolmemory-zh)已发布,见
[arXiv:2608.03403](https://arxiv.org/abs/2608.03403)。
@ -84,9 +82,15 @@ pip install "reme-ai[core]"
```bash
git clone https://github.com/agentscope-ai/ReMe.git
cd ReMe
pip install -e ".[core]"
pip install -e packages/reme_ai_studio -e ".[core]"
cd website
npm ci
npm run build:static
cd ..
```
静态构建要求 Node.js 22.13 或更高版本,并让源码安装可以直接使用 Studio。
### 环境变量
如果需要 LLM 驱动的记忆演化或 embedding 检索可以配置环境变量。embedding 默认关闭,因此默认配置不会启动 embedding 模型,也不需要
@ -125,13 +129,6 @@ reme start service.port=8181
# reme start workspace_dir=/tmp/reme-demo service.port=8181
```
启动后可以检查服务状态;如果使用了自定义端口,请将下面 URL 中的 `2333` 替换为对应端口。
如果安装包中包含 Web 构建产物HTTP 服务还会在 <http://127.0.0.1:2333/> 提供 **ReMe Studio**,用于浏览、编辑和搜索
workspace与只读 workspace agent 对话,以及查看 digest wikilink 图。可以设置
`service.web_enabled=false` 关闭,或通过 `service.web_static_dir` / `REME_WEB_STATIC_DIR` 指定自定义静态目录。找不到 Web
构建产物时Job API 仍可正常使用。
```bash
reme version
reme health_check
@ -139,31 +136,11 @@ reme help
curl -s http://127.0.0.1:2333/version -H 'Content-Type: application/json' -d '{}'
```
### 使用 ReMe Studio
### ReMe Studio(可选)
启动默认 HTTP 服务后,在浏览器打开 <http://127.0.0.1:2333/>。Studio 提供:
- **文件、日记和知识库视图**:浏览整个 workspace或聚焦 `daily/``digest/`
- **Markdown 多标签页**:支持预览、分栏编辑、基于修改时间的冲突检查、保存和本地下载。
- **记忆图谱**:浏览已索引的 `personal``procedure``wiki` 节点,并打开对应 Markdown 源文件。
- **只读 Agent 对话**:流式查看工具调用和模型用量;可将 workspace 文件拖入输入框作为引用。
- **设置与服务管理**:查看服务/组件状态、脱敏后的生效配置和版本,并安全重建派生索引。
- 支持中英文切换,以及浅色、深色和跟随系统外观。
如需开发前端,在两个终端中分别启动 ReMe 和 Studio
```bash
# 终端 1仓库根目录
reme start
# 终端 2
cd website
npm install
npm run dev
```
然后打开 <http://localhost:3000>。开发服务默认连接 `http://127.0.0.1:2333`;如需连接其他 ReMe HTTP
服务,请设置 `NEXT_PUBLIC_REME_API_URL`。静态构建和前端配置说明见 [ReMe Studio 指南](https://reme.agentscope.io/?doc=studio-zh)。
上面的 `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)。
### 5 分钟记忆 Demo
@ -205,7 +182,7 @@ ReMe 会把 Agent 记忆保存为可读的 Markdown。
| 文档 | 主要内容 |
|------|----------|
| [快速开始](docs/zh/quick_start.md) | 安装 ReMe、启动服务、使用 Studio,并执行首次文件和记忆操作。 |
| [快速开始](docs/zh/quick_start.md) | 安装 ReMe、启动服务并执行首次文件和记忆操作。 |
| [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 卡片。 |
@ -214,8 +191,7 @@ ReMe 会把 Agent 记忆保存为可读的 Markdown。
| [Proactive](docs/zh/proactive.md) | 安全读取兴趣主题,并将其接入宿主 Agent 的决策流程。 |
| [Agent 接入场景](docs/zh/reme_scene.md) | 在 CLI/SKILL.md、HTTP、MCP 和嵌入式 Python 集成之间选择。 |
| [框架说明](docs/zh/framework.md) | 理解 Application、Job、Step、Component、service、配置和生命周期边界。 |
| [ReMe Studio](https://reme.agentscope.io/?doc=studio-zh) | 使用、配置、开发、测试和构建 Web 前端。 |
| [ReMe 博客](docs/zh/reme-blog.md) | 了解完整产品故事、设计动机、使用示例和评测摘要。 |
| [ReMe 博客](https://agentscope-ai.github.io/ReMe/?doc=zh-reme-blog) | 了解完整产品故事、设计动机、使用示例和评测摘要。 |
## 🧑‍🍳 Cookbooks
@ -231,8 +207,8 @@ cookbook 会继续在表格中按行追加。
> Memory as File, File as Memory.
ReMe 将 **记忆视为文件**,让过滤后的对话来源记录和外部资料从 `session/``resource/` 渐进加工到 `daily/`,再沉淀为 `digest/`
中可长期复用的知识节点。默认 workspace 是当前目录下的 `.reme/`;可通过 `workspace_dir=...` 选择其他由用户控制的位置。
ReMe 将 **记忆视为文件**,让过滤后的对话来源记录和外部资料从 `session/``resource/` 渐进加工到 `daily/`,再沉淀为
`digest/`。默认 workspace 是当前目录下的 `.reme/`;可通过 `workspace_dir=...` 选择其他由用户控制的位置。
### 目录结构
@ -272,13 +248,7 @@ ReMe 将 **记忆视为文件**,让过滤后的对话来源记录和外部资
## 🧭 记忆设计理念
> 捕获过滤后的对话来源记录和资料,将其整理为长期偏好、可复用经验和有价值的知识,并让结果始终能被用户和 Agent 直接编辑。
### 自动记忆流程
ReMe 遵循 capture → index → consolidate → recall 的循环。对话和资料先变成 daily 记忆卡片;后台任务保持文件可检索;
`auto_dream` 将稳定知识沉淀到 `digest/`Agent 再通过搜索、wikilink 或 proactive topics 召回记忆。文件是持久化的事实来源,
`metadata/` 中的索引、图谱、catalog 和缓存都可以由它们重建。
ReMe 遵循 capture → index → consolidate → recall 的循环。workspace 文件是持久化的事实来源,`metadata/` 中的内容均可重建。
| 能力 | 入口 | 作用 | 输出 |
|---------------------------------------------|-------------------------------------------|----------------------------------------------------------------------------------------------|--------------------------------------------------------------|
@ -307,8 +277,7 @@ ReMe 遵循 capture → index → consolidate → recall 的循环。对话和
</tr>
</table>
搜索会先返回最相关的 chunks、文件路径和行号范围再以元数据形式列出数量受限的入链与出链邻居。Agent 只需在判断确实相关后再读取原文或继续遍历图谱。
启用 embedding 时BM25 和向量排名通过 RRF 融合;默认未启用 embedding 时,则使用 BM25 + wikilink 扩展。
搜索返回带行号范围的相关 chunks 和数量受限的 wikilink 邻居;可选向量结果通过 RRF 与 BM25 融合。
> [!IMPORTANT]
> `proactive` 只读取并暴露 Auto Dream 生成的兴趣主题,不会自行联网、发送通知或改写知识库;是否以及如何使用主题,由宿主 Agent
@ -330,7 +299,7 @@ ReMe 通过 Agent 多轮搜索与读取的方式,评测多会话和超长上
## 🤝 Agent-friendly Integration
ReMe 既可以作为本地记忆服务,通过 CLI、HTTP API 或 MCP server 接入,也可以通过 Python API 嵌入宿主进程。不同 Agent 可以选择适合自身
runtime 的路径,并按需共享同一个本地 memory workspace。默认 HTTP 服务还可以在同一地址提供 ReMe Studio
runtime 的路径。
| Agent | 推荐接入方式 | 接入后能力 |
|-----------------------------------------------|-------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------|
@ -369,26 +338,20 @@ runtime 的路径,并按需共享同一个本地 memory workspace。默认 HTT
## 🛠️ ReMe Operations
ReMe 通过 CLI 暴露的统一 job interface 操作 workspace。Agent 通常只需要使用检索、读取、写入、编辑和自动记忆相关命令;更底层的索引、
frontmatter 和文件操作接口主要用于维护、调试或高级集成。完整 job 列表可以运行 `reme help` 查看。
运行 `reme help` 可查看完整 job 列表。常用 workspace 与维护命令如下:
| 命令 | 作用 |
|-------------------------------------------|---------------------------------------------------------------|
| `reme start` | 启动本地 ReMe 服务。 |
| `reme version` / `reme health_check` | 检查包版本和组件状态。 |
| `reme status` | 查看有状态数据组件的内存估算及进程 RSS。 |
| [`reme search`](docs/zh/memory_search.md) | 默认使用 BM25 和 wikilink 检索,启用后增加向量检索。 |
| `reme read` / `reme write` / `reme edit` | 检查和维护 Markdown 记忆文件。 |
| `reme traverse` / `reme graph_snapshot` | 浏览 wikilink 邻域或按类别组织的 digest 图。 |
| `reme chat` | 与可感知 workspace 的只读 Agent 进行流式对话;需要 LLM 凭证。 |
| `reme auto_memory` | 将对话 messages 转为 daily 记忆卡片;需要 LLM 凭证。 |
| `reme auto_resource` | 将 `resource/` 下的文件解读为 daily 资料卡片;需要 LLM 凭证。 |
| `reme auto_dream` / `reme proactive` | 将 daily 记忆整理为长期 digest并暴露值得关注的主题。 |
| `reme reindex` | 基于已有文件重建检索和 wikilink 索引。 |
## 🤝 社区与支持
- **问题反馈与需求**:请先查看 [Open Issues](https://github.com/agentscope-ai/ReMe/issues);如无相关讨论,可新建 Issue
- **问题反馈、需求与帮助**:请先查看 [Open Issues](https://github.com/agentscope-ai/ReMe/issues);如无相关讨论,可新建 Issue
说明背景、目标行为和影响范围。
- **代码贡献**:改动前建议阅读 [贡献指南](https://docs.agentscope.io/reme/latest/zh/contribution)。架构与扩展方式以源码、schema
和测试为准。
@ -397,8 +360,7 @@ frontmatter 和文件操作接口主要用于维护、调试或高级集成。
`docs(zh): update quick start`
- **提交前检查**:提交 PR 前请尽量运行 `pre-commit run --all-files``pytest`;如有依赖 LLM、embedding 或外部服务的测试无法运行,请在
PR 中说明。
- **获取帮助**:如需反馈 Bug 或功能请求,请使用 [GitHub Issues](https://github.com/agentscope-ai/ReMe/issues);项目文档见
[https://reme.agentscope.io](https://reme.agentscope.io)。
- **项目文档**:访问 [reme.agentscope.io](https://reme.agentscope.io)。
### 贡献者

View file

@ -182,8 +182,8 @@ when present. The two data sources reach a mirror differently: Hugging Face is g
parameter, while arXiv is driven by its environment variable alone.
```dotenv
# Enable the mirror for the built-in daily_paper_cron job
DAILY_PAPER_USE_HF_MIRROR=true
# The built-in daily_paper_cron job enables the mirror by default; set false to use the official service
DAILY_PAPER_USE_HF_MIRROR=false
# Read only when the manual or scheduled job enables the mirror; defaults to https://hf-mirror.com when unset
HF_MIRROR_URL=https://hf-mirror.com
@ -202,8 +202,8 @@ trailing slash is optional. There is no fallback chain: whichever base URL a cli
> **Behavior change:** `HF_MIRROR_URL` used to redirect Hugging Face traffic on its own. It is now read only when the
> job runs with `use_hf_mirror=true`; otherwise the official service is used and the client logs a warning that the
> variable was ignored. Pass `use_hf_mirror=true` for manual requests, or set
> `DAILY_PAPER_USE_HF_MIRROR=true` for `daily_paper_cron`, to keep an existing mirror-only setup working.
> variable was ignored. Pass `use_hf_mirror=true` for manual requests. The built-in `daily_paper_cron` job enables the
> mirror by default; set `DAILY_PAPER_USE_HF_MIRROR=false` to make that scheduled job use the official service.
## Running the workflow
@ -231,8 +231,9 @@ reme start config=daily_cookbook
```
The built-in service listens on `127.0.0.1:8001`. `daily_paper_cron` runs every day at 08:00 in the
`Asia/Shanghai` timezone. Set `DAILY_PAPER_USE_HF_MIRROR=true` to make that scheduled job use the Hugging Face mirror.
Override the bind address with `DAILY_PAPER_HOST`, `DAILY_PAPER_PORT`, or startup arguments.
`Asia/Shanghai` timezone, prioritizes the topic `大模型长期记忆`, and uses the Hugging Face mirror by default. Set
`DAILY_PAPER_USE_HF_MIRROR=false` to use the official service. Override the bind address with `DAILY_PAPER_HOST`,
`DAILY_PAPER_PORT`, or startup arguments.
```bash
curl -s http://127.0.0.1:8001/daily_paper \

View file

@ -172,8 +172,8 @@ reme_workspace/
Face 由 `use_hf_mirror` 任务参数控制arXiv 仅由环境变量驱动。
```dotenv
# 内置 daily_paper_cron 定时任务启用镜像站
DAILY_PAPER_USE_HF_MIRROR=true
# 内置 daily_paper_cron 定时任务默认启用镜像站;设为 false 可改用官方服务
DAILY_PAPER_USE_HF_MIRROR=false
# 仅在手动任务或定时任务启用镜像时读取;未配置时使用 https://hf-mirror.com
HF_MIRROR_URL=https://hf-mirror.com
@ -192,7 +192,8 @@ URL就只访问该地址。
> **行为变更:** 以往只要设置 `HF_MIRROR_URL` 就会改变 Hugging Face
> 的访问地址;现在该变量仅在任务启用镜像时才会读取,否则直接访问官方站点,并输出一条“已忽略该变量”的告警日志。手动调用需传入
> `use_hf_mirror=true``daily_paper_cron` 定时任务需设置 `DAILY_PAPER_USE_HF_MIRROR=true`,才能继续走镜像。
> `use_hf_mirror=true`。内置 `daily_paper_cron` 定时任务默认启用镜像;设置 `DAILY_PAPER_USE_HF_MIRROR=false`
> 可让该定时任务改用官方服务。
## 运行方式
@ -219,9 +220,9 @@ reme start config=daily_cookbook job=daily_paper date=2026-08-06 force=true
reme start config=daily_cookbook
```
内置服务监听 `127.0.0.1:8001``daily_paper_cron``Asia/Shanghai` 时区每天 08:00 运行。设置
`DAILY_PAPER_USE_HF_MIRROR=true` 可让该定时任务使用 Hugging Face 镜像站。可通过 `DAILY_PAPER_HOST``DAILY_PAPER_PORT`
或启动参数覆盖监听地址和端口。
内置服务监听 `127.0.0.1:8001``daily_paper_cron``Asia/Shanghai` 时区每天 08:00 运行,默认优先关注
`大模型长期记忆`,并使用 Hugging Face 镜像站。设置 `DAILY_PAPER_USE_HF_MIRROR=false` 可改用官方服务。可通过
`DAILY_PAPER_HOST``DAILY_PAPER_PORT` 或启动参数覆盖监听地址和端口。
```bash
curl -s http://127.0.0.1:8001/daily_paper \

View file

@ -40,6 +40,10 @@ The project requires Python 3.11 or later. A virtual environment is recommended:
python -m venv .venv
source .venv/bin/activate
pip install -e packages/reme_ai_studio -e ".[dev,full]"
cd website
npm ci
npm run build:static
cd ..
pre-commit install
```

View file

@ -16,8 +16,14 @@ Install from source:
git clone https://github.com/agentscope-ai/ReMe.git
cd ReMe
pip install -e packages/reme_ai_studio -e ".[core]"
cd website
npm ci
npm run build:static
cd ..
```
The static build step requires Node.js 22.13 or newer and makes Studio available when running ReMe from the source tree.
Installing the `core` extra is recommended. The current code imports the AgentScope wrapper, and self-evolving memory
also depends on it.

View file

@ -38,6 +38,10 @@ ReMe 的核心代码位于:
python -m venv .venv
source .venv/bin/activate
pip install -e packages/reme_ai_studio -e ".[dev,full]"
cd website
npm ci
npm run build:static
cd ..
pre-commit install
```

View file

@ -16,8 +16,14 @@ pip install "reme-ai[core]"
git clone https://github.com/agentscope-ai/ReMe.git
cd ReMe
pip install -e packages/reme_ai_studio -e ".[core]"
cd website
npm ci
npm run build:static
cd ..
```
静态构建步骤需要 Node.js 22.13 或更高版本,用于在从源码运行 ReMe 时提供 Studio。
`core` extra 建议安装:当前代码会导入 AgentScope wrapper自进化记忆也依赖它。
如果要使用 `auto_memory``auto_resource``auto_dream` 这类 Agent 流程,再配置 LLM

View file

@ -9,7 +9,8 @@
"scripts": {
"dev": "node scripts/generate-content.mjs && vite",
"build": "node scripts/generate-content.mjs && vite build",
"preview": "vite preview"
"preview": "vite preview",
"test": "node --test tests/*.test.mjs"
},
"dependencies": {
"dompurify": "^3.2.6",

View file

@ -228,7 +228,7 @@ for (const product of productDocuments) {
}
}
await mkdir(path.join(outputDir, "website", "public"), { recursive: true });
await cp(path.join(repoDir, "website", "public", "og.png"), path.join(outputDir, "website", "public", "og.png"));
await cp(path.join(repoDir, "website", "public", "og.jpg"), path.join(outputDir, "website", "public", "og.jpg"));
await mkdir(path.join(outputDir, "skills", "reme_memory"), { recursive: true });
await cp(
path.join(repoDir, "skills", "reme_memory", "SKILL.md"),

View file

@ -1,5 +1,6 @@
import DOMPurify from "dompurify";
import { marked } from "marked";
import { stripMarkdownFrontmatter } from "./markdown.js";
import "./styles.css";
const baseUrl = import.meta.env.BASE_URL;
@ -370,7 +371,7 @@ async function openDocument(id, pushHistory = true) {
const response = await fetch(`${baseUrl}content/${document.path}`);
if (!response.ok) throw new Error(`Unable to load ${document.path}`);
configureMarkdown(document);
const markdown = await response.text();
const markdown = stripMarkdownFrontmatter(await response.text());
const body = DOMPurify.sanitize(await marked.parse(markdown), {
ADD_ATTR: ["target"],
});

View file

@ -0,0 +1,6 @@
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

@ -91,8 +91,8 @@ a { color: inherit; }
.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 { max-width: 800px; margin: 0; color: #102019; font-size: clamp(46px, 6.3vw, 76px); line-height: 0.99; letter-spacing: -0.055em; }
.home-lead { max-width: 720px; margin: 27px 0 0; color: #526159; font-size: 18px; line-height: 1.72; }
.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); }

View file

@ -0,0 +1,15 @@
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);
});

1
packages/reme_ai_studio/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/LICENSE

View file

@ -1,9 +1,10 @@
[project]
name = "reme-ai-studio"
version = "0.4.1.6"
version = "0.4.1.7"
description = "Optional ReMe Studio static frontend."
readme = "README.md"
license = "Apache-2.0"
license-files = ["LICENSE"]
requires-python = ">=3.11"
[project.urls]
@ -20,5 +21,5 @@ include-package-data = false
"reme_ai_studio" = ["static/**"]
[build-system]
requires = ["setuptools>=45", "wheel"]
requires = ["setuptools>=77", "wheel"]
build-backend = "setuptools.build_meta"

View file

@ -43,7 +43,7 @@ dependencies = [
[project.optional-dependencies]
web = [
"reme-ai-studio==0.4.1.6",
"reme-ai-studio==0.4.1.7",
]
core = [
"agentscope==2.0.4.post1",
@ -58,9 +58,10 @@ core = [
"neo4j>=6.2.0",
"networkx>=3.4.2",
"polars>=1.43.0",
"reme-ai-studio==0.4.1.6",
"reme-ai-studio==0.4.1.7",
]
dev = [
"packaging>=24.2",
"pre-commit>=4.6.1",
"pytest>=9.1.1",
"pytest-asyncio>=0.23",

View file

@ -1,6 +1,6 @@
"""ReMe CLI package."""
__version__ = "0.4.1.6"
__version__ = "0.4.1.7"
from . import config
from . import constants

View file

@ -441,7 +441,8 @@ jobs:
daily_paper_cron:
backend: cron
cron: "0 8 * * *"
use_hf_mirror: ${DAILY_PAPER_USE_HF_MIRROR:-false}
topics: "大模型长期记忆"
use_hf_mirror: ${DAILY_PAPER_USE_HF_MIRROR:-true}
candidate_limit: *candidate_limit
rrf_k: *rrf_k
weekly_weight: *weekly_weight

205
scripts/bump_version.py Normal file
View file

@ -0,0 +1,205 @@
"""Validate or update the ReMe and ReMe Studio release version."""
from __future__ import annotations
import argparse
import os
import re
import tempfile
import tomllib
from pathlib import Path
from packaging.version import InvalidVersion, Version
REPOSITORY_DIR = Path(__file__).resolve().parents[1]
_VERSION_PATTERN = re.compile(r'(?m)^__version__ = "(?P<version>[^"]+)"$')
_STUDIO_VERSION_PATTERN = re.compile(r'(?m)^version = "(?P<version>[^"]+)"$')
def _paths(repository: Path) -> tuple[Path, Path, Path]:
return (
repository / "reme" / "__init__.py",
repository / "pyproject.toml",
repository / "packages" / "reme_ai_studio" / "pyproject.toml",
)
def _canonical_version(value: str) -> str:
"""Return one canonical PEP 440 version or explain how to correct it."""
try:
canonical = str(Version(value))
except InvalidVersion as error:
raise ValueError(
f"Invalid PEP 440 version {value!r}. Use a version such as 0.4.1.8, then rerun this command.",
) from error
if canonical != value:
raise ValueError(f"Version {value!r} is not canonical PEP 440; use {canonical!r} instead.")
return canonical
def _read_toml(source: Path) -> dict:
"""Read one TOML file or identify the file that needs correction."""
try:
return tomllib.loads(source.read_text(encoding="utf-8"))
except (OSError, UnicodeError, tomllib.TOMLDecodeError) as error:
raise ValueError(f"{source}: cannot read valid TOML ({error}). Fix this file and retry.") from error
def _required_table(config: dict, keys: tuple[str, ...], source: Path) -> dict:
"""Return a required TOML table or explain where to add it."""
value: object = config
for key in keys:
if not isinstance(value, dict) or key not in value:
table = ".".join(keys)
raise ValueError(f"{source}: missing or invalid [{table}] table. Add or fix this table and retry.")
value = value[key]
if not isinstance(value, dict):
table = ".".join(keys)
raise ValueError(f"{source}: [{table}] must be a TOML table. Fix this table and retry.")
return value
def _match_version(text: str, pattern: re.Pattern[str], source: Path) -> str:
matches = list(pattern.finditer(text))
if len(matches) != 1:
raise ValueError(f"Expected exactly one version declaration in {source}, found {len(matches)}.")
return matches[0].group("version")
def _replace_version(text: str, pattern: re.Pattern[str], version: str, source: Path) -> str:
_match_version(text, pattern, source)
return pattern.sub(lambda match: match.group(0).replace(match.group("version"), version), text)
def _write_atomic(path: Path, content: str) -> None:
"""Replace one text file without exposing a partially written file."""
temporary_name: str | None = None
try:
with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=path.parent, delete=False) as temporary:
temporary.write(content)
temporary_name = temporary.name
os.chmod(temporary_name, path.stat().st_mode)
os.replace(temporary_name, path)
finally:
if temporary_name is not None and os.path.exists(temporary_name):
os.unlink(temporary_name)
def read_version(repository: Path = REPOSITORY_DIR) -> str:
"""Read and validate the main distribution version."""
version_file, _, _ = _paths(repository)
version = _match_version(version_file.read_text(encoding="utf-8"), _VERSION_PATTERN, version_file)
try:
return _canonical_version(version)
except ValueError as error:
raise ValueError(f"{version_file}: {error} Fix the __version__ declaration and retry.") from error
def check_versions(repository: Path = REPOSITORY_DIR, expected_version: str | None = None) -> str:
"""Validate both distributions and their exact optional-dependency pins."""
version_file, main_config_file, studio_config_file = _paths(repository)
version = read_version(repository)
main_config = _read_toml(main_config_file)
studio_config = _read_toml(studio_config_file)
dependency = f"reme-ai-studio=={version}"
optional_dependencies = _required_table(main_config, ("project", "optional-dependencies"), main_config_file)
studio_project = _required_table(studio_config, ("project",), studio_config_file)
problems: list[str] = []
studio_version = studio_project.get("version")
if studio_version != version:
problems.append(f"{studio_config_file}: project.version is {studio_version!r}; expected {version!r}")
if optional_dependencies.get("web") != [dependency]:
problems.append(f"{main_config_file}: web must be exactly [{dependency!r}]")
core_dependencies = optional_dependencies.get("core")
if not isinstance(core_dependencies, list) or core_dependencies.count(dependency) != 1:
problems.append(f"{main_config_file}: core must contain {dependency!r} exactly once")
expected = None
if expected_version is not None:
expected = _canonical_version(expected_version.removeprefix("v"))
if version != expected:
problems.append(f"{version_file}: package version is {version!r}; release expects {expected!r}")
if problems:
details = "\n".join(f"- {problem}" for problem in problems)
raise ValueError(
"Version metadata is inconsistent:\n"
f"{details}\n"
f"Fix the listed values, or run `python scripts/bump_version.py {expected or version}` "
"after restoring a consistent current version.",
)
return version
def bump_version(version: str, repository: Path = REPOSITORY_DIR) -> str:
"""Update every release version, rolling back if any write or validation fails."""
version = _canonical_version(version)
current_version = check_versions(repository)
version_file, main_config_file, studio_config_file = _paths(repository)
paths = (version_file, main_config_file, studio_config_file)
original = {path: path.read_text(encoding="utf-8") for path in paths}
current_dependency = f"reme-ai-studio=={current_version}"
main_text = original[main_config_file]
if main_text.count(current_dependency) != 2:
raise ValueError(f"Expected exactly two {current_dependency!r} pins in {main_config_file}.")
updated = {
version_file: _replace_version(original[version_file], _VERSION_PATTERN, version, version_file),
main_config_file: main_text.replace(current_dependency, f"reme-ai-studio=={version}"),
studio_config_file: _replace_version(
original[studio_config_file],
_STUDIO_VERSION_PATTERN,
version,
studio_config_file,
),
}
written: list[Path] = []
try:
for path in paths:
_write_atomic(path, updated[path])
written.append(path)
check_versions(repository, version)
except BaseException as error:
rollback_errors: list[str] = []
for path in reversed(written):
try:
_write_atomic(path, original[path])
except OSError as rollback_error:
rollback_errors.append(f"{path}: {rollback_error}")
if rollback_errors:
raise RuntimeError(
"Version update failed and rollback was incomplete. Restore these files from Git:\n- "
+ "\n- ".join(rollback_errors),
) from error
raise RuntimeError(
"Version update failed; all changed files were restored. Fix the error and retry.",
) from error
return current_version
def main() -> None:
"""Run the version validation or update command."""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("version", nargs="?", help="New version for both distributions, for example 0.4.1.8")
parser.add_argument("--check", action="store_true", help="Validate versions without changing files")
parser.add_argument("--expected-version", help="Also require this release/tag version; a leading v is accepted")
args = parser.parse_args()
if args.check:
if args.version:
parser.error("version cannot be used with --check; use --expected-version")
version = check_versions(expected_version=args.expected_version)
print(f"Release versions are consistent: {version}")
return
if args.expected_version:
parser.error("--expected-version requires --check")
if not args.version:
parser.error("provide a version to update, or use --check")
previous_version = bump_version(args.version)
print(f"Updated ReMe and ReMe Studio from {previous_version} to {args.version}")
if __name__ == "__main__":
main()

View file

@ -10,6 +10,8 @@ REPOSITORY_DIR = Path(__file__).resolve().parents[1]
WEBSITE_DIR = REPOSITORY_DIR / "website"
PACKAGE_DIR = REPOSITORY_DIR / "packages" / "reme_ai_studio"
STATIC_DIR = PACKAGE_DIR / "src" / "reme_ai_studio" / "static"
LICENSE_FILE = REPOSITORY_DIR / "LICENSE"
STATIC_GITIGNORE = "*\n!.gitignore\n"
_RAW_WEBSITE_URL = "https://raw.githubusercontent.com/agentscope-ai/ReMe/main/website"
_REPOSITORY_URL = "https://github.com/agentscope-ai/ReMe"
@ -35,15 +37,20 @@ def build_readme() -> str:
def prepare_package(*, copy_static: bool = True) -> None:
"""Generate the PyPI README and optionally stage the static build."""
"""Generate package metadata files and optionally stage the static build."""
(PACKAGE_DIR / "README.md").write_text(build_readme(), encoding="utf-8")
shutil.copyfile(LICENSE_FILE, PACKAGE_DIR / "LICENSE")
if not copy_static:
return
source = WEBSITE_DIR / "dist-static"
if not (source / "index.html").is_file():
raise FileNotFoundError(f"Studio static build is unavailable: {source}")
shutil.rmtree(STATIC_DIR, ignore_errors=True)
shutil.copytree(source, STATIC_DIR)
try:
shutil.rmtree(STATIC_DIR, ignore_errors=True)
shutil.copytree(source, STATIC_DIR)
finally:
STATIC_DIR.mkdir(parents=True, exist_ok=True)
(STATIC_DIR / ".gitignore").write_text(STATIC_GITIGNORE, encoding="utf-8")
def main() -> None:

View file

@ -503,6 +503,11 @@ def test_daily_paper_topics_parameter_defaults_to_empty():
}
def test_daily_paper_cron_prioritizes_long_term_llm_memory():
"""The scheduled workflow prioritizes papers about long-term LLM memory."""
assert _load_config("daily_cookbook")["jobs"]["daily_paper_cron"]["topics"] == "大模型长期记忆"
def test_daily_paper_hf_mirror_parameter_defaults_to_disabled():
"""The public job schema exposes an explicit Hugging Face mirror switch."""
use_hf_mirror = _load_config("daily_cookbook")["jobs"]["daily_paper"]["parameters"]["properties"]["use_hf_mirror"]
@ -514,14 +519,14 @@ def test_daily_paper_hf_mirror_parameter_defaults_to_disabled():
}
def test_daily_paper_cron_hf_mirror_uses_explicit_environment_switch(monkeypatch):
"""The scheduled workflow can opt in to the Hugging Face mirror."""
def test_daily_paper_cron_hf_mirror_defaults_enabled_with_environment_override(monkeypatch):
"""The scheduled workflow uses the mirror by default and supports an explicit override."""
monkeypatch.delenv("DAILY_PAPER_USE_HF_MIRROR", raising=False)
assert _load_config("daily_cookbook")["jobs"]["daily_paper_cron"]["use_hf_mirror"] is False
monkeypatch.setenv("DAILY_PAPER_USE_HF_MIRROR", "true")
assert _load_config("daily_cookbook")["jobs"]["daily_paper_cron"]["use_hf_mirror"] is True
monkeypatch.setenv("DAILY_PAPER_USE_HF_MIRROR", "false")
assert _load_config("daily_cookbook")["jobs"]["daily_paper_cron"]["use_hf_mirror"] is False
def test_paper_pick_list_uses_an_object_root_for_tool_output():
"""AgentScope function arguments require an object-root JSON schema."""

View file

@ -1,28 +1,204 @@
"""Keep the separately published distributions version-compatible."""
import importlib.util
from pathlib import Path
import tomllib
from types import ModuleType
import reme
from scripts.package_studio import build_readme
import pytest
REPOSITORY = Path(__file__).resolve().parents[2]
def _load_script(name: str) -> ModuleType:
script = REPOSITORY / "scripts" / f"{name}.py"
spec = importlib.util.spec_from_file_location(name, script)
if spec is None or spec.loader is None:
raise ImportError(f"Unable to load {script}")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
bump_version = _load_script("bump_version")
package_studio = _load_script("package_studio")
def test_studio_package_and_extra_versions_match_reme() -> None:
"""Require one version bump to update both wheels and their exact pin."""
repository = Path(__file__).resolve().parents[2]
main_config = tomllib.loads((repository / "pyproject.toml").read_text(encoding="utf-8"))
main_config = tomllib.loads((REPOSITORY / "pyproject.toml").read_text(encoding="utf-8"))
studio_config = tomllib.loads(
(repository / "packages" / "reme_ai_studio" / "pyproject.toml").read_text(encoding="utf-8"),
(REPOSITORY / "packages" / "reme_ai_studio" / "pyproject.toml").read_text(encoding="utf-8"),
)
expected_dependency = f"reme-ai-studio=={reme.__version__}"
version = bump_version.read_version(REPOSITORY)
expected_dependency = f"reme-ai-studio=={version}"
assert studio_config["project"]["version"] == reme.__version__
assert studio_config["project"]["version"] == version
assert main_config["project"]["optional-dependencies"]["web"] == [expected_dependency]
assert expected_dependency in main_config["project"]["optional-dependencies"]["core"]
def _write_version_fixture(repository: Path, *, studio_version: str = "1.2.3") -> None:
(repository / "reme").mkdir()
(repository / "packages" / "reme_ai_studio").mkdir(parents=True)
(repository / "reme" / "__init__.py").write_text('__version__ = "1.2.3"\n', encoding="utf-8")
(repository / "pyproject.toml").write_text(
"""[project]
name = "reme-ai"
[project.optional-dependencies]
web = ["reme-ai-studio==1.2.3"]
core = ["example", "reme-ai-studio==1.2.3"]
""",
encoding="utf-8",
)
(repository / "packages" / "reme_ai_studio" / "pyproject.toml").write_text(
f'[project]\nname = "reme-ai-studio"\nversion = "{studio_version}"\n',
encoding="utf-8",
)
def test_bump_version_updates_both_packages_and_exact_pins(tmp_path: Path) -> None:
"""Update the two package versions and both dependency declarations together."""
_write_version_fixture(tmp_path)
previous_version = bump_version.bump_version("1.2.4", tmp_path)
assert previous_version == "1.2.3"
assert (tmp_path / "reme" / "__init__.py").read_text(encoding="utf-8") == '__version__ = "1.2.4"\n'
assert (tmp_path / "pyproject.toml").read_text(encoding="utf-8").count("reme-ai-studio==1.2.4") == 2
studio_config = tomllib.loads(
(tmp_path / "packages" / "reme_ai_studio" / "pyproject.toml").read_text(encoding="utf-8"),
)
assert studio_config["project"]["version"] == "1.2.4"
def test_bump_version_rejects_inconsistent_sources_before_writing(
tmp_path: Path,
) -> None:
"""Refuse to update any file if the current release metadata has drifted."""
_write_version_fixture(tmp_path, studio_version="1.2.2")
version_file = tmp_path / "reme" / "__init__.py"
original_version_text = version_file.read_text(encoding="utf-8")
with pytest.raises(ValueError, match=r"project.version is '1.2.2'; expected '1.2.3'"):
bump_version.bump_version("1.2.4", tmp_path)
assert version_file.read_text(encoding="utf-8") == original_version_text
@pytest.mark.parametrize("version", ["release", "1..2", "1.2.3_"])
def test_bump_version_rejects_invalid_pep440_versions(tmp_path: Path, version: str) -> None:
"""Reject invalid release versions before changing any source file."""
_write_version_fixture(tmp_path)
version_file = tmp_path / "reme" / "__init__.py"
original_version_text = version_file.read_text(encoding="utf-8")
with pytest.raises(ValueError, match="Invalid PEP 440 version"):
bump_version.bump_version(version, tmp_path)
assert version_file.read_text(encoding="utf-8") == original_version_text
def test_check_versions_reports_release_tag_mismatch(tmp_path: Path) -> None:
"""Explain which source must change when a release tag does not match."""
_write_version_fixture(tmp_path)
with pytest.raises(ValueError, match=r"package version is '1.2.3'; release expects '1.2.4'"):
bump_version.check_versions(tmp_path, "v1.2.4")
def test_read_version_identifies_invalid_source_file(tmp_path: Path) -> None:
"""Point maintainers to the invalid Python version declaration."""
_write_version_fixture(tmp_path)
version_file = tmp_path / "reme" / "__init__.py"
version_file.write_text('__version__ = "1..2"\n', encoding="utf-8")
with pytest.raises(ValueError, match=rf"{version_file}.*Fix the __version__ declaration"):
bump_version.check_versions(tmp_path)
def test_check_versions_identifies_invalid_toml_file(tmp_path: Path) -> None:
"""Point maintainers to malformed package metadata."""
_write_version_fixture(tmp_path)
config_file = tmp_path / "pyproject.toml"
config_file.write_text("[project\n", encoding="utf-8")
with pytest.raises(ValueError, match=rf"{config_file}.*cannot read valid TOML.*Fix this file"):
bump_version.check_versions(tmp_path)
def test_check_versions_identifies_missing_table(tmp_path: Path) -> None:
"""Explain which required TOML table must be restored."""
_write_version_fixture(tmp_path)
config_file = tmp_path / "pyproject.toml"
config_file.write_text('[project]\nname = "reme-ai"\n', encoding="utf-8")
with pytest.raises(ValueError, match=rf"{config_file}.*\[project.optional-dependencies\].*Add or fix"):
bump_version.check_versions(tmp_path)
def test_bump_version_rolls_back_when_a_write_fails(monkeypatch, tmp_path: Path) -> None:
"""Restore earlier files when a later atomic replacement fails."""
# pylint: disable=protected-access
_write_version_fixture(tmp_path)
paths = bump_version._paths(tmp_path)
original = {path: path.read_text(encoding="utf-8") for path in paths}
write_atomic = bump_version._write_atomic
attempts = 0
def fail_second_write(path: Path, content: str) -> None:
nonlocal attempts
attempts += 1
if attempts == 2:
raise OSError("simulated write failure")
write_atomic(path, content)
monkeypatch.setattr(bump_version, "_write_atomic", fail_second_write)
with pytest.raises(RuntimeError, match="all changed files were restored"):
bump_version.bump_version("1.2.4", tmp_path)
assert {path: path.read_text(encoding="utf-8") for path in paths} == original
def test_studio_readme_is_generated_from_website_docs() -> None:
"""Keep the packaged PyPI description synchronized with the source docs."""
repository = Path(__file__).resolve().parents[2]
packaged_readme = repository / "packages" / "reme_ai_studio" / "README.md"
assert packaged_readme.read_text(encoding="utf-8") == build_readme()
packaged_readme = REPOSITORY / "packages" / "reme_ai_studio" / "README.md"
assert packaged_readme.read_text(encoding="utf-8") == package_studio.build_readme()
def test_studio_package_preparation_copies_license(monkeypatch, tmp_path: Path) -> None:
"""Include the repository license in the independently distributed Studio package."""
package_dir = tmp_path / "reme_ai_studio"
package_dir.mkdir()
monkeypatch.setattr(package_studio, "PACKAGE_DIR", package_dir)
package_studio.prepare_package(copy_static=False)
assert (package_dir / "LICENSE").read_text(encoding="utf-8") == (REPOSITORY / "LICENSE").read_text(
encoding="utf-8",
)
def test_studio_package_preparation_preserves_static_gitignore(monkeypatch, tmp_path: Path) -> None:
"""Keep generated static assets ignored after staging the Studio build."""
package_dir = tmp_path / "reme_ai_studio"
package_dir.mkdir()
website_dir = tmp_path / "website"
source_dir = website_dir / "dist-static"
source_dir.mkdir(parents=True)
(source_dir / "index.html").write_text("<html></html>", encoding="utf-8")
static_dir = package_dir / "src" / "reme_ai_studio" / "static"
static_dir.mkdir(parents=True)
(static_dir / ".gitignore").write_text(package_studio.STATIC_GITIGNORE, encoding="utf-8")
monkeypatch.setattr(package_studio, "PACKAGE_DIR", package_dir)
monkeypatch.setattr(package_studio, "WEBSITE_DIR", website_dir)
monkeypatch.setattr(package_studio, "STATIC_DIR", static_dir)
monkeypatch.setattr(package_studio, "build_readme", lambda: "# ReMe Studio\n")
package_studio.prepare_package()
assert (static_dir / "index.html").is_file()
assert (static_dir / ".gitignore").read_text(encoding="utf-8") == package_studio.STATIC_GITIGNORE