Compare commits
126 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99afc2604f | ||
|
|
2dd2255760 | ||
|
|
d8d667c6ac | ||
|
|
940a923f06 | ||
|
|
3d2ecc60d2 | ||
|
|
6f38d201b6 | ||
|
|
ef3f99f019 | ||
|
|
b78e32ef03 | ||
|
|
6a6e0b3c29 | ||
|
|
a457bf7542 | ||
|
|
513fb5b7f4 | ||
|
|
1a6b584274 | ||
|
|
15d12be6b6 | ||
|
|
626c850ccb | ||
|
|
01ef1a6efb | ||
|
|
efcc2b34d1 | ||
|
|
c8e1248769 | ||
|
|
8416fd3ac9 | ||
|
|
f44f52d919 | ||
|
|
ebcb154e37 | ||
|
|
39233f4e62 | ||
|
|
94b7dedc26 | ||
|
|
87187c1d25 | ||
|
|
f5ec230fef | ||
|
|
2f5fd46b44 | ||
|
|
618e8cec66 | ||
|
|
d3aee1adf5 | ||
|
|
6b9a75267b | ||
|
|
c792fd197c | ||
|
|
fd2894f939 | ||
|
|
2a05914150 | ||
|
|
29eb51d7ba | ||
|
|
da9a8b7810 | ||
|
|
dbf2a17da6 | ||
|
|
64249873ce | ||
|
|
28fa636506 | ||
|
|
52fdd446fb | ||
|
|
ab66f2bb56 | ||
|
|
215c1f72f2 | ||
|
|
9533c17d51 | ||
|
|
b8f48c8004 | ||
|
|
3924f89bb4 | ||
|
|
c7dbf31c3f | ||
|
|
58276f740b | ||
|
|
3095564313 | ||
|
|
21057931a9 | ||
|
|
5a5855f5ff | ||
|
|
072cb6a55b | ||
|
|
fca42f4e6c | ||
|
|
d5e0d2837b | ||
|
|
e05b201da9 | ||
|
|
765103a597 | ||
|
|
168b7194ab | ||
|
|
e7b9274190 | ||
|
|
c5d92a24ab | ||
|
|
9218a2d0e3 | ||
|
|
6503e1271c | ||
|
|
23d4c96c15 | ||
|
|
f31daf1949 | ||
|
|
b00eb0a9ea | ||
|
|
ad4f23e4dc | ||
|
|
5bc46c88b6 | ||
|
|
e256c556ca | ||
|
|
d2b8872f2e | ||
|
|
eac8223387 | ||
|
|
dc7df26e95 | ||
|
|
a9ec334adc | ||
|
|
6b035c6553 | ||
|
|
3d487d8d45 | ||
|
|
f3d32e203d | ||
|
|
550317c3bf | ||
|
|
a367c2ce13 | ||
|
|
c937be9d94 | ||
|
|
4eb2adf961 | ||
|
|
f34dcdb09b | ||
|
|
2f79977df0 | ||
|
|
0522135791 | ||
|
|
11fe50d89c | ||
|
|
1687179f84 | ||
|
|
46adb5ae1e | ||
|
|
630f26b119 | ||
|
|
7b1da5a9ee | ||
|
|
e7d44f6f3b | ||
|
|
b4333fbef8 | ||
|
|
55ef4bd6ad | ||
|
|
cf22ef3b1d | ||
|
|
1c08eaa559 | ||
|
|
987f275985 | ||
|
|
9c9b040d42 | ||
|
|
c1a25e9ff4 | ||
|
|
329fd9a6a6 | ||
|
|
2eb05392c6 | ||
|
|
c3b1e93918 | ||
|
|
2a85c36fa9 | ||
|
|
2e87b7a52e | ||
|
|
8042f74b6f | ||
|
|
b5e0ec2d8d | ||
|
|
07d4527a0d | ||
|
|
bf7ca17705 | ||
|
|
90e7adc2d2 | ||
|
|
6a2dd02e48 | ||
|
|
b1c9bf67bf | ||
|
|
e41b1673ad | ||
|
|
c5eefe4da3 | ||
|
|
2612d25959 | ||
|
|
82971ac5b0 | ||
|
|
41c6cdaff5 | ||
|
|
b53d3db8d0 | ||
|
|
eb471d7d94 | ||
|
|
38cf16071b | ||
|
|
10da205797 | ||
|
|
bf902b3479 | ||
|
|
0a7eea18f8 | ||
|
|
1e798d3b4e | ||
|
|
7369342115 | ||
|
|
43a407bc4f | ||
|
|
f63165c66b | ||
|
|
6bf2db8ff4 | ||
|
|
8877743ca9 | ||
|
|
c060933e4d | ||
|
|
5a3450ddb3 | ||
|
|
435aa713a2 | ||
|
|
9d14e988d8 | ||
|
|
1c05d0359b | ||
|
|
6244e7eeaa | ||
|
|
e7ef2c8ce6 |
97
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
name: Bug report
|
||||
description: Report reproducible incorrect or unexpected ReMe behavior
|
||||
title: "[Bug]: "
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for helping improve ReMe. Please remove secrets, API keys, and private memory content before submitting.
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: What happened, and what did you expect instead?
|
||||
placeholder: Describe the observed and expected behavior.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Provide the smallest configuration and command sequence that reproduces the problem.
|
||||
placeholder: |
|
||||
1. Configure ...
|
||||
2. Run ...
|
||||
3. Observe ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: config
|
||||
attributes:
|
||||
label: Relevant configuration
|
||||
description: Include only relevant values and redact credentials, tokens, endpoints, and private paths.
|
||||
render: yaml
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs or traceback
|
||||
description: Paste relevant output after removing secrets and private workspace content.
|
||||
render: shell
|
||||
|
||||
- type: input
|
||||
id: reme-version
|
||||
attributes:
|
||||
label: ReMe version
|
||||
placeholder: e.g. 0.4.1.8 or a commit SHA
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: python-version
|
||||
attributes:
|
||||
label: Python version
|
||||
placeholder: e.g. 3.11.9
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating system
|
||||
options:
|
||||
- Linux
|
||||
- macOS
|
||||
- Windows
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: Affected area
|
||||
options:
|
||||
- CLI or configuration
|
||||
- HTTP, MCP, or local service
|
||||
- Memory or workspace files
|
||||
- Search, catalog, graph, or index
|
||||
- Model or agent integration
|
||||
- ReMe Studio
|
||||
- Plugin or external integration
|
||||
- Packaging or installation
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: safety
|
||||
attributes:
|
||||
label: Data safety
|
||||
options:
|
||||
- label: I removed credentials and private memory content from this report.
|
||||
required: true
|
||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: ReMe documentation
|
||||
url: https://reme.agentscope.io
|
||||
about: Read the installation, configuration, and usage guides.
|
||||
- name: Existing issues
|
||||
url: https://github.com/agentscope-ai/ReMe/issues
|
||||
about: Search for existing reports and discussions before opening a new issue.
|
||||
64
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
name: Feature request
|
||||
description: Propose a focused enhancement to ReMe
|
||||
title: "[Feature]: "
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Problem
|
||||
description: What user problem or limitation should this change address?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: proposal
|
||||
attributes:
|
||||
label: Proposed behavior
|
||||
description: Describe the desired behavior and its user-visible contract.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: Area
|
||||
options:
|
||||
- CLI or configuration
|
||||
- Jobs or steps
|
||||
- Memory or workspace files
|
||||
- Search, catalog, graph, or index
|
||||
- Service or client
|
||||
- Model or agent integration
|
||||
- ReMe Studio
|
||||
- Plugin or external integration
|
||||
- Documentation
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: ownership
|
||||
attributes:
|
||||
label: Local-first and compatibility considerations
|
||||
description: Explain any effect on user-owned files, rebuildable state, configuration, schemas, or service interfaces.
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives considered
|
||||
description: Describe workarounds or alternative designs you considered.
|
||||
|
||||
- type: textarea
|
||||
id: examples
|
||||
attributes:
|
||||
label: Example usage
|
||||
description: Show the proposed CLI, configuration, API, or UI behavior when useful.
|
||||
render: shell
|
||||
|
||||
- type: checkboxes
|
||||
id: contribution
|
||||
attributes:
|
||||
label: Contribution
|
||||
options:
|
||||
- label: I am willing to help implement or test this feature.
|
||||
53
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
name: Usage question
|
||||
description: Ask for help using or configuring ReMe
|
||||
title: "[Question]: "
|
||||
labels: [question]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Please check the documentation and existing issues before asking a new question.
|
||||
|
||||
- type: textarea
|
||||
id: goal
|
||||
attributes:
|
||||
label: What are you trying to achieve?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: attempted
|
||||
attributes:
|
||||
label: What have you tried?
|
||||
description: Include relevant commands or configuration, with secrets and private memory content removed.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: reme-version
|
||||
attributes:
|
||||
label: ReMe version
|
||||
placeholder: e.g. 0.4.1.8 or a commit SHA
|
||||
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: Area
|
||||
options:
|
||||
- Installation
|
||||
- Configuration
|
||||
- CLI or service usage
|
||||
- Memory and workspace management
|
||||
- Search and retrieval
|
||||
- ReMe Studio
|
||||
- Plugin or integration
|
||||
- Other
|
||||
|
||||
- type: checkboxes
|
||||
id: checked
|
||||
attributes:
|
||||
label: Before submitting
|
||||
options:
|
||||
- label: I checked the [ReMe documentation](https://reme.agentscope.io) and searched existing issues.
|
||||
required: true
|
||||
- label: I removed credentials and private memory content.
|
||||
required: true
|
||||
35
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
## Summary
|
||||
|
||||
<!-- Explain the problem and the smallest coherent change that addresses it. -->
|
||||
|
||||
## Related issue
|
||||
|
||||
<!-- Use "Fixes #123" when applicable. -->
|
||||
|
||||
## Contract and data impact
|
||||
|
||||
- [ ] No public configuration, schema, CLI, endpoint, streaming, or workspace-layout contract changes
|
||||
- [ ] No user-owned memory files are deleted or rewritten
|
||||
- [ ] Derived indexes, catalogs, graphs, caches, and metadata remain rebuildable
|
||||
|
||||
<!-- If any item is unchecked, describe the impact and migration or recovery path. -->
|
||||
|
||||
## Validation
|
||||
|
||||
<!-- List the exact checks run and their results. Explain relevant checks that were not run. -->
|
||||
|
||||
- [ ] Focused tests pass
|
||||
- [ ] Unit tests pass, or omitted tests are explained below
|
||||
- [ ] `pre-commit run --all-files` passes, or omitted checks are explained below
|
||||
- [ ] Frontend checks were run when `reme_studio/` changed
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] I reviewed the diff for unrelated changes and sensitive data
|
||||
- [ ] Tests cover intentional behavior changes
|
||||
- [ ] Defaults, schemas, and concise documentation were updated together when required
|
||||
- [ ] Long-lived clients, tasks, services, and executors follow the application lifecycle
|
||||
|
||||
## Screenshots or additional notes
|
||||
|
||||
<!-- Include UI screenshots, compatibility notes, or follow-up work when relevant. -->
|
||||
58
.github/workflows/_build-docs.yml
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
name: _Build documentation
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
run_tests:
|
||||
description: Run the documentation test suite before building
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
upload_pages_artifact:
|
||||
description: Upload the build for a later GitHub Pages deployment job
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build documentation
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: github-pages
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
||||
with:
|
||||
node-version: '22.22.3'
|
||||
cache: npm
|
||||
cache-dependency-path: github-pages/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run tests
|
||||
if: inputs.run_tests
|
||||
run: npm test
|
||||
|
||||
- name: Build documentation
|
||||
run: npm run build
|
||||
|
||||
- name: Configure Pages
|
||||
if: inputs.upload_pages_artifact
|
||||
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6
|
||||
|
||||
- name: Upload Pages artifact
|
||||
if: inputs.upload_pages_artifact
|
||||
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4
|
||||
with:
|
||||
path: github-pages/dist
|
||||
88
.github/workflows/_build-python-packages.yml
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
name: _Build Python packages
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
expected_version:
|
||||
description: Expected release version; omit for a consistency-only check
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
upload_artifacts:
|
||||
description: Upload distributions for later publish jobs
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
distributions:
|
||||
name: Build Python distributions
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # 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 package versions
|
||||
if: inputs.expected_version == ''
|
||||
run: python scripts/bump_version.py --check
|
||||
|
||||
- name: Validate release version
|
||||
if: inputs.expected_version != ''
|
||||
env:
|
||||
EXPECTED_VERSION: ${{ inputs.expected_version }}
|
||||
run: python scripts/bump_version.py --check --expected-version "${EXPECTED_VERSION}"
|
||||
|
||||
- name: Run package tests
|
||||
run: PYTHONPATH=. python -m pytest tests/unit/test_package_versions.py -q
|
||||
|
||||
- name: Build and check distributions
|
||||
run: |
|
||||
mkdir -p dist/reme
|
||||
python -m build --outdir dist/reme
|
||||
python -m twine check dist/reme/*
|
||||
|
||||
- name: Verify distributions and isolated installation
|
||||
run: |
|
||||
REME_WHEEL="$(pwd)/$(ls dist/reme/reme_ai-[0-9]*.whl)"
|
||||
python -m zipfile -l "${REME_WHEEL}" | (! grep 'reme/web/')
|
||||
python -m zipfile -l "${REME_WHEEL}" | (! grep 'reme_studio/')
|
||||
python -m venv "${RUNNER_TEMP}/reme-package-smoke"
|
||||
"${RUNNER_TEMP}/reme-package-smoke/bin/python" -m pip install "${REME_WHEEL}[as]"
|
||||
cd "${RUNNER_TEMP}"
|
||||
"${RUNNER_TEMP}/reme-package-smoke/bin/python" -c "import reme"
|
||||
|
||||
- name: Verify released core dependencies
|
||||
if: inputs.expected_version != ''
|
||||
run: |
|
||||
REME_WHEEL="$(pwd)/$(ls dist/reme/reme_ai-[0-9]*.whl)"
|
||||
python -m venv "${RUNNER_TEMP}/reme-core-package-smoke"
|
||||
"${RUNNER_TEMP}/reme-core-package-smoke/bin/python" -m pip install "${REME_WHEEL}[core]"
|
||||
cd "${RUNNER_TEMP}"
|
||||
"${RUNNER_TEMP}/reme-core-package-smoke/bin/python" - <<'PY'
|
||||
from reme_studio import static_dir
|
||||
|
||||
assert (static_dir() / "index.html").is_file()
|
||||
PY
|
||||
|
||||
- name: Upload ReMe distributions
|
||||
if: inputs.upload_artifacts
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: reme-distributions
|
||||
path: dist/reme/
|
||||
if-no-files-found: error
|
||||
48
.github/workflows/ci-docs.yml
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
name: CI / Documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master, dev, develop]
|
||||
paths:
|
||||
- '.github/workflows/ci-docs.yml'
|
||||
- '.github/workflows/_build-docs.yml'
|
||||
- 'AGENTS.md'
|
||||
- 'README.md'
|
||||
- 'README_ZH.md'
|
||||
- 'docs/**'
|
||||
- 'github-pages/**'
|
||||
- 'reme_studio/README*.md'
|
||||
- 'reme_studio/public/og.jpg'
|
||||
- 'typescript/README*.md'
|
||||
- 'plugins/*/README*.md'
|
||||
- 'benchmark/*/README*.md'
|
||||
pull_request:
|
||||
branches: [main, master, dev, develop]
|
||||
paths:
|
||||
- '.github/workflows/ci-docs.yml'
|
||||
- '.github/workflows/_build-docs.yml'
|
||||
- 'AGENTS.md'
|
||||
- 'README.md'
|
||||
- 'README_ZH.md'
|
||||
- 'docs/**'
|
||||
- 'github-pages/**'
|
||||
- 'reme_studio/README*.md'
|
||||
- 'reme_studio/public/og.jpg'
|
||||
- 'typescript/README*.md'
|
||||
- 'plugins/*/README*.md'
|
||||
- 'benchmark/*/README*.md'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
documentation:
|
||||
name: Test and build documentation
|
||||
uses: ./.github/workflows/_build-docs.yml
|
||||
with:
|
||||
run_tests: true
|
||||
40
.github/workflows/ci-packages.yml
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
name: CI / Python packages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master, dev, develop]
|
||||
paths:
|
||||
- '.github/workflows/ci-packages.yml'
|
||||
- '.github/workflows/_build-python-packages.yml'
|
||||
- '.github/workflows/release-python.yml'
|
||||
- 'pyproject.toml'
|
||||
- 'README.md'
|
||||
- 'reme/**'
|
||||
- 'scripts/bump_version.py'
|
||||
- 'tests/unit/test_package_versions.py'
|
||||
- 'LICENSE'
|
||||
pull_request:
|
||||
branches: [main, master, dev, develop]
|
||||
paths:
|
||||
- '.github/workflows/ci-packages.yml'
|
||||
- '.github/workflows/_build-python-packages.yml'
|
||||
- '.github/workflows/release-python.yml'
|
||||
- 'pyproject.toml'
|
||||
- 'README.md'
|
||||
- 'reme/**'
|
||||
- 'scripts/bump_version.py'
|
||||
- 'tests/unit/test_package_versions.py'
|
||||
- 'LICENSE'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
distributions:
|
||||
name: Build and verify distributions
|
||||
uses: ./.github/workflows/_build-python-packages.yml
|
||||
40
.github/workflows/ci-python-quality.yml
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
name: CI / Python quality
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
name: Pre-commit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
cache: pip
|
||||
|
||||
- name: Update setuptools
|
||||
run: |
|
||||
pip install -U setuptools wheel
|
||||
|
||||
- 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
|
||||
|
||||
- name: Pre-commit starts
|
||||
run: pre-commit run --all-files
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
name: Tests ReMe
|
||||
name: CI / Python tests
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
@ -11,6 +11,9 @@ concurrency:
|
|||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
unit-tests:
|
||||
name: Unit Tests - py${{ matrix.python-version }}
|
||||
|
|
@ -21,10 +24,12 @@ jobs:
|
|||
python-version: ["3.11", "3.12", "3.13"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
|
|
@ -32,12 +37,18 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
pip install -e ".[dev,core]"
|
||||
pip install -e reme_studio -e ".[dev,core]"
|
||||
pip install --no-deps -e plugins/auto-fin
|
||||
pip install -e plugins/daily_paper
|
||||
pip install coverage
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
pytest tests/unit \
|
||||
coverage run -m pytest tests/unit plugins/auto-fin plugins/daily_paper \
|
||||
-v \
|
||||
--tb=long \
|
||||
-s \
|
||||
--log-cli-level=WARNING
|
||||
|
||||
- name: Generate coverage report
|
||||
run: coverage report -m
|
||||
90
.github/workflows/ci-reme-studio.yml
vendored
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
name: CI / ReMe Studio
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "reme_studio/**"
|
||||
- ".github/workflows/ci-reme-studio.yml"
|
||||
- ".github/workflows/release-reme-studio.yml"
|
||||
- "scripts/package_studio.py"
|
||||
- "tests/unit/test_package_versions.py"
|
||||
- "pyproject.toml"
|
||||
- "LICENSE"
|
||||
pull_request:
|
||||
paths:
|
||||
- "reme_studio/**"
|
||||
- ".github/workflows/ci-reme-studio.yml"
|
||||
- ".github/workflows/release-reme-studio.yml"
|
||||
- "scripts/package_studio.py"
|
||||
- "tests/unit/test_package_versions.py"
|
||||
- "pyproject.toml"
|
||||
- "LICENSE"
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
studio:
|
||||
name: Studio checks
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: reme_studio
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
||||
with:
|
||||
node-version: "22.22.3"
|
||||
cache: npm
|
||||
cache-dependency-path: reme_studio/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run format check
|
||||
run: npm run format:check
|
||||
|
||||
- name: Run lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
- name: Verify npm package
|
||||
run: |
|
||||
npm pack --pack-destination "${RUNNER_TEMP}"
|
||||
tar -tzf "${RUNNER_TEMP}"/agentscope-ai-reme_studio-*.tgz | grep '^package/dist-static/index.html$'
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Build and verify Python package
|
||||
working-directory: .
|
||||
run: |
|
||||
python -m pip install build packaging pytest twine
|
||||
PYTHONPATH=. python -m pytest tests/unit/test_package_versions.py -q
|
||||
python scripts/package_studio.py
|
||||
python -m build reme_studio --outdir dist/studio
|
||||
python -m twine check dist/studio/*
|
||||
STUDIO_WHEEL="$(pwd)/$(ls dist/studio/reme_studio-*.whl)"
|
||||
python -m venv "${RUNNER_TEMP}/reme-studio-package-smoke"
|
||||
"${RUNNER_TEMP}/reme-studio-package-smoke/bin/python" -m pip install "${STUDIO_WHEEL}"
|
||||
cd "${RUNNER_TEMP}"
|
||||
"${RUNNER_TEMP}/reme-studio-package-smoke/bin/python" - <<'PY'
|
||||
from reme_studio import static_dir
|
||||
|
||||
assert (static_dir() / "index.html").is_file()
|
||||
PY
|
||||
51
.github/workflows/ci-typescript.yml
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
name: CI / TypeScript integrations
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master, dev, develop]
|
||||
paths:
|
||||
- '.github/workflows/ci-typescript.yml'
|
||||
- '.github/workflows/release-typescript.yml'
|
||||
- 'typescript/**'
|
||||
pull_request:
|
||||
branches: [main, master, dev, develop]
|
||||
paths:
|
||||
- '.github/workflows/ci-typescript.yml'
|
||||
- '.github/workflows/release-typescript.yml'
|
||||
- 'typescript/**'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
package:
|
||||
name: Type-check, test, and pack
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: typescript
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
||||
with:
|
||||
node-version: '22.22.3'
|
||||
cache: npm
|
||||
cache-dependency-path: typescript/package-lock.json
|
||||
|
||||
- run: npm ci
|
||||
- run: npm run format:check
|
||||
- run: npm run lint
|
||||
- run: npm run typecheck
|
||||
- run: npm test
|
||||
- run: npm run test:package
|
||||
- name: Validate OpenClaw package contract
|
||||
run: npx --yes clawhub@0.23.3 package validate . --json
|
||||
51
.github/workflows/ci-windows.yml
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
name: CI / Windows
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master, dev, develop]
|
||||
pull_request:
|
||||
branches: [main, master, dev, develop]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
cli-smoke:
|
||||
name: CLI smoke - py${{ matrix.python-version }}
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.11"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install package
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
pip install -e ".[dev,as]"
|
||||
|
||||
- name: Run version job
|
||||
run: reme start config=tests/fixtures/config/version-smoke.yaml job=version
|
||||
|
||||
- name: Run Windows path tests
|
||||
run: |
|
||||
python -m pytest `
|
||||
tests/unit/test_auto_dream.py::test_scan_day_files_includes_nested_md_and_excludes_interests `
|
||||
tests/unit/test_auto_dream.py::test_dream_extract_matches_posix_catalog_paths `
|
||||
tests/unit/test_read_with_neighbors.py::test_read_with_neighbors_uses_posix_nested_path `
|
||||
-v
|
||||
52
.github/workflows/deploy-docs.yml
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
name: Deploy / Documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "github-pages/**"
|
||||
- "docs/**"
|
||||
- "README.md"
|
||||
- "README_ZH.md"
|
||||
- "reme_studio/README*.md"
|
||||
- "reme_studio/public/og.jpg"
|
||||
- "typescript/README*.md"
|
||||
- "plugins/*/README*.md"
|
||||
- "benchmark/*/README*.md"
|
||||
- "AGENTS.md"
|
||||
- ".github/workflows/deploy-docs.yml"
|
||||
- ".github/workflows/_build-docs.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build documentation
|
||||
uses: ./.github/workflows/_build-docs.yml
|
||||
with:
|
||||
run_tests: true
|
||||
upload_pages_artifact: true
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
permissions:
|
||||
pages: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Deploy
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
|
||||
40
.github/workflows/policy-pr-title.yml
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
name: Policy / PR title
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main, master, dev, develop]
|
||||
types: [opened, edited, synchronize, reopened]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
check-pr-title:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check PR title format
|
||||
uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
types: |
|
||||
feat
|
||||
fix
|
||||
docs
|
||||
ci
|
||||
refactor
|
||||
test
|
||||
chore
|
||||
perf
|
||||
style
|
||||
build
|
||||
revert
|
||||
requireScope: false
|
||||
scopePattern: ^[a-z0-9_-]+$
|
||||
scopePatternError: |
|
||||
The scope must contain only lowercase letters, numbers, hyphens, and underscores.
|
||||
Example: "feat(memory): add redis cache support"
|
||||
validateSingleCommit: false
|
||||
ignoreLabels: |
|
||||
ignore-semantic-pull-request
|
||||
38
.github/workflows/pre-commit.yml
vendored
|
|
@ -1,38 +0,0 @@
|
|||
name: Pre-commit
|
||||
|
||||
on: [ push, pull_request ]
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: True
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
env:
|
||||
OS: ${{ matrix.os }}
|
||||
PYTHON: '3.11'
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@master
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Update setuptools
|
||||
run: |
|
||||
pip install -U setuptools wheel
|
||||
- name: Install
|
||||
run: |
|
||||
pip install -q -e .[dev]
|
||||
- name: Install pre-commit
|
||||
run: |
|
||||
pre-commit install
|
||||
- name: Pre-commit starts
|
||||
run: |
|
||||
pre-commit run --all-files > pre-commit.log 2>&1 || true
|
||||
cat pre-commit.log
|
||||
if grep -q Failed pre-commit.log; then
|
||||
echo -e "\e[41m [**FAIL**] Please install pre-commit and format your code first. \e[0m"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "\e[46m ********************************Passed******************************** \e[0m"
|
||||
40
.github/workflows/python-publish.yml
vendored
|
|
@ -1,40 +0,0 @@
|
|||
# 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
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install setuptools wheel build
|
||||
- name: Build package
|
||||
run: python -m build
|
||||
- name: Publish package to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
157
.github/workflows/release-auto-fin.yml
vendored
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
# 发布操作手册:
|
||||
# 1. 先将 plugins/auto-fin/pyproject.toml 中的 project.version 更新为待发布版本并合入目标分支。
|
||||
# 2. 确认插件依赖的 reme-ai 版本已经发布到 PyPI;本工作流会在构建阶段验证该依赖可下载。
|
||||
# 3. 确认 PyPI Trusted Publisher 已绑定本仓库、此工作流和 pypi environment,且 PyPI 上不存在相同版本。
|
||||
# 4. 在 GitHub 仓库的 Actions 页面选择“Release / Auto Fin plugin”,点击“Run workflow”。
|
||||
# 5. 输入与 project.version 完全一致的版本号(例如 0.1.0)后运行;版本也可以带 v 前缀。
|
||||
#
|
||||
# 推荐发布顺序:reme-ai -> reme-auto-fin -> QwenPaw 更新依赖并通过 plugins: [auto-fin] 启用。
|
||||
# 当前仅支持 workflow_dispatch 手动触发,不会因 push、tag 或 release 自动发布。
|
||||
|
||||
name: Release / Auto Fin plugin
|
||||
|
||||
run-name: Publish reme-auto-fin ${{ inputs.version }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Version from plugins/auto-fin/pyproject.toml (for example, 0.1.0)
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: publish-reme-auto-fin
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RELEASE_VERSION: ${{ inputs.version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install test and build dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install build packaging pytest pytest-asyncio twine
|
||||
python -m pip install -e ".[core]"
|
||||
python -m pip install --no-deps -e plugins/auto-fin
|
||||
|
||||
- name: Validate package name and release version
|
||||
id: package
|
||||
run: |
|
||||
python - "${RELEASE_VERSION}" <<'PY'
|
||||
import os
|
||||
import sys
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
from packaging.requirements import Requirement
|
||||
from packaging.version import Version
|
||||
|
||||
project = tomllib.loads(Path("plugins/auto-fin/pyproject.toml").read_text(encoding="utf-8"))["project"]
|
||||
expected = Version(sys.argv[1].removeprefix("v"))
|
||||
actual = Version(project["version"])
|
||||
if project["name"] != "reme-auto-fin":
|
||||
raise SystemExit(f"Expected project name 'reme-auto-fin', found {project['name']!r}")
|
||||
if actual != expected:
|
||||
raise SystemExit(f"Package version is {actual}, but workflow input is {expected}")
|
||||
requirements = [requirement for requirement in project["dependencies"] if requirement.startswith("reme-ai")]
|
||||
if len(requirements) != 1:
|
||||
raise SystemExit(f"Expected one reme-ai dependency, found {requirements!r}")
|
||||
reme_requirement = Requirement(requirements[0])
|
||||
if reme_requirement.name != "reme-ai" or reme_requirement.extras:
|
||||
raise SystemExit(f"Expected a base reme-ai dependency, found {requirements[0]!r}")
|
||||
if Version("0.4.1.8") in reme_requirement.specifier or Version("0.4.1.9") not in reme_requirement.specifier:
|
||||
raise SystemExit(f"Expected reme-ai>=0.4.1.9, found {requirements[0]!r}")
|
||||
with Path(os.environ["GITHUB_OUTPUT"]).open("a", encoding="utf-8") as output:
|
||||
print(f"reme_requirement={reme_requirement}", file=output)
|
||||
print(f"Publishing {project['name']} {actual}")
|
||||
PY
|
||||
|
||||
- name: Run Auto Fin tests
|
||||
run: python -m pytest plugins/auto-fin -q
|
||||
|
||||
- name: Require the plugin-enabled ReMe release on PyPI
|
||||
env:
|
||||
REME_REQUIREMENT: ${{ steps.package.outputs.reme_requirement }}
|
||||
run: |
|
||||
python -m pip download --no-deps \
|
||||
--dest "${RUNNER_TEMP}/reme-auto-fin-base" \
|
||||
"${REME_REQUIREMENT}"
|
||||
|
||||
- name: Build and check distributions
|
||||
run: |
|
||||
mkdir -p dist/auto-fin
|
||||
python -m build plugins/auto-fin --outdir dist/auto-fin
|
||||
python -m twine check dist/auto-fin/*
|
||||
|
||||
- name: Verify distributions and isolated installation
|
||||
run: |
|
||||
AUTO_FIN_WHEEL="$(pwd)/$(ls dist/auto-fin/reme_auto_fin-*.whl)"
|
||||
AUTO_FIN_SDIST="$(pwd)/$(ls dist/auto-fin/reme_auto_fin-*.tar.gz)"
|
||||
python -m zipfile -l "${AUTO_FIN_WHEEL}" | grep 'dist-info/licenses/LICENSE'
|
||||
python -m tarfile -l "${AUTO_FIN_SDIST}" | grep '/LICENSE'
|
||||
python -m venv "${RUNNER_TEMP}/reme-auto-fin-smoke"
|
||||
"${RUNNER_TEMP}/reme-auto-fin-smoke/bin/python" -m pip install \
|
||||
"agentscope[model-ollama]==2.0.7" "${AUTO_FIN_WHEEL}"
|
||||
cd "${RUNNER_TEMP}"
|
||||
"${RUNNER_TEMP}/reme-auto-fin-smoke/bin/python" - <<'PY'
|
||||
from importlib.metadata import distribution
|
||||
|
||||
from reme.plugin_manifest import load_package_manifest
|
||||
|
||||
package = distribution("reme-auto-fin")
|
||||
plugins = {entry.name: entry for entry in package.entry_points if entry.group == "reme.plugins"}
|
||||
assert plugins["auto-fin"].value == "reme_auto_fin"
|
||||
manifest = load_package_manifest("reme_auto_fin", plugin_name="auto-fin")
|
||||
assert set(manifest.backends) == {
|
||||
"auto_fin_data_step",
|
||||
"auto_fin_topic_step",
|
||||
"auto_fin_merge_step",
|
||||
}
|
||||
assert set(manifest.application_defaults["jobs"]) == {
|
||||
"auto_fin",
|
||||
"auto_fin_cron",
|
||||
}
|
||||
PY
|
||||
|
||||
- name: Upload distributions
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: reme-auto-fin-${{ inputs.version }}
|
||||
path: dist/auto-fin/
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment: pypi
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Download distributions
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
name: reme-auto-fin-${{ inputs.version }}
|
||||
path: dist/auto-fin
|
||||
|
||||
- name: Publish reme-auto-fin
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
|
||||
with:
|
||||
packages-dir: dist/auto-fin
|
||||
157
.github/workflows/release-daily-paper.yml
vendored
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
# Release checklist:
|
||||
# 1. Update project.version in plugins/daily_paper/pyproject.toml and merge it into the target branch.
|
||||
# 2. Publish the required reme-ai version before this plugin; the build verifies that dependency on PyPI.
|
||||
# 3. Configure PyPI Trusted Publishing for this repository/workflow and its pypi environment.
|
||||
# 4. Run "Release / Daily Paper plugin" from GitHub Actions with the exact project version (a v prefix is accepted).
|
||||
#
|
||||
# Recommended order: reme-ai -> reme-daily-paper -> downstream applications enabling plugins: [daily-paper].
|
||||
# This workflow is intentionally manual and never publishes from a push, tag, or GitHub release event.
|
||||
|
||||
name: Release / Daily Paper plugin
|
||||
|
||||
run-name: Publish reme-daily-paper ${{ inputs.version }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Version from plugins/daily_paper/pyproject.toml (for example, 0.1.0)
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: publish-reme-daily-paper
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RELEASE_VERSION: ${{ inputs.version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install test and build dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install build packaging pytest pytest-asyncio twine
|
||||
python -m pip install -e ".[core]"
|
||||
python -m pip install -e plugins/daily_paper
|
||||
|
||||
- name: Validate package name, dependencies, and release version
|
||||
id: package
|
||||
run: |
|
||||
python - "${RELEASE_VERSION}" <<'PY'
|
||||
import os
|
||||
import sys
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
from packaging.requirements import Requirement
|
||||
from packaging.version import Version
|
||||
|
||||
project = tomllib.loads(Path("plugins/daily_paper/pyproject.toml").read_text(encoding="utf-8"))["project"]
|
||||
expected = Version(sys.argv[1].removeprefix("v"))
|
||||
actual = Version(project["version"])
|
||||
if project["name"] != "reme-daily-paper":
|
||||
raise SystemExit(f"Expected project name 'reme-daily-paper', found {project['name']!r}")
|
||||
if actual != expected:
|
||||
raise SystemExit(f"Package version is {actual}, but workflow input is {expected}")
|
||||
requirements = [Requirement(value) for value in project["dependencies"]]
|
||||
reme_requirements = [requirement for requirement in requirements if requirement.name == "reme-ai"]
|
||||
if len(reme_requirements) != 1 or reme_requirements[0].extras:
|
||||
raise SystemExit(f"Expected one base reme-ai dependency, found {reme_requirements!r}")
|
||||
if Version("0.4.1.8") in reme_requirements[0].specifier or Version("0.4.1.9") not in reme_requirements[0].specifier:
|
||||
raise SystemExit(f"Expected reme-ai>=0.4.1.9, found {reme_requirements!r}")
|
||||
if sum(requirement.name == "pypdf" for requirement in requirements) != 1:
|
||||
raise SystemExit("Expected exactly one pypdf dependency")
|
||||
with Path(os.environ["GITHUB_OUTPUT"]).open("a", encoding="utf-8") as output:
|
||||
print(f"reme_requirement={reme_requirements[0]}", file=output)
|
||||
print(f"Publishing {project['name']} {actual}")
|
||||
PY
|
||||
|
||||
- name: Run Daily Paper tests
|
||||
run: python -m pytest plugins/daily_paper -q
|
||||
|
||||
- name: Require the plugin-enabled ReMe release on PyPI
|
||||
env:
|
||||
REME_REQUIREMENT: ${{ steps.package.outputs.reme_requirement }}
|
||||
run: |
|
||||
python -m pip download --no-deps \
|
||||
--dest "${RUNNER_TEMP}/reme-daily-paper-base" \
|
||||
"${REME_REQUIREMENT}"
|
||||
|
||||
- name: Build and check distributions
|
||||
run: |
|
||||
mkdir -p dist/daily-paper
|
||||
python -m build plugins/daily_paper --outdir dist/daily-paper
|
||||
python -m twine check dist/daily-paper/*
|
||||
|
||||
- name: Verify distributions and isolated installation
|
||||
run: |
|
||||
DAILY_PAPER_WHEEL="$(pwd)/$(ls dist/daily-paper/reme_daily_paper-*.whl)"
|
||||
DAILY_PAPER_SDIST="$(pwd)/$(ls dist/daily-paper/reme_daily_paper-*.tar.gz)"
|
||||
python -m zipfile -l "${DAILY_PAPER_WHEEL}" | grep 'reme_daily_paper/plugin.yaml'
|
||||
python -m zipfile -l "${DAILY_PAPER_WHEEL}" | grep 'reme_daily_paper/analyze.yaml'
|
||||
python -m zipfile -l "${DAILY_PAPER_WHEEL}" | grep 'dist-info/licenses/LICENSE'
|
||||
python -m tarfile -l "${DAILY_PAPER_SDIST}" | grep '/LICENSE'
|
||||
python -m venv "${RUNNER_TEMP}/reme-daily-paper-smoke"
|
||||
"${RUNNER_TEMP}/reme-daily-paper-smoke/bin/python" -m pip install \
|
||||
"agentscope[model-ollama]==2.0.7" "${DAILY_PAPER_WHEEL}"
|
||||
cd "${RUNNER_TEMP}"
|
||||
"${RUNNER_TEMP}/reme-daily-paper-smoke/bin/python" - <<'PY'
|
||||
from importlib.metadata import distribution
|
||||
|
||||
from reme.plugin_manifest import load_package_manifest
|
||||
|
||||
package = distribution("reme-daily-paper")
|
||||
plugins = {entry.name: entry for entry in package.entry_points if entry.group == "reme.plugins"}
|
||||
assert plugins["daily-paper"].value == "reme_daily_paper"
|
||||
manifest = load_package_manifest("reme_daily_paper", plugin_name="daily-paper")
|
||||
assert set(manifest.backends) == {
|
||||
"daily_paper_collect_step",
|
||||
"daily_paper_rank_step",
|
||||
"daily_paper_select_step",
|
||||
"daily_paper_analyze_step",
|
||||
"daily_paper_digest_step",
|
||||
}
|
||||
assert set(manifest.application_defaults["jobs"]) == {"daily_paper", "daily_paper_cron"}
|
||||
PY
|
||||
|
||||
- name: Upload distributions
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: reme-daily-paper-${{ inputs.version }}
|
||||
path: dist/daily-paper/
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment: pypi
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Download distributions
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
name: reme-daily-paper-${{ inputs.version }}
|
||||
path: dist/daily-paper
|
||||
|
||||
- name: Publish reme-daily-paper
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
|
||||
with:
|
||||
packages-dir: dist/daily-paper
|
||||
47
.github/workflows/release-python.yml
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
name: Release / Python packages
|
||||
|
||||
# Configure a PyPI Trusted Publisher for this repository, workflow, and its
|
||||
# pypi environment before running the manual release.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Release version
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: publish-reme-ai
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and verify distributions
|
||||
uses: ./.github/workflows/_build-python-packages.yml
|
||||
with:
|
||||
expected_version: ${{ inputs.version }}
|
||||
upload_artifacts: true
|
||||
|
||||
publish-reme:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment: pypi
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Download ReMe distributions
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
name: reme-distributions
|
||||
path: dist/reme
|
||||
|
||||
- name: Publish ReMe
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
|
||||
with:
|
||||
packages-dir: dist/reme
|
||||
skip-existing: true
|
||||
158
.github/workflows/release-reme-studio.yml
vendored
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
# Release checklist:
|
||||
# 1. Update reme_studio/pyproject.toml, package.json, and package-lock.json to the same Studio version.
|
||||
# 2. Configure npm Trusted Publishing and PyPI Trusted Publishing with the pypi environment.
|
||||
# 3. Run this workflow manually with the exact Studio version.
|
||||
|
||||
name: Release / ReMe Studio
|
||||
|
||||
run-name: Publish ReMe Studio ${{ inputs.version }} (${{ inputs.npm_tag }})
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Version from the Studio Python and npm manifests
|
||||
required: true
|
||||
type: string
|
||||
npm_tag:
|
||||
description: npm distribution tag
|
||||
required: true
|
||||
default: latest
|
||||
type: choice
|
||||
options:
|
||||
- next
|
||||
- latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: publish-reme-studio
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RELEASE_VERSION: ${{ inputs.version }}
|
||||
NPM_TAG: ${{ inputs.npm_tag }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
||||
with:
|
||||
node-version: "22.22.3"
|
||||
cache: npm
|
||||
cache-dependency-path: reme_studio/package-lock.json
|
||||
|
||||
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Validate Studio package names and version
|
||||
run: |
|
||||
python - <<'PY'
|
||||
import json
|
||||
import os
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
studio = Path("reme_studio")
|
||||
python_manifest = tomllib.loads((studio / "pyproject.toml").read_text(encoding="utf-8"))["project"]
|
||||
npm_manifest = json.loads((studio / "package.json").read_text(encoding="utf-8"))
|
||||
expected = os.environ["RELEASE_VERSION"].removeprefix("v")
|
||||
if python_manifest["name"] != "reme_studio":
|
||||
raise SystemExit(f"Unexpected Python package name: {python_manifest['name']}")
|
||||
if npm_manifest["name"] != "@agentscope-ai/reme_studio":
|
||||
raise SystemExit(f"Unexpected npm package name: {npm_manifest['name']}")
|
||||
if python_manifest["version"] != expected or npm_manifest["version"] != expected:
|
||||
raise SystemExit(
|
||||
f"Studio manifests are {python_manifest['version']} and {npm_manifest['version']}; "
|
||||
f"workflow input is {expected}",
|
||||
)
|
||||
prerelease = "-" in expected
|
||||
if prerelease != (os.environ["NPM_TAG"] == "next"):
|
||||
raise SystemExit("Prereleases must use next; stable releases must use latest")
|
||||
PY
|
||||
|
||||
- name: Install dependencies and run checks
|
||||
working-directory: reme_studio
|
||||
run: |
|
||||
npm ci
|
||||
npm run format:check
|
||||
npm run lint
|
||||
npm test
|
||||
|
||||
- name: Build Studio distributions
|
||||
run: |
|
||||
python -m pip install build twine
|
||||
mkdir -p dist/studio-python dist/studio-npm
|
||||
npm pack ./reme_studio --pack-destination dist/studio-npm
|
||||
python scripts/package_studio.py
|
||||
python -m build reme_studio --outdir dist/studio-python
|
||||
python -m twine check dist/studio-python/*
|
||||
|
||||
- name: Verify Studio distributions and isolated installation
|
||||
run: |
|
||||
STUDIO_WHEEL="$(pwd)/$(ls dist/studio-python/reme_studio-*.whl)"
|
||||
tar -tzf dist/studio-npm/*.tgz | grep '^package/dist-static/index.html$'
|
||||
python -m venv "${RUNNER_TEMP}/reme-studio-package-smoke"
|
||||
"${RUNNER_TEMP}/reme-studio-package-smoke/bin/python" -m pip install "${STUDIO_WHEEL}"
|
||||
cd "${RUNNER_TEMP}"
|
||||
"${RUNNER_TEMP}/reme-studio-package-smoke/bin/python" - <<'PY'
|
||||
from reme_studio import static_dir
|
||||
|
||||
assert (static_dir() / "index.html").is_file()
|
||||
PY
|
||||
|
||||
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: reme-studio-${{ inputs.version }}
|
||||
path: |
|
||||
dist/studio-python/*
|
||||
dist/studio-npm/*
|
||||
if-no-files-found: error
|
||||
|
||||
publish-python:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment: pypi
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
name: reme-studio-${{ inputs.version }}
|
||||
path: dist
|
||||
|
||||
- name: Publish ReMe Studio to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
|
||||
with:
|
||||
packages-dir: dist/studio-python
|
||||
skip-existing: true
|
||||
|
||||
publish-npm:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
||||
with:
|
||||
node-version: "24"
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
name: reme-studio-${{ inputs.version }}
|
||||
path: dist
|
||||
|
||||
- name: Publish ReMe Studio to npm
|
||||
env:
|
||||
NPM_TAG: ${{ inputs.npm_tag }}
|
||||
run: npm publish dist/studio-npm/*.tgz --access public --tag "${NPM_TAG}" --provenance
|
||||
167
.github/workflows/release-typescript.yml
vendored
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
# Release checklist:
|
||||
# 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 or CLAWHUB_TOKEN before enabling ClawHub publication.
|
||||
# 5. Use the `next` tag for prereleases and `latest` only for stable releases.
|
||||
|
||||
name: Release / TypeScript integrations
|
||||
|
||||
run-name: Publish @agentscope-ai/reme ${{ inputs.version }} (${{ inputs.npm_tag }})
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Version from typescript/package.json (for example, 0.1.0)
|
||||
required: true
|
||||
type: string
|
||||
npm_tag:
|
||||
description: npm distribution tag
|
||||
required: true
|
||||
default: latest
|
||||
type: choice
|
||||
options:
|
||||
- next
|
||||
- latest
|
||||
publish_clawhub:
|
||||
description: Also publish the verified tarball to ClawHub
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: publish-agentscope-ai-reme
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.validate.outputs.version }}
|
||||
env:
|
||||
RELEASE_VERSION: ${{ inputs.version }}
|
||||
NPM_TAG: ${{ inputs.npm_tag }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
||||
with:
|
||||
node-version: '22.22.3'
|
||||
|
||||
- name: Validate package name and release version
|
||||
id: validate
|
||||
working-directory: typescript
|
||||
run: |
|
||||
node --input-type=module <<'JS'
|
||||
import { appendFileSync, readFileSync } from 'node:fs';
|
||||
|
||||
const manifest = JSON.parse(readFileSync('package.json', 'utf8'));
|
||||
const expected = process.env.RELEASE_VERSION.replace(/^v/, '');
|
||||
if (manifest.name !== '@agentscope-ai/reme') {
|
||||
throw new Error(`Unexpected package name: ${manifest.name}`);
|
||||
}
|
||||
if (manifest.version !== expected) {
|
||||
throw new Error(`package.json is ${manifest.version}, workflow input is ${expected}`);
|
||||
}
|
||||
const prerelease = manifest.version.includes('-');
|
||||
const npmTag = process.env.NPM_TAG;
|
||||
if (prerelease !== (npmTag === 'next')) {
|
||||
throw new Error(prerelease
|
||||
? 'Prerelease versions must use the next npm tag'
|
||||
: 'Stable versions must use the latest npm tag');
|
||||
}
|
||||
console.log(`Preparing ${manifest.name}@${manifest.version}`);
|
||||
appendFileSync(process.env.GITHUB_OUTPUT, `version=${manifest.version}\n`);
|
||||
JS
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: typescript
|
||||
run: npm ci
|
||||
|
||||
- name: Type-check and test
|
||||
working-directory: typescript
|
||||
run: |
|
||||
npm run format:check
|
||||
npm run lint
|
||||
npm run typecheck
|
||||
npm test
|
||||
npm run test:package
|
||||
npx --yes clawhub@0.23.3 package validate . --json
|
||||
|
||||
- name: Pack npm tarball
|
||||
working-directory: typescript
|
||||
run: |
|
||||
mkdir -p "${RUNNER_TEMP}/reme-typescript-package"
|
||||
npm pack --pack-destination "${RUNNER_TEMP}/reme-typescript-package"
|
||||
|
||||
- name: Upload npm tarball
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: agentscope-ai-reme-${{ inputs.version }}
|
||||
path: ${{ runner.temp }}/reme-typescript-package/*.tgz
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Set up Node for npm
|
||||
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
||||
with:
|
||||
node-version: '24'
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- name: Download npm tarball
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
name: agentscope-ai-reme-${{ inputs.version }}
|
||||
path: dist/typescript
|
||||
|
||||
- name: Reject an existing package version
|
||||
env:
|
||||
PACKAGE_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
PACKAGE_VERSION="${PACKAGE_VERSION#v}"
|
||||
if npm view "@agentscope-ai/reme@${PACKAGE_VERSION}" version >/dev/null 2>&1; then
|
||||
echo "@agentscope-ai/reme@${PACKAGE_VERSION} already exists" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Publish to npm
|
||||
env:
|
||||
NPM_TAG: ${{ inputs.npm_tag }}
|
||||
run: npm publish dist/typescript/*.tgz --access public --tag "${NPM_TAG}" --provenance
|
||||
|
||||
publish-clawhub:
|
||||
if: ${{ inputs.publish_clawhub }}
|
||||
needs: build
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
id-token: write
|
||||
uses: openclaw/clawhub/.github/workflows/package-publish.yml@87ca030c30f3cfb78ab15c8e66b5ff1469c8f9c8 # v0.23.3
|
||||
with:
|
||||
owner: agentscope-ai
|
||||
family: code-plugin
|
||||
version: ${{ needs.build.outputs.version }}
|
||||
tags: ${{ inputs.npm_tag }}
|
||||
source_repo: ${{ github.repository }}
|
||||
source_commit: ${{ github.sha }}
|
||||
source_ref: ${{ github.ref }}
|
||||
source_path: typescript
|
||||
package_artifact_name: agentscope-ai-reme-${{ inputs.version }}
|
||||
wait_for_publication: true
|
||||
secrets:
|
||||
clawhub_token: ${{ secrets.CLAWHUB_TOKEN }}
|
||||
46
.github/workflows/security-codeql.yml
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
name: Security / CodeQL
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '0 1 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
packages: read
|
||||
security-events: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze ${{ matrix.language }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [python, javascript-typescript]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: none
|
||||
|
||||
- name: Perform CodeQL analysis
|
||||
uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4
|
||||
with:
|
||||
category: /language:${{ matrix.language }}
|
||||
28
.gitignore
vendored
|
|
@ -2,6 +2,7 @@
|
|||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
.qoder/
|
||||
*.code-workspace
|
||||
|
||||
# Local environment
|
||||
|
|
@ -29,7 +30,9 @@ htmlcov/
|
|||
# Packaging / build outputs
|
||||
build/
|
||||
dist/
|
||||
node_modules/
|
||||
*.egg-info/
|
||||
typescript/reports/
|
||||
|
||||
# Logs / temporary files
|
||||
*.log
|
||||
|
|
@ -44,6 +47,8 @@ temp*/
|
|||
|
||||
# ReMe runtime data
|
||||
.reme/
|
||||
reme_workspace/
|
||||
reme_workspace_auto_fin_real_test*/
|
||||
vault/
|
||||
*.db
|
||||
*.sqlite
|
||||
|
|
@ -52,3 +57,26 @@ vault/
|
|||
# Documentation build outputs
|
||||
docs/_build/
|
||||
site/
|
||||
|
||||
evaluation/
|
||||
# The pi-Bench suite ships its own trace-history render config, which must
|
||||
# stay in git even though it lives under an evaluation/ directory.
|
||||
!benchmark/pibench/config/bench/evaluation/
|
||||
!benchmark/pibench/config/bench/evaluation/**
|
||||
datasets/
|
||||
|
||||
# Claude Code skills (local only)
|
||||
.claude/skills/
|
||||
|
||||
# Benchmark memory workspaces (created on demand by run.py via mkdir)
|
||||
benchmark/*/workspaces/
|
||||
|
||||
# Benchmark datasets (LongMemEval via download.py, BEAM via git clone)
|
||||
benchmark/*/dataset/
|
||||
|
||||
# Benchmark outputs (created on demand by run.py via mkdir)
|
||||
benchmark/*/results/
|
||||
|
||||
# integration tests outputs
|
||||
tests/integration/logs/
|
||||
daily/
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
exclude: ^skills/
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0
|
||||
|
|
@ -14,10 +16,10 @@ repos:
|
|||
hooks:
|
||||
- id: add-trailing-comma
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 25.9.0
|
||||
rev: 26.5.1
|
||||
hooks:
|
||||
- id: black
|
||||
args: [--line-length=120]
|
||||
args: [--line-length=120, --target-version=py311]
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 7.3.0
|
||||
hooks:
|
||||
|
|
@ -27,7 +29,7 @@ repos:
|
|||
"--max-line-length=120"
|
||||
]
|
||||
- repo: https://github.com/pylint-dev/pylint
|
||||
rev: v4.0.2
|
||||
rev: v4.0.6
|
||||
hooks:
|
||||
- id: pylint
|
||||
exclude:
|
||||
|
|
@ -76,7 +78,7 @@ repos:
|
|||
--max-module-lines=1500,
|
||||
]
|
||||
- repo: https://github.com/regebro/pyroma
|
||||
rev: "5.0"
|
||||
rev: "5.0.1"
|
||||
hooks:
|
||||
- id: pyroma
|
||||
args: [--min=10, .]
|
||||
|
|
|
|||
214
AGENTS.md
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
# AGENTS.md
|
||||
|
||||
This file guides coding agents working in the ReMe repository. Keep changes small, testable, and consistent with the
|
||||
contracts expressed by the current code.
|
||||
|
||||
## Project Principles
|
||||
|
||||
ReMe is a local-first, file-native memory system for agents.
|
||||
|
||||
- User-owned workspace files are the durable source of truth.
|
||||
- Indexes, catalogs, graphs, caches, and generated metadata must remain rebuildable.
|
||||
- Prefer transparent formats and predictable behavior over hidden state.
|
||||
- Preserve user control over workspace paths, configuration, and service boundaries.
|
||||
- Keep concepts focused on project intent; let code and schemas describe implementation.
|
||||
|
||||
When convenience conflicts with these principles, favor data ownership, recoverability, and explicit behavior.
|
||||
|
||||
## Sources of Truth
|
||||
|
||||
Use this order when documentation and implementation disagree:
|
||||
|
||||
1. Current code and public Pydantic schemas.
|
||||
2. Tests that describe supported behavior.
|
||||
3. CLI behavior and the built-in configuration.
|
||||
4. README files and other development documentation.
|
||||
|
||||
Do not duplicate large implementation descriptions in documentation. Express the stable contract and link to the
|
||||
relevant module where useful. When behavior changes intentionally, update the implementation, schemas, tests, defaults,
|
||||
and concise documentation together.
|
||||
|
||||
## Repository Map
|
||||
|
||||
- `reme/reme.py`: CLI entry point; dispatches `start`, `find_reme`, and client calls.
|
||||
- `reme/application.py`: application assembly, dependency ordering, job execution, and lifecycle.
|
||||
- `reme/config/config_parser.py`: YAML/JSON loading, environment expansion, dot-notation parsing, and deep config
|
||||
merging.
|
||||
- `reme/config/default.yaml`: default service, jobs, steps, and components. Other files in
|
||||
`reme/config/` are named configuration variants.
|
||||
- `reme/schema/application_config.py`: typed application, component, and job configuration.
|
||||
- `reme/schema/`: request, response, streaming, memory, graph, and file contracts.
|
||||
- `reme/components/application_context.py`: application-wide wiring and in-memory shared state.
|
||||
- `reme/components/runtime_context.py`: request-scoped data, response, streaming queue, and stop event.
|
||||
- `reme/components/base_component.py`: component lifecycle, dependency binding, and workspace helpers.
|
||||
- `reme/components/component_registry.py`: the frozen built-in registry template and application-local registry factory.
|
||||
- `reme/components/job/`: base, stream, background, and cron job implementations.
|
||||
- `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/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.
|
||||
- `tests/integration/`: service/model tests that may need credentials or external processes.
|
||||
- `reme_studio/`: ReMe Studio frontend source plus the independently published `reme_studio` Python package and
|
||||
`@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.
|
||||
- `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.
|
||||
|
||||
## Development Setup
|
||||
|
||||
ReMe requires Python 3.11 or newer. Install the editable development environment with:
|
||||
|
||||
```bash
|
||||
pip install -e reme_studio -e ".[dev,core]"
|
||||
```
|
||||
|
||||
Before changing behavior, inspect the adjacent implementation, schema, built-in config, and focused tests. Follow
|
||||
existing async and typing patterns unless the task explicitly requires a new contract.
|
||||
|
||||
## Configuration and CLI Contracts
|
||||
|
||||
- CLI syntax is `reme ACTION key=value ...`; leading `-` or `--` on arguments is accepted.
|
||||
- Nested overrides use dot notation. Values support null, booleans, numbers, JSON collections, and quoted JSON strings;
|
||||
leading-zero numeric-looking values remain strings.
|
||||
- `config=<name-or-path>` loads a discovered config name or a `.yaml`, `.yml`, or `.json` file. With no explicit config
|
||||
path, `default` is loaded when available.
|
||||
- Config files expand `${VAR}` and `${VAR:-default}` recursively. An undefined variable without a default is an error.
|
||||
- CLI/config overrides are deep-merged over the loaded file. Do not silently change this merge behavior or stable
|
||||
configuration keys.
|
||||
- `ApplicationConfig` normalizes `workspace_dir` to an expanded absolute path. `session_dir`
|
||||
must remain workspace-relative; standard transcripts live under `{session_dir}/dialog`.
|
||||
- `reme start` runs the configured service. `reme start job=<name> ...` switches to the one-shot CLI service and runs
|
||||
the job through the normal application lifecycle.
|
||||
- Other actions use a client selected from the running service configuration when discoverable, otherwise from local
|
||||
config. Client-selection arguments must not leak into the job payload.
|
||||
|
||||
## Registration and Application Lifecycle
|
||||
|
||||
Component and Step discovery is import-driven:
|
||||
|
||||
- Implementations declare a non-`BASE` `component_type` and register with `@R.register("backend")`
|
||||
or `R.register(Class, "backend")`.
|
||||
- Component packages must be imported through `reme/components/__init__.py`.
|
||||
- Step packages/modules must be reachable through their package `__init__.py` chain and ultimately
|
||||
`reme/steps/__init__.py`.
|
||||
- Adding an implementation without its registration import leaves it undiscoverable at runtime. Treat implementation,
|
||||
registration, import side effect, defaults, and tests as one change.
|
||||
|
||||
`Application` validates config through `ApplicationContext`, creates workspace directories, instantiates the service,
|
||||
configured components, and jobs, and then manages lifecycle as follows:
|
||||
|
||||
- Components start in topological dependency order. Missing required dependencies and cycles fail explicitly; optional
|
||||
dependencies may resolve to `None`.
|
||||
- Jobs start after components in this order: base jobs, stream jobs, background jobs, then cron jobs.
|
||||
- Shutdown closes everything in reverse start order and then shuts down the optional thread pool.
|
||||
- If startup fails, already-started resources are closed.
|
||||
- `BaseComponent.start()` and `close()` are lock-protected and idempotent. Dependencies created by a standalone
|
||||
`default_factory` are owned and closed by the parent component.
|
||||
|
||||
Keep async clients, tasks, executors, and services under this lifecycle. Do not introduce an untracked long-lived
|
||||
resource.
|
||||
|
||||
## Jobs, Steps, and State
|
||||
|
||||
`BaseJob` resolves configured Step classes during job startup and constructs fresh Step instances for every invocation.
|
||||
Job-level kwargs are merged into each `RuntimeContext`, with call-time kwargs taking precedence. Sequential Steps in one
|
||||
invocation share the same `RuntimeContext` and `Response`.
|
||||
|
||||
Treat Step instances as invocation-scoped:
|
||||
|
||||
- Constructor fields and `self.kwargs` hold Step configuration and resolved dependencies. They may be cached or adjusted
|
||||
during that one invocation, but must not be relied on across Job calls.
|
||||
- `self.context.data` holds request inputs and intermediate values shared by sequential Steps.
|
||||
- `self.context.response.answer`, `success`, and `metadata` are request-scoped output. Because the same response travels
|
||||
through the Step chain, later Steps may consume metadata produced earlier, but it is not application-lifetime or
|
||||
durable storage.
|
||||
- `self.app_context.metadata` holds in-memory state shared across Job/Step invocations for the life of one
|
||||
`Application`, such as counters, tool-context state, session maps, or locks.
|
||||
- Workspace files or a dedicated Component/store hold durable state that must survive restart.
|
||||
|
||||
Use narrow, namespaced keys in `app_context.metadata` and protect shared mutable values against concurrent access. The
|
||||
search/draft helpers intentionally mirror tool-context state into
|
||||
`self.kwargs` only when no `ApplicationContext` exists for standalone use and unit tests; do not generalize that
|
||||
compatibility fallback into persistent runtime state. If shared state becomes a stable service contract or needs
|
||||
dedicated lifecycle, locking, or persistence, promote it to a typed context field or Component.
|
||||
|
||||
Additional Step contracts:
|
||||
|
||||
- `Ref` dependencies resolve in this order: Step kwargs, current `RuntimeContext`, then the named application component.
|
||||
The value is cached only on the current Step instance and cleared before each call.
|
||||
- `input_mapping` and `output_mapping` copy keys within `RuntimeContext.data`; missing sources are ignored.
|
||||
- Dispatched Steps receive the current `RuntimeContext`, so their data and response are shared.
|
||||
- Base jobs convert uncaught Step errors into `Response(success=False)`; stream jobs emit an error chunk and always a
|
||||
terminal `DONE`; background jobs let errors reach their supervisor.
|
||||
- Background jobs are never service-exposed. MCP also skips stream jobs. Respect `enable_serve`
|
||||
and any configured service job allowlist.
|
||||
|
||||
## Workspace and File Safety
|
||||
|
||||
- Application startup creates the workspace plus configured metadata, session, memory-session, resource, daily, and
|
||||
digest directories.
|
||||
- File-operation paths are resolved against the workspace and must stay inside it. Home-relative paths are unsupported,
|
||||
traversal escapes are rejected, and `_allowed_paths` restrictions fail closed when invalid.
|
||||
- Preserve per-path locking, encoding detection, byte limits, truncation behavior, and optimistic
|
||||
`expected_mtime` checks when modifying file operations.
|
||||
- Do not bypass the existing file steps or stores in a way that weakens workspace containment.
|
||||
- Never write test state into the repository's `.reme/`; use `tmp_path` or another isolated workspace.
|
||||
- Do not delete or rewrite user memory to repair an index or make a test pass. Rebuild derived state from source files
|
||||
instead.
|
||||
|
||||
## Validation
|
||||
|
||||
Use the narrowest useful check while iterating, then broaden it according to risk.
|
||||
|
||||
Focused test:
|
||||
|
||||
```bash
|
||||
pytest tests/unit/path/to/test_file.py -v
|
||||
```
|
||||
|
||||
Main unit suite:
|
||||
|
||||
```bash
|
||||
pytest tests/unit -v --tb=long -s --log-cli-level=WARNING
|
||||
```
|
||||
|
||||
Repository formatting and lint checks:
|
||||
|
||||
```bash
|
||||
pre-commit run --all-files
|
||||
```
|
||||
|
||||
Black and Flake8 use a 120-character line limit and Python 3.11 formatting; Pylint is also run by pre-commit. If
|
||||
`reme_studio/` changes, use its Node 22.13+ scripts and run the proportionate checks from that directory, such as
|
||||
`npm run format:check`, `npm run lint`, or `npm test`.
|
||||
|
||||
Integration tests may contact real model providers, services, or agent subprocesses and can require credentials. Do not
|
||||
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.
|
||||
|
||||
## Change Guardrails
|
||||
|
||||
- Preserve unrelated user changes in a dirty working tree.
|
||||
- Make the smallest coherent change and avoid unrelated cleanup or broad refactors.
|
||||
- Do not edit generated output when the source can be changed instead. The publish workflow builds
|
||||
`reme_studio/dist-static` and stages it under `reme_studio/src/reme_studio/static`; change `reme_studio/` source for
|
||||
frontend work.
|
||||
- Do not silently change CLI flags, configuration keys, workspace layouts, serialized schemas, endpoint shapes,
|
||||
streaming termination, or service interfaces. Preserve compatibility where practical and document intentional
|
||||
migrations.
|
||||
- Do not introduce dependencies without a concrete repository-level need.
|
||||
- Do not commit `.env` files, credentials, runtime memory, logs, indexes, caches, benchmark outputs, or generated
|
||||
Studio distributions.
|
||||
- State which validations passed and which relevant checks were not run in the final handoff.
|
||||
|
||||
If a requirement is ambiguous, infer intent from nearby code, schemas, defaults, and tests. Ask the user only when the
|
||||
remaining choice would materially alter a public contract, user data, or an external system.
|
||||
1
CLAUDE.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
AGENTS.md
|
||||
361
README.md
|
|
@ -1,5 +1,5 @@
|
|||
<p align="center">
|
||||
<img src="docs/figure/reme_logo.png" alt="ReMe Logo" width="50%">
|
||||
<img src="https://raw.githubusercontent.com/agentscope-ai/ReMe/main/docs/figure/reme_logo.png" alt="ReMe Logo" width="50%">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
<a href="https://pepy.tech/project/reme-ai/"><img src="https://img.shields.io/pypi/dm/reme-ai" alt="PyPI Downloads"></a>
|
||||
<a href="https://github.com/agentscope-ai/ReMe"><img src="https://img.shields.io/github/commit-activity/m/agentscope-ai/ReMe?style=flat-square" alt="GitHub commit activity"></a>
|
||||
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-black" alt="License"></a>
|
||||
<a href="https://reme.agentscope.io"><img src="https://img.shields.io/badge/docs-ReMe-blue" alt="Documentation"></a>
|
||||
<a href="./README.md"><img src="https://img.shields.io/badge/English-Click-yellow" alt="English"></a>
|
||||
<a href="./README_ZH.md"><img src="https://img.shields.io/badge/简体中文-点击查看-orange" alt="简体中文"></a>
|
||||
<a href="https://github.com/agentscope-ai/ReMe"><img src="https://img.shields.io/github/stars/agentscope-ai/ReMe?style=social" alt="GitHub Stars"></a>
|
||||
|
|
@ -19,36 +20,47 @@
|
|||
</p>
|
||||
|
||||
<p align="center">
|
||||
<strong>A memory management toolkit for AI agents — Remember Me, Refine Me.</strong><br>
|
||||
<strong>A local-first, self-evolving personal knowledge base for AI agents.</strong><br>
|
||||
</p>
|
||||
|
||||
> Previous versions: [0.3.x](https://github.com/agentscope-ai/ReMe/tree/reme_v3) ·
|
||||
> [0.2.x](https://github.com/agentscope-ai/ReMe/tree/v0.2.0.6) ·
|
||||
> [MemoryScope](https://github.com/agentscope-ai/ReMe/tree/memoryscope_branch)
|
||||
|
||||
🧠 ReMe is a memory management toolkit for **AI agents**. It turns conversations and resources into readable, editable, and searchable file-based long-term memory.
|
||||
## ✨ Why ReMe?
|
||||
|
||||
## ✨ Core Ideas
|
||||
🧠 ReMe turns conversations and resources into readable, editable, searchable, and interconnected Markdown memory. Agents
|
||||
such as QwenPaw and DeepSeek Harness can share the same workspace to retrieve, maintain, and evolve knowledge, while
|
||||
users retain control of the durable files.
|
||||
|
||||
- **Memory as File**: Markdown files with frontmatter and wikilinks serve as memory nodes that both users and agents can read and write directly.
|
||||
- **Self-evolving knowledge base**: Auto Memory, Auto Resource, and Auto Dream progressively transform conversations and resources into long-term Markdown memories, while automatically building wikilink relationships.
|
||||
- **Progressive hybrid search**: ReMe combines wikilinks, BM25, and embeddings for hybrid retrieval across keyword matching, semantic recall, and relationship expansion.
|
||||
- **Agent-friendly integration**: SKILL.md + CLI integration makes it easy for different agents to read, write, maintain, and reuse memory.
|
||||
- **Memory as File, File as Memory**: ReMe stores durable memory as ordinary Markdown with frontmatter and wikilinks.
|
||||
Users and agents can inspect, edit, move, sync, and back it up with familiar tools, while indexes and generated
|
||||
metadata remain rebuildable.
|
||||
- **Self-evolving knowledge base**: ReMe progressively turns conversations and resources into daily notes and long-term
|
||||
knowledge, preserving sources while refining facts, preferences, procedures, and relationships over time.
|
||||
- **Recall is precise and context-aware.** BM25, optional embeddings, and wikilink expansion retrieve relevant
|
||||
line-level passages and their relationships without loading the entire knowledge base into the agent context.
|
||||
- **One memory workspace works across agents.** Personal assistants, coding agents, and other agent runtimes can share
|
||||
the same local workspace through native integrations, SKILL.md, CLI, HTTP, MCP, or Python APIs.
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/figure/design-philosophy.svg" alt="ReMe Design Philosophy" width="92%">
|
||||
</p>
|
||||
|
||||
<details>
|
||||
<summary><b>Use Cases</b></summary>
|
||||
## 📰 Latest Updates
|
||||
|
||||
<br>
|
||||
|
||||
- **Personal assistants**: Provide long-term memory for agents such as [QwenPaw](https://github.com/agentscope-ai/QwenPaw).
|
||||
- **Coding assistants**: Preserve coding style, project background, and workflow experience across sessions.
|
||||
- **Knowledge QA**: Progressively transform resources and conversations into a searchable, traceable, and linked Markdown knowledge base.
|
||||
- **Task automation**: Reuse successful paths, lessons from failures, and operation procedures from past tasks.
|
||||
</details>
|
||||
- [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
|
||||
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
|
||||
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
|
||||
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/)
|
||||
has been accepted to Findings of ACL 2026.
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
|
|
@ -67,21 +79,14 @@ Install from source:
|
|||
```bash
|
||||
git clone https://github.com/agentscope-ai/ReMe.git
|
||||
cd ReMe
|
||||
pip install -e ".[core]"
|
||||
pip install -e reme_studio -e ".[core]"
|
||||
cd reme_studio
|
||||
npm ci
|
||||
npm run build:static
|
||||
cd ..
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Configure environment variables:
|
||||
|
||||
```bash
|
||||
cat > .env <<'EOF'
|
||||
EMBEDDING_API_KEY=sk-xxx
|
||||
EMBEDDING_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
LLM_API_KEY=sk-xxx
|
||||
LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
EOF
|
||||
```
|
||||
The static build requires Node.js 22.13 or newer and makes Studio available from the source tree.
|
||||
|
||||
### Start the Service
|
||||
|
||||
|
|
@ -96,141 +101,281 @@ 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.
|
||||
|
||||
```bash
|
||||
reme version
|
||||
reme health_check
|
||||
reme help
|
||||
curl -s http://127.0.0.1:2333/version -H 'Content-Type: application/json' -d '{}'
|
||||
```
|
||||
|
||||
### Agent Integration
|
||||
### 5-Minute Memory Demo
|
||||
|
||||
ReMe integrates with supported agent frameworks through **SKILL.md + CLI + hooks (optional)**. A typical integration looks like this:
|
||||
With the service running, write a memory node, let ReMe index it, then retrieve it:
|
||||
|
||||
- Add the [memory skill](skills/reme_memory/SKILL.md) to the agent and grant the agent permission to call the CLI.
|
||||
- Call `auto_memory` and `proactive` from agent hooks as needed, so conversations are automatically consolidated into daily memories and proactive reminders can be read at the right time.
|
||||
- `auto_index` and `auto_resource` are triggered by file monitoring to maintain indexes and process resources.
|
||||
- `auto_dream` is triggered by a scheduled task to further organize daily memories into reusable long-term digest memories.
|
||||
```bash
|
||||
reme write \
|
||||
path=digest/wiki/quick-start-demo \
|
||||
name="Quick Start Demo" \
|
||||
description="A first ReMe memory node" \
|
||||
content="# Quick Start Demo
|
||||
|
||||
QwenPaw 2.0 will integrate the new ReMe version. A Claude Code plugin will also be released later to reduce manual integration work.
|
||||
ReMe stores agent memory as readable Markdown.
|
||||
|
||||
For more details, see the [Quick Start](docs/zh/quick_start.md).
|
||||
Related: [[digest/wiki/memory-as-file.md]]"
|
||||
|
||||
## 📁 Memory System
|
||||
reme search query="agent memory markdown" limit=5
|
||||
reme read path=digest/wiki/quick-start-demo start_line=1 end_line=20
|
||||
```
|
||||
|
||||
The generated file is ordinary Markdown with frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: Quick Start Demo
|
||||
description: A first ReMe memory node
|
||||
---
|
||||
|
||||
# Quick Start Demo
|
||||
|
||||
ReMe stores agent memory as readable Markdown.
|
||||
|
||||
Related: [[digest/wiki/memory-as-file.md]]
|
||||
```
|
||||
|
||||
### ReMe Studio (Optional)
|
||||
|
||||
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.
|
||||
|
||||
### Optional Model Configuration
|
||||
|
||||
Configure environment variables when you want LLM-powered memory evolution or embedding retrieval. Embeddings are
|
||||
disabled by default, so the default setup does not start an embedding model or require an embedding API key.
|
||||
|
||||
```bash
|
||||
cat > .env <<'EOF'
|
||||
# Optional: used only after embedding components are explicitly enabled in the config.
|
||||
# EMBEDDING_API_KEY=sk-xxx
|
||||
# EMBEDDING_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
|
||||
# Required for auto_memory, auto_resource, and auto_dream.
|
||||
LLM_API_KEY=sk-xxx
|
||||
LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
EOF
|
||||
```
|
||||
|
||||
Basic file operations, BM25 search, wikilink traversal, and reading proactive topics can run without LLM credentials.
|
||||
|
||||
> [!NOTE]
|
||||
> To enable embedding-based semantic retrieval, uncomment `components.as_embedding` and
|
||||
> `components.embedding_store` in [`reme/config/default.yaml`](reme/config/default.yaml), then change
|
||||
> `components.file_store.default.embedding_store` from `""` to `default`. See the
|
||||
> [memory search guide](docs/en/memory_search.md) for details.
|
||||
|
||||
## 🤝 Use ReMe with Your Agent
|
||||
|
||||
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. Host integrations can add memory guidance, recall, and capture to the agent
|
||||
lifecycle according to the capabilities of each runtime.
|
||||
|
||||
| Agent | Recommended path | Available after integration |
|
||||
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| **DeepSeek Harness** | Install [`@agentscope-ai/reme`](typescript/README.md#deepseek-harness) with `dsh plugin --profile web add @agentscope-ai/reme`. | Long-term memory guidance, the `reme_search` tool, and automatic capture of completed main-agent turns. |
|
||||
| **OpenClaw** | Install [`@agentscope-ai/reme`](typescript/README.md#openclaw) with `openclaw plugins install @agentscope-ai/reme`. | Native memory tools, recall before user-triggered runs, and automatic turn capture. |
|
||||
| **QwenPaw** | Embed ReMe in-process through its Python API. | Reuse the host lifecycle and model config while keeping memory local and file-based. |
|
||||
| **Claude Code** | Start the streamable HTTP MCP service and install [the ReMe plugin](integrations/claude_code/reme). | MCP recall tools, the `reme-memory` skill, and a Stop hook that records sessions automatically. |
|
||||
| **Hermes** | Start the HTTP service and install [the ReMe provider](integrations/hermes_agent). | Recall before model calls and asynchronous `auto_memory` after each completed turn. |
|
||||
| **Codex and other CLI agents** | Install or copy the [ReMe Memory skill](skills/reme_memory/SKILL.md). | Search, read, and write memory through the CLI; automatic capture requires host lifecycle integration. |
|
||||
|
||||
<p align="center"><b>Integration demos</b></p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"></td>
|
||||
<td width="45%" align="center"><b>Auto Memory</b></td>
|
||||
<td width="45%" align="center"><b>Auto Dream</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><b>QwenPaw</b></td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/qwenpaw-auto-memory.gif" alt="QwenPaw Auto Memory demo" width="100%">
|
||||
</td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/qwenpaw-auto-dream.gif" alt="QwenPaw Auto Dream demo" width="100%">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><b>Claude Code</b></td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/cc-auto-memory.gif" alt="Claude Code Auto Memory demo" width="100%">
|
||||
</td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/cc-auto-dream.gif" alt="Claude Code Auto Dream demo" width="100%">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## 🧠 How ReMe Works
|
||||
|
||||
> Memory as File, File as Memory.
|
||||
|
||||
ReMe treats **memory as files**, progressively processing raw conversations and external resources from `session/` and `resource/` into `daily/`, then consolidating them into reusable long-term knowledge nodes under `digest/`.
|
||||
ReMe treats **memory as files**, progressively processing filtered conversation source records and external resources
|
||||
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
|
||||
### Workspace Layout
|
||||
|
||||
```text
|
||||
<workspace_dir>/
|
||||
├── metadata/ # Persistent system state such as indexes, graphs, and catalogs
|
||||
├── session/ # Raw conversations and agent sessions
|
||||
├── metadata/ # Rebuildable indexes, graphs, catalogs, and caches
|
||||
├── session/ # Conversation source records and agent sessions
|
||||
│ ├── dialog/
|
||||
│ │ └── <session_id>.jsonl
|
||||
│ ├── agentscope/
|
||||
│ │ └── <session_id>.jsonl # Source messages saved by auto_memory
|
||||
│ └── claude_code/
|
||||
│ └── <session_id>.jsonl # ReMe copy used by auto_memory_cc
|
||||
├── mem_session/ # Generated agent-wrapper sessions/config, not user memory
|
||||
│ ├── agentscope/
|
||||
│ ├── claude_config/
|
||||
│ └── codex/
|
||||
├── resource/ # External raw materials
|
||||
│ ├── <resource>.<ext> # Root-level files enter today's daily layer
|
||||
│ └── YYYY-MM-DD/
|
||||
│ └── <resource>.<ext>
|
||||
├── daily/ # Lightly processed memory: daily facts, conversation summaries, resource readings
|
||||
│ ├── YYYY-MM-DD.md
|
||||
│ └── YYYY-MM-DD/
|
||||
│ ├── <session_id>.md
|
||||
│ ├── <resource_stem>.md
|
||||
│ ├── <generated_name>.md # Topic-named conversation or resource card
|
||||
│ └── interests.yaml
|
||||
└── digest/ # Long-term memory: personal facts, procedural experience, knowledge nodes
|
||||
├── personal/
|
||||
│ └── {topic/event}.md
|
||||
├── procedure/
|
||||
│ └── {topic/event}.md
|
||||
└── wiki/
|
||||
└── {topic/event}.md
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/figure/reme-overview.svg" alt="ReMe file-based memory system overview" width="92%">
|
||||
</p>
|
||||
|
||||
### Automatic Memory Flow
|
||||
### Memory Lifecycle
|
||||
|
||||
ReMe's automatic memory flow gradually turns raw conversations and resources into searchable, traceable, and reusable file-based memory. During normal operation, background watchers maintain indexes and process resources, agent hooks trigger conversation memory, and long-term consolidation plus proactive reminders run through scheduled tasks or on-demand calls.
|
||||
ReMe follows a capture → index → consolidate → recall loop. Workspace files remain the durable source of truth;
|
||||
everything under `metadata/` is rebuildable.
|
||||
|
||||
<details>
|
||||
<summary><b>Automatic Memory Capabilities</b></summary>
|
||||
|
||||
<br>
|
||||
|
||||
| Capability | How it runs | Purpose | Main parameters |
|
||||
|-----------------------------------------------|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------|
|
||||
| [`auto_index`](docs/zh/memory_search.md) | Background maintenance via `index_update_loop` | Scans on startup and continuously watches Markdown/JSONL changes in `daily/`, `digest/`, and `resource/`; updates chunk, BM25, embedding, and wikilink graph indexes. | Config: `watch_dirs`, `watch_suffixes` |
|
||||
| [`auto_memory`](docs/zh/auto_memory.md) | Agent after-reply hook; also callable on demand | Saves raw conversation text and turns long-term valuable information into `daily/<date>/<session_id>.md` memory cards. | Required: `messages`; optional: `session_id`, `memory_hint` |
|
||||
| [`auto_resource`](docs/zh/auto_resource.md) | Automatically triggered by resource watching; also callable on demand | Reads resource changes under `resource/<date>/` and creates or updates LLM-named daily resource cards linked by `source_resource`. | Required: `changes`; each item may include `path`, `file_path`, `change` |
|
||||
| [`auto_dream`](docs/zh/auto_dream.md) | Scheduled by `dream_cron`; also callable on demand | Scans daily input for a given date, extracts long-term memory units, integrates them into `digest/`, and writes `daily/<date>/interests.yaml`. | `date`, `hint`, `topic_count`, `topic_diversity_days` |
|
||||
| [`proactive`](docs/zh/proactive.md) | Read on demand before agent proactive reminders | Reads `interests.yaml` generated by `auto_dream` and exposes topics worth attention to the upper-level agent; the caller decides whether to remind the user. | `date`, `include_content` |
|
||||
|
||||
</details>
|
||||
| Capability | Entry point | What it does | Output |
|
||||
| ------------------------------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
|
||||
| [`auto_memory`](docs/en/auto_memory.md) | Agent hook or `reme auto_memory` | Distills useful conversation facts while preserving a filtered conversation source record. | `session/dialog/*.jsonl`, `daily/<date>/<generated-name>.md` |
|
||||
| [`auto_resource`](docs/en/auto_resource.md) | Resource watcher or `reme auto_resource` | Turns files under `resource/` into source-linked, content-named daily cards. | `daily/<date>/<resource-card>.md` |
|
||||
| [`auto_index`](docs/en/memory_search.md) | Background watcher or `reme reindex` | Live-indexes Markdown in `daily/` and `digest/`; a full rebuild also scans `resource/` and JSONL. | Searchable chunks, BM25, wikilink graph, and optional vectors |
|
||||
| [`auto_dream`](docs/en/auto_dream.md) | `dream_cron` or `reme auto_dream` | By default, extracts up to five reusable units from changed files in the latest two-day window, then creates, corroborates, refines, or corrects digest nodes. | `digest/**`, `daily/<date>/interests.yaml` |
|
||||
| [`proactive`](docs/en/proactive.md) | `reme proactive` before an agent decides to act | Reads topics generated by `auto_dream`; the host agent decides whether and how to mention them. | Structured topics from `daily/<date>/interests.yaml` |
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="50%">
|
||||
<img src="docs/figure/memory-as-file.svg" alt="Memory as File" width="100%">
|
||||
<img src="docs/figure/memory-as-file.svg" alt="Memory as File" width="92%">
|
||||
</td>
|
||||
<td align="center" width="50%">
|
||||
<img src="docs/figure/auto-memory-resource.svg" alt="Auto Memory and Resource" width="100%">
|
||||
<img src="docs/figure/auto-memory-resource.svg" alt="Auto Memory and Resource" width="92%">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" width="50%">
|
||||
<img src="docs/figure/auto-dream-and-proactive.svg" alt="Auto Dream and Proactive" width="100%">
|
||||
<img src="docs/figure/auto-dream-and-proactive.svg" alt="Auto Dream and Proactive" width="92%">
|
||||
</td>
|
||||
<td align="center" width="50%">
|
||||
<img src="docs/figure/auto-index-and-memory-search.svg" alt="Auto Index and Memory Search" width="100%">
|
||||
<img src="docs/figure/auto-index-and-memory-search.svg" alt="Auto Index and Memory Search" width="92%">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### Workspace Operation Interface
|
||||
Search returns matching chunks with line ranges and bounded wikilink neighbors. Optional vector results are fused with
|
||||
BM25 through reciprocal rank fusion (RRF).
|
||||
|
||||
ReMe operates the workspace through a unified CLI / Service Job interface. Agents usually only need retrieval, read, write, edit, and automatic memory commands. Lower-level indexing, frontmatter, and file operation commands are mainly for maintenance, debugging, or advanced integration.
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> `proactive` only reads and exposes interest topics produced by Auto Dream. It does not independently browse the web,
|
||||
> send notifications, or rewrite the knowledge base; the host agent decides whether and how to act on a topic.
|
||||
|
||||
<details>
|
||||
<summary><b>Workspace Operation Interface</b></summary>
|
||||
## 📊 Benchmarks
|
||||
|
||||
<br>
|
||||
ReMe evaluates multi-session and long-context memory with agentic search-and-read workflows. The figures below are the
|
||||
published reference runs in this repository; model, prompt, dataset, and judging details are documented with each
|
||||
benchmark.
|
||||
|
||||
| Category | name | Description | Parameters |
|
||||
|----------------|--------------------------------------|-----------------------------------------------------------------------------|--------------------------------------------------------|
|
||||
| System status | `version` | Returns the ReMe package version. | None |
|
||||
| System status | `health_check` | Returns a health-check summary for ReMe components. | None |
|
||||
| System status | `help` | Lists registered jobs and their metadata. | None |
|
||||
| Retrieval/read | [`search`](docs/zh/memory_search.md) | Performs hybrid retrieval in the workspace with vector recall, BM25, and RRF fusion. | Required: `query`; optional: `limit`, `min_score` |
|
||||
| Retrieval/read | `node_search` | Recalls similar digest nodes by candidate abstraction name and description, mainly for `auto_dream` deduplication or association. | Required: `query`; optional: `limit` |
|
||||
| Retrieval/read | `traverse` | Traverses the wikilink graph from a specified path. | Required: `path`; optional: `depth`, `direction` |
|
||||
| Retrieval/read | `read` | Reads a Markdown file under the workspace. | Required: `path`; optional: `start_line`, `end_line` |
|
||||
| Retrieval/read | `read_image` | Reads an image file under the workspace and returns base64. | Required: `path` |
|
||||
| Index | `reindex` | Clears file-store indexes and rebuilds indexes from existing files. | Config: `watch_dirs`, `watch_suffixes` |
|
||||
| Daily | `daily_list` | Lists notes for a day. | `date` |
|
||||
| Daily | `daily_reindex` | Rebuilds the day-index page `daily/<date>.md`. | `date` |
|
||||
| Metadata | `frontmatter_read` | Reads file frontmatter. | Required: `path` |
|
||||
| Metadata | `frontmatter_update` | Merges key-values into file frontmatter. | Required: `path`, `metadata` |
|
||||
| Metadata | `frontmatter_delete` | Deletes specified keys from file frontmatter. | Required: `path`, `keys` |
|
||||
| File operation | `stat` | Gets workspace path status, including size, mtime, existence, and file/directory type. | Required: `path` |
|
||||
| File operation | `list` | Lists files under a workspace path. | `path`, `recursive`, `limit` |
|
||||
| File operation | `write` | Creates or overwrites a Markdown file and writes name/description frontmatter. | Required: `path`, `name`, `description`, `content`; optional: `metadata` |
|
||||
| File operation | `edit` | Performs full-text find-and-replace on a Markdown file. | Required: `path`, `old`, `new` |
|
||||
| File operation | `move` | Moves or renames a workspace file and rewrites inbound wikilinks by default. | Required: `src_path`, `dst_path`; optional: `overwrite`, `retarget` |
|
||||
| File operation | `delete` | Deletes a workspace file or folder and returns inbound wikilinks that still exist. | Required: `path` |
|
||||
| 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 |
|
||||
|
||||
</details>
|
||||
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
|
||||
measures proactive handling of hidden intent, clarification, cross-session preferences and conventions, task
|
||||
dependencies, and underspecified requests.
|
||||
|
||||
## 🤝 Community and Support
|
||||
## 🧩 Extensions and Plugins
|
||||
|
||||
- **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.
|
||||
- **Code contributions**: Before making changes, read the [contribution guide](docs/zh/contributing.md) and [code framework](docs/zh/framework.md), and follow the CLI / Service / Application / Job / Step / Component layering.
|
||||
- **Documentation contributions**: For user-visible installation, configuration, invocation, or behavior changes, update `docs/zh/` or `README.md` accordingly.
|
||||
- **Commit convention**: Conventional Commits are recommended, for example `feat(search): add link expansion option` or `docs(zh): update quick start`.
|
||||
- **Pre-submit checks**: Before submitting a PR, try to run `pre-commit run --all-files` and `pytest`. If tests depending 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/).
|
||||
Plugins are optional Python distributions that contribute Component, Step, or Job backends and configuration. They are
|
||||
installed separately and enabled explicitly by configuration. Daily Paper and Auto Fin are independently packaged
|
||||
plugins; see the source distributions and their documentation for [Daily Paper](plugins/daily_paper/README.md) and
|
||||
[Auto Fin](plugins/auto-fin/README.md).
|
||||
|
||||
| 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. |
|
||||
|
||||
See [Plugin Management](docs/en/plugin_management.md) to install, inspect, validate, enable, and uninstall ReMe plugins.
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
These guides cover the main user workflows and the runtime contracts implemented by the current code.
|
||||
|
||||
| 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. |
|
||||
| [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. |
|
||||
| [Auto Dream](docs/en/auto_dream.md) and [Auto Link](docs/en/auto_link.md) | Consolidate daily notes into evolving digest nodes and readable wikilink relationships. |
|
||||
| [Memory Search](docs/en/memory_search.md) | Use BM25, optional vectors, RRF fusion, line-range recall, and progressive link expansion. |
|
||||
| [Proactive](docs/en/proactive.md) | Read interest topics safely and integrate them into a host agent's decision flow. |
|
||||
| [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. |
|
||||
|
||||
## 🛠️ Common Commands
|
||||
|
||||
Run `reme help` for the full job list. Common workspace and maintenance commands are:
|
||||
|
||||
| Command | Purpose |
|
||||
| ----------------------------------------- | --------------------------------------------------------------------------------- |
|
||||
| `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 reindex` | Rebuild search and wikilink indexes from existing files. |
|
||||
|
||||
## 🤝 Community and Contributing
|
||||
|
||||
- **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 repository's
|
||||
[contribution guide](docs/en/contributing.md). Source, schemas, and tests are the authoritative architecture and
|
||||
extension guide.
|
||||
- **Documentation contributions**: Update the canonical files under `docs/en/`, `docs/zh/`, or the relevant package
|
||||
directory in this repository. The documentation site is generated from these files.
|
||||
- **Commit convention**: Conventional Commits are recommended, for example `feat(search): add link expansion option` or
|
||||
`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.
|
||||
- **Documentation**: Visit [reme.agentscope.io](https://reme.agentscope.io).
|
||||
|
||||
### Contributors
|
||||
|
||||
|
|
@ -243,8 +388,8 @@ Thanks to everyone who has contributed to ReMe:
|
|||
## 📄 Citation
|
||||
|
||||
```bibtex
|
||||
@software{AgentscopeReMe2026,
|
||||
title = {AgentscopeReMe: Memory Management Kit for Agents},
|
||||
@software{ReMe2026,
|
||||
title = {Remember me, Refine me: Memory Management Kit for Agents},
|
||||
author = {ReMe Team},
|
||||
url = {https://reme.agentscope.io},
|
||||
year = {2026}
|
||||
|
|
@ -254,7 +399,3 @@ Thanks to everyone who has contributed to ReMe:
|
|||
## ⚖️ License
|
||||
|
||||
This project is open source under the Apache License 2.0. See [LICENSE](./LICENSE) for details.
|
||||
|
||||
## 📈 Star History
|
||||
|
||||
[](https://www.star-history.com/#agentscope-ai/ReMe&Date)
|
||||
|
|
|
|||
345
README_ZH.md
|
|
@ -1,5 +1,5 @@
|
|||
<p align="center">
|
||||
<img src="docs/figure/reme_logo.png" alt="ReMe Logo" width="50%">
|
||||
<img src="https://raw.githubusercontent.com/agentscope-ai/ReMe/main/docs/figure/reme_logo.png" alt="ReMe Logo" width="50%">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
<a href="https://pepy.tech/project/reme-ai/"><img src="https://img.shields.io/pypi/dm/reme-ai" alt="PyPI Downloads"></a>
|
||||
<a href="https://github.com/agentscope-ai/ReMe"><img src="https://img.shields.io/github/commit-activity/m/agentscope-ai/ReMe?style=flat-square" alt="GitHub commit activity"></a>
|
||||
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-black" alt="License"></a>
|
||||
<a href="https://reme.agentscope.io"><img src="https://img.shields.io/badge/docs-ReMe-blue" alt="文档"></a>
|
||||
<a href="./README.md"><img src="https://img.shields.io/badge/English-Click-yellow" alt="English"></a>
|
||||
<a href="./README_ZH.md"><img src="https://img.shields.io/badge/简体中文-点击查看-orange" alt="简体中文"></a>
|
||||
<a href="https://github.com/agentscope-ai/ReMe"><img src="https://img.shields.io/github/stars/agentscope-ai/ReMe?style=social" alt="GitHub Stars"></a>
|
||||
|
|
@ -19,35 +20,46 @@
|
|||
</p>
|
||||
|
||||
<p align="center">
|
||||
<strong>A memory management toolkit for AI agents — Remember Me, Refine Me.</strong><br>
|
||||
<strong>面向 AI Agent 的 local-first 自进化个人知识库。</strong><br>
|
||||
</p>
|
||||
|
||||
> 历史版本:[0.3.x](https://github.com/agentscope-ai/ReMe/tree/reme_v3) ·
|
||||
> [0.2.x](https://github.com/agentscope-ai/ReMe/tree/v0.2.0.6) ·
|
||||
> [MemoryScope](https://github.com/agentscope-ai/ReMe/tree/memoryscope_branch)
|
||||
|
||||
🧠 ReMe 是一个面向 **AI 智能体** 的记忆管理工具,可将对话和资料沉淀为可读、可编辑、可检索的文件化长期记忆。
|
||||
## ✨ 为什么选择 ReMe?
|
||||
|
||||
## ✨ 核心创新
|
||||
🧠 ReMe 将对话和资料持续沉淀为可读、可编辑、可检索、相互链接的 Markdown 记忆。QwenPaw、DeepSeek Harness 等 Agent
|
||||
可以共享同一个 workspace,共同检索、维护和演化知识,而持久文件始终由用户掌控。
|
||||
|
||||
- **Memory as File**:以带 frontmatter 和 wikilink 的 Markdown 作为记忆节点,让用户和 Agent 都能直接读写。
|
||||
- **自进化知识库**:通过 Auto Memory、Auto Resource 和 Auto Dream,把对话与资料逐步加工为长期 Markdown 记忆,并自动建立 wikilink 关系。
|
||||
- **渐进式混合搜索**:融合 wikilink、BM25 和 embedding,支持从关键词匹配到语义召回、关系扩展的混合检索。
|
||||
- **Agent 友好集成**:通过 SKILL.md + CLI 接入,方便不同 Agent 读写、维护与复用记忆。
|
||||
- **Memory as File, File as Memory**:ReMe 使用带 frontmatter 和 wikilink 的普通 Markdown 保存持久记忆。用户和 Agent
|
||||
都可以使用熟悉的工具查看、编辑、移动、同步和备份;索引及生成的元数据均可重建。
|
||||
- **自进化知识库**:ReMe 将对话和资料逐步加工为 daily note 与长期知识,在保留来源的同时,持续提炼事实、偏好、
|
||||
流程经验及其关系。
|
||||
- **精准召回所需上下文。** ReMe 结合 BM25、可选 embedding 和 wikilink 展开,召回带行号的相关片段及其关系,无需把整个知识库塞入
|
||||
Agent 上下文。
|
||||
- **一个 workspace,可供不同 Agent 共同使用。** 个人助理、coding agent 和其他 Agent runtime 可以通过原生集成、SKILL.md、CLI、
|
||||
HTTP、MCP 或 Python API 共享同一个本地记忆空间。
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/figure/design-philosophy.svg" alt="ReMe 设计理念" width="92%">
|
||||
</p>
|
||||
|
||||
<details>
|
||||
<summary><b>适用场景</b></summary>
|
||||
## 📰 最新动态
|
||||
|
||||
<br>
|
||||
- **个人助理**:为 [QwenPaw](https://github.com/agentscope-ai/QwenPaw) 等 Agent 提供长期记忆。
|
||||
- **编程助手**:沉淀代码风格、项目背景和流程经验,跨会话保持一致。
|
||||
- **知识问答**:把资料和对话渐进加工成可检索、可追溯、可链接的 Markdown 知识库。
|
||||
- **任务自动化**:复用历史任务中的成功路径、失败教训和操作流程。
|
||||
</details>
|
||||
- [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/?doc=toolmemory-zh)已发布,见
|
||||
[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 小时的主题相关财联社新闻,通过本地记忆搜索回顾历史材料并构建
|
||||
wikilink。
|
||||
- [2026.07] -
|
||||
我们的论文 [Remember Me, Refine Me: A Dynamic Procedural Memory Framework for Experience-Driven Agent Evolution](https://aclanthology.org/2026.findings-acl.829/)
|
||||
已被 Findings of ACL 2026 接收。
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
|
|
@ -66,21 +78,14 @@ pip install "reme-ai[core]"
|
|||
```bash
|
||||
git clone https://github.com/agentscope-ai/ReMe.git
|
||||
cd ReMe
|
||||
pip install -e ".[core]"
|
||||
pip install -e reme_studio -e ".[core]"
|
||||
cd reme_studio
|
||||
npm ci
|
||||
npm run build:static
|
||||
cd ..
|
||||
```
|
||||
|
||||
### 环境变量
|
||||
|
||||
配置环境变量:
|
||||
|
||||
```bash
|
||||
cat > .env <<'EOF'
|
||||
EMBEDDING_API_KEY=sk-xxx
|
||||
EMBEDDING_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
LLM_API_KEY=sk-xxx
|
||||
LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
EOF
|
||||
```
|
||||
静态构建要求 Node.js 22.13 或更高版本,并让源码安装可以直接使用 Studio。
|
||||
|
||||
### 启动服务
|
||||
|
||||
|
|
@ -95,149 +100,269 @@ reme start service.port=8181
|
|||
# reme start workspace_dir=/tmp/reme-demo service.port=8181
|
||||
```
|
||||
|
||||
启动后可以检查服务状态;如果使用了自定义端口,请将下面 URL 中的 `2333` 替换为对应端口。
|
||||
|
||||
```bash
|
||||
reme version
|
||||
reme health_check
|
||||
reme help
|
||||
curl -s http://127.0.0.1:2333/version -H 'Content-Type: application/json' -d '{}'
|
||||
```
|
||||
|
||||
### 快速接入
|
||||
### 5 分钟记忆 Demo
|
||||
|
||||
ReMe 通过 **SKILL.md + CLI + hook(可选)** 接入支持的 Agent 框架。典型接入方式如下:
|
||||
服务运行后,可以写入一个记忆节点,让 ReMe 索引并检索它:
|
||||
|
||||
- 为 Agent 添加 [memory skill](skills/reme_memory/SKILL.md),并授予 Agent 调用 CLI 的权限。
|
||||
- 在 Agent hook 中按需调用 `auto_memory` 和 `proactive`,让对话自动沉淀为 daily 记忆,并在合适时机读取主动提醒。
|
||||
- `auto_index` 与 `auto_resource` 由文件监控自动触发,负责索引维护和资源加工。
|
||||
- `auto_dream` 由定时任务触发,将 daily 记忆进一步整理为可长期复用的 digest 记忆。
|
||||
```bash
|
||||
reme write \
|
||||
path=digest/wiki/quick-start-demo \
|
||||
name="Quick Start Demo" \
|
||||
description="第一个 ReMe 记忆节点" \
|
||||
content="# Quick Start Demo
|
||||
|
||||
QwenPaw 2.0 将集成新版 ReMe;后续也会推出 Claude Code plugin,进一步降低手动接入成本。
|
||||
ReMe 会把 Agent 记忆保存为可读的 Markdown。
|
||||
|
||||
更多细节见 [快速开始](docs/zh/quick_start.md)。
|
||||
相关链接:[[digest/wiki/memory-as-file.md]]"
|
||||
|
||||
## 📁 记忆系统
|
||||
reme search query="agent memory markdown" limit=5
|
||||
reme read path=digest/wiki/quick-start-demo start_line=1 end_line=20
|
||||
```
|
||||
|
||||
生成的文件是普通 Markdown,并带有 frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: Quick Start Demo
|
||||
description: 第一个 ReMe 记忆节点
|
||||
---
|
||||
|
||||
# Quick Start Demo
|
||||
|
||||
ReMe 会把 Agent 记忆保存为可读的 Markdown。
|
||||
|
||||
相关链接:[[digest/wiki/memory-as-file.md]]
|
||||
```
|
||||
|
||||
### ReMe Studio(可选)
|
||||
|
||||
上面的 `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)。
|
||||
|
||||
### 可选模型配置
|
||||
|
||||
如果需要 LLM 驱动的记忆演化或 embedding 检索,可以配置环境变量。embedding 默认关闭,因此默认配置不会启动 embedding 模型,也不需要
|
||||
embedding API key。
|
||||
|
||||
```bash
|
||||
cat > .env <<'EOF'
|
||||
# 可选:仅在配置中显式启用 embedding 组件后使用。
|
||||
# EMBEDDING_API_KEY=sk-xxx
|
||||
# EMBEDDING_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
|
||||
# 必须:auto_memory、auto_resource 和 auto_dream 需要 LLM。
|
||||
LLM_API_KEY=sk-xxx
|
||||
LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
EOF
|
||||
```
|
||||
|
||||
基础文件读写、BM25 检索、wikilink 遍历和 proactive topics 读取可以先不配置 LLM 凭证。
|
||||
|
||||
> [!NOTE]
|
||||
> 如需启用基于 embedding 的语义检索,请取消 [`reme/config/default.yaml`](reme/config/default.yaml) 中
|
||||
> `components.as_embedding` 和 `components.embedding_store` 的注释,并将
|
||||
> `components.file_store.default.embedding_store` 从 `""` 改为 `default`。完整说明见
|
||||
> [记忆检索文档](docs/zh/memory_search.md)。
|
||||
|
||||
## 🤝 将 ReMe 接入你的 Agent
|
||||
|
||||
ReMe 既可以作为本地记忆服务,通过 CLI、HTTP API 或 MCP server 接入,也可以通过 Python API 嵌入宿主进程。宿主集成可根据不同
|
||||
runtime 的能力,将记忆指引、召回和捕获接入 Agent 生命周期。
|
||||
|
||||
| Agent | 推荐接入方式 | 接入后能力 |
|
||||
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| **DeepSeek Harness** | 使用 `dsh plugin --profile web add @agentscope-ai/reme` 安装 [`@agentscope-ai/reme`](typescript/README_ZH.md#deepseek-harness)。 | 长期记忆指引、`reme_search` 工具,以及自动捕获已完成的主 Agent 对话。 |
|
||||
| **OpenClaw** | 使用 `openclaw plugins install @agentscope-ai/reme` 安装 [`@agentscope-ai/reme`](typescript/README_ZH.md#openclaw)。 | 原生记忆工具、用户触发运行前召回和自动对话捕获。 |
|
||||
| **QwenPaw** | 通过 Python API 在进程内嵌入 ReMe。 | 复用宿主生命周期和模型配置,同时保持记忆本地、文件化。 |
|
||||
| **Claude Code** | 启动 streamable HTTP MCP service,并安装 [ReMe 插件](integrations/claude_code/reme)。 | MCP 召回工具、`reme-memory` skill,以及自动记录会话的 Stop hook。 |
|
||||
| **Hermes** | 启动 HTTP service,并安装 [ReMe provider](integrations/hermes_agent)。 | 模型调用前召回,每轮对话完成后异步执行 `auto_memory`。 |
|
||||
| **Codex 及其他 CLI Agent** | 安装或复制 [ReMe Memory skill](skills/reme_memory/SKILL.md)。 | 通过 CLI 搜索、读取和写入记忆;自动捕获需要显式接入宿主生命周期。 |
|
||||
|
||||
<p align="center"><b>集成演示</b></p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"></td>
|
||||
<td width="45%" align="center"><b>Auto Memory</b></td>
|
||||
<td width="45%" align="center"><b>Auto Dream</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><b>QwenPaw</b></td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/qwenpaw-auto-memory.gif" alt="QwenPaw Auto Memory 演示" width="100%">
|
||||
</td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/qwenpaw-auto-dream.gif" alt="QwenPaw Auto Dream 演示" width="100%">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><b>Claude Code</b></td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/cc-auto-memory.gif" alt="Claude Code Auto Memory 演示" width="100%">
|
||||
</td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/cc-auto-dream.gif" alt="Claude Code Auto Dream 演示" width="100%">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## 🧠 ReMe 如何工作
|
||||
|
||||
> Memory as File, File as Memory.
|
||||
|
||||
ReMe 将**记忆视为文件**,让原始对话和外部资料从 `session/`、`resource/` 渐进加工到 `daily/`,再沉淀为 `digest/`
|
||||
中可长期复用的知识节点。
|
||||
ReMe 将 **记忆视为文件**,让过滤后的对话来源记录和外部资料从 `session/`、`resource/` 渐进加工到 `daily/`,再沉淀为
|
||||
`digest/`。默认 workspace 是当前目录下的 `.reme/`;可通过 `workspace_dir=...` 选择其他由用户控制的位置。
|
||||
|
||||
### 目录结构
|
||||
### Workspace 结构
|
||||
|
||||
```text
|
||||
<workspace_dir>/
|
||||
├── metadata/ # 系统索引、图谱、catalog 等持久状态
|
||||
├── session/ # 原始对话和 Agent session
|
||||
├── metadata/ # 可重建的索引、图谱、catalog 和缓存
|
||||
├── session/ # 对话来源记录和 Agent session
|
||||
│ ├── dialog/
|
||||
│ │ └── <session_id>.jsonl
|
||||
│ ├── agentscope/
|
||||
│ │ └── <session_id>.jsonl # auto_memory 保存的来源消息
|
||||
│ └── claude_code/
|
||||
│ └── <session_id>.jsonl # auto_memory_cc 使用的 ReMe 副本
|
||||
├── mem_session/ # Agent wrapper 生成的 session/配置,不是用户记忆
|
||||
│ ├── agentscope/
|
||||
│ ├── claude_config/
|
||||
│ └── codex/
|
||||
├── resource/ # 外部原始材料
|
||||
│ ├── <resource>.<ext> # 根目录文件进入当天 daily 层
|
||||
│ └── YYYY-MM-DD/
|
||||
│ └── <resource>.<ext>
|
||||
├── daily/ # 浅加工记忆:当天事实、对话摘要、资源解读
|
||||
│ ├── YYYY-MM-DD.md
|
||||
│ └── YYYY-MM-DD/
|
||||
│ ├── <session_id>.md
|
||||
│ ├── <resource_stem>.md
|
||||
│ ├── <generated_name>.md # 按主题命名的对话或资源卡片
|
||||
│ └── interests.yaml
|
||||
└── digest/ # 长期记忆:个人事实、流程经验、知识节点
|
||||
├── personal/
|
||||
│ └── {topic/event}.md
|
||||
├── procedure/
|
||||
│ └── {topic/event}.md
|
||||
└── wiki/
|
||||
└── {topic/event}.md
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/figure/reme-overview.svg" alt="ReMe 文件化记忆系统总览" width="92%">
|
||||
</p>
|
||||
|
||||
### 自动记忆流程
|
||||
### 记忆生命周期
|
||||
|
||||
ReMe 的自动记忆流程会把原始对话和资料逐步加工成可检索、可追溯、可长期复用的文件化记忆。常规运行时,后台监听负责维护索引和处理资源,Agent
|
||||
hook 负责触发对话记忆,长期整理与主动提醒则通过定时任务或按需调用完成。
|
||||
ReMe 遵循 capture → index → consolidate → recall 的循环。workspace 文件是持久化的事实来源,`metadata/` 中的内容均可重建。
|
||||
|
||||
<details>
|
||||
<summary><b>查看自动记忆能力表</b></summary>
|
||||
|
||||
<br>
|
||||
|
||||
| 能力 | 运行方式 | 作用 | 主要参数 |
|
||||
|---------------------------------------------|-------------------------------|---------------------------------------------------------------------------------------------------------|----------------------------------------------------|
|
||||
| [`auto_index`](docs/zh/memory_search.md) | 后台维护;对应 `index_update_loop` | 启动时扫描并持续监听 `daily/`、`digest/`、`resource/` 中的 Markdown/JSONL 变化,更新 chunk、BM25、embedding 与 wikilink 图谱索引。 | 配置项:`watch_dirs`、`watch_suffixes` |
|
||||
| [`auto_memory`](docs/zh/auto_memory.md) | Agent after-reply hook;也可按需调用 | 保存对话原文,并把有长期价值的信息整理成 `daily/<date>/<session_id>.md` 记忆卡片。 | 必填:`messages`;可选:`session_id`、`memory_hint` |
|
||||
| [`auto_resource`](docs/zh/auto_resource.md) | 资源监听自动触发;也可按需调用 | 解读 `resource/<date>/` 下的资源变更,生成或更新由 LLM 命名、通过 `source_resource` 关联的 daily 资源卡片。 | 必填:`changes`;每项可含 `path`、`file_path`、`change` |
|
||||
| [`auto_dream`](docs/zh/auto_dream.md) | 定时任务 `dream_cron`;也可按需调用 | 扫描指定日期的 daily 输入,抽取长期记忆单元并整合进 `digest/`,同时写入 `daily/<date>/interests.yaml`。 | `date`、`hint`、`topic_count`、`topic_diversity_days` |
|
||||
| [`proactive`](docs/zh/proactive.md) | Agent 主动提醒前按需读取 | 读取 `auto_dream` 生成的 `interests.yaml`,将当天值得关注的主题暴露给上层 Agent;是否提醒用户由调用方决定。 | `date`、`include_content` |
|
||||
|
||||
</details>
|
||||
| 能力 | 入口 | 作用 | 输出 |
|
||||
| ------------------------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
|
||||
| [`auto_memory`](docs/zh/auto_memory.md) | Agent hook 或 `reme auto_memory` | 提炼有长期价值的对话事实,同时保留过滤后的对话来源记录。 | `session/dialog/*.jsonl`、`daily/<date>/<generated-name>.md` |
|
||||
| [`auto_resource`](docs/zh/auto_resource.md) | 资源监听或 `reme auto_resource` | 将 `resource/` 下的文件转为带来源链接、按内容命名的 daily 卡片。 | `daily/<date>/<resource-card>.md` |
|
||||
| [`auto_index`](docs/zh/memory_search.md) | 后台监听或 `reme reindex` | 实时索引 `daily/` 和 `digest/` 中的 Markdown;全量重建还会扫描 `resource/` 和 JSONL。 | 可检索的 chunks、BM25、wikilink 图谱和可选向量 |
|
||||
| [`auto_dream`](docs/zh/auto_dream.md) | `dream_cron` 或 `reme auto_dream` | 默认从最近两天内变化的文件中最多提取 5 个可复用 unit,再创建、印证、补充或修正 digest 节点。 | `digest/**`、`daily/<date>/interests.yaml` |
|
||||
| [`proactive`](docs/zh/proactive.md) | Agent 决定主动行动前调用 `reme proactive` | 读取 `auto_dream` 生成的 topics;是否以及如何提醒用户由宿主 Agent 决定。 | 来自 `daily/<date>/interests.yaml` 的结构化 topics |
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="50%">
|
||||
<img src="docs/figure/memory-as-file.svg" alt="Memory as File" width="100%">
|
||||
<img src="docs/figure/memory-as-file.svg" alt="Memory as File" width="92%">
|
||||
</td>
|
||||
<td align="center" width="50%">
|
||||
<img src="docs/figure/auto-memory-resource.svg" alt="Auto Memory and Resource" width="100%">
|
||||
<img src="docs/figure/auto-memory-resource.svg" alt="Auto Memory and Resource" width="92%">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" width="50%">
|
||||
<img src="docs/figure/auto-dream-and-proactive.svg" alt="Auto Dream and Proactive" width="100%">
|
||||
<img src="docs/figure/auto-dream-and-proactive.svg" alt="Auto Dream and Proactive" width="92%">
|
||||
</td>
|
||||
<td align="center" width="50%">
|
||||
<img src="docs/figure/auto-index-and-memory-search.svg" alt="Auto Index and Memory Search" width="100%">
|
||||
<img src="docs/figure/auto-index-and-memory-search.svg" alt="Auto Index and Memory Search" width="92%">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### Workspace 操作接口
|
||||
搜索返回带行号范围的相关 chunks 和数量受限的 wikilink 邻居;可选向量结果通过 RRF 与 BM25 融合。
|
||||
|
||||
ReMe 通过统一的 CLI / Service Job 接口操作 workspace。Agent 通常只需要使用检索、读取、写入、编辑和自动记忆相关命令;更底层的索引、frontmatter
|
||||
和文件操作接口主要用于维护、调试或高级集成。
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> `proactive` 只读取并暴露 Auto Dream 生成的兴趣主题,不会自行联网、发送通知或改写知识库;是否以及如何使用主题,由宿主 Agent
|
||||
> 决定。
|
||||
|
||||
<details>
|
||||
<summary><b>查看 Workspace 操作接口表</b></summary>
|
||||
## 📊 评测结果
|
||||
|
||||
<br>
|
||||
ReMe 通过 Agent 多轮搜索与读取的方式,评测多会话和超长上下文中的记忆能力。下表为仓库中已公开的参考实验结果;模型、prompt、数据集和评判细节见各评测文档。
|
||||
|
||||
| 分类 | name | 描述 | 参数 |
|
||||
|-------|--------------------------------------|---------------------------------------------------------------------------|--------------------------------------------------------|
|
||||
| 系统状态 | `version` | 返回 ReMe 包版本。 | 无 |
|
||||
| 系统状态 | `health_check` | 返回 ReMe 组件健康检查摘要。 | 无 |
|
||||
| 系统状态 | `help` | 列出已注册 jobs 及其 metadata。 | 无 |
|
||||
| 检索读取 | [`search`](docs/zh/memory_search.md) | 在 workspace 中执行混合检索,结合向量召回、BM25 和 RRF 融合。 | 必填:`query`;可选:`limit`、`min_score` |
|
||||
| 检索读取 | `node_search` | 根据候选抽象的名称与描述召回相似 digest 节点,主要用于 `auto_dream` 去重或关联。 | 必填:`query`;可选:`limit` |
|
||||
| 检索读取 | `traverse` | 从指定路径出发遍历 wikilink 图谱。 | 必填:`path`;可选:`depth`、`direction` |
|
||||
| 检索读取 | `read` | 读取 workspace 下的 Markdown 文件。 | 必填:`path`;可选:`start_line`、`end_line` |
|
||||
| 检索读取 | `read_image` | 读取 workspace 下的图片文件并返回 base64。 | 必填:`path` |
|
||||
| 索引维护 | `reindex` | 清空文件存储索引,并基于现有文件重建索引。 | 配置项:`watch_dirs`、`watch_suffixes` |
|
||||
| Daily | `daily_list` | 列出某一天的 notes。 | `date` |
|
||||
| Daily | `daily_reindex` | 重建 day-index 页面 `daily/<date>.md`。 | `date` |
|
||||
| 元数据 | `frontmatter_read` | 读取文件 frontmatter。 | 必填:`path` |
|
||||
| 元数据 | `frontmatter_update` | 合并 key-values 到文件 frontmatter。 | 必填:`path`、`metadata` |
|
||||
| 元数据 | `frontmatter_delete` | 删除文件 frontmatter 中的指定 keys。 | 必填:`path`、`keys` |
|
||||
| 文件操作 | `stat` | 获取 workspace 路径状态,包括大小、mtime、是否存在、是否目录或文件。 | 必填:`path` |
|
||||
| 文件操作 | `list` | 列出 workspace 路径下的文件。 | `path`、`recursive`、`limit` |
|
||||
| 文件操作 | `write` | 创建或覆盖 Markdown 文件,并写入 name/description frontmatter。 | 必填:`path`、`name`、`description`、`content`;可选:`metadata` |
|
||||
| 文件操作 | `edit` | 对 Markdown 文件执行全文 find-and-replace。 | 必填:`path`、`old`、`new` |
|
||||
| 文件操作 | `move` | 移动或重命名 workspace 文件,并默认重写入站 wikilink。 | 必填:`src_path`、`dst_path`;可选:`overwrite`、`retarget` |
|
||||
| 文件操作 | `delete` | 删除 workspace 文件或文件夹,并返回仍存在的入站 wikilink。 | 必填:`path` |
|
||||
| 基准 | 设置 | 样本量 | 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% | 超长对话设置 |
|
||||
|
||||
</details>
|
||||
在仓库的 [π-Bench 评测](https://reme.agentscope.io/?doc=pibench-zh)中,ReMe Agent 在 5 种用户角色上的平均 **PROC 得分为 0.580**
|
||||
,比相同测试模型配置的 NanoBot 高 2.4%。PROC 用于评估隐藏意图完成、针对性澄清、跨会话偏好和规范复用、跨任务依赖推断以及欠规格请求推进等主动性能力。
|
||||
|
||||
## 🤝 社区与支持
|
||||
## 🧩 扩展与插件
|
||||
|
||||
- **问题反馈与需求**:请先查看 [Open Issues](https://github.com/agentscope-ai/ReMe/issues);如无相关讨论,可新建 Issue
|
||||
插件是可选的独立 Python distribution,可以贡献 Component、Step、Job backend 和配置,并通过配置显式启用。每日论文与 Auto Fin
|
||||
均已独立打包,源码 distribution 及说明分别见[每日论文](plugins/daily_paper/README_ZH.md)和
|
||||
[Auto Fin](plugins/auto-fin/README_ZH.md)。
|
||||
|
||||
| 插件 | 能力 |
|
||||
| ---------------------------------------------------------- | ------------------------------------------------------------------------------ |
|
||||
| [每日论文](https://reme.agentscope.io/?doc=daily-paper-zh) | 发现并排序论文,使用 Agent 解读 PDF,生成文件化论文笔记和五分钟简报。 |
|
||||
| [Auto Fin](https://reme.agentscope.io/?doc=auto-fin-zh) | 拉取主题相关财联社新闻,搜索 ReMe 历史材料并生成带 wikilink 的 Markdown 报告。 |
|
||||
|
||||
安装、查看、校验、启用和卸载 ReMe 插件的方法见[插件管理](docs/zh/plugin_management.md)。
|
||||
|
||||
## 📚 文档
|
||||
|
||||
下列文档覆盖主要使用流程,并以当前代码的运行时契约为准。
|
||||
|
||||
| 文档 | 主要内容 |
|
||||
| ------------------------------------------------------------------------ | ---------------------------------------------------------------------- |
|
||||
| [快速开始](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 卡片。 |
|
||||
| [Auto Dream](docs/zh/auto_dream.md) 与 [Auto Link](docs/zh/auto_link.md) | 将 daily 记忆整理为持续演化的 digest 节点和可读 wikilink 关系。 |
|
||||
| [记忆检索](docs/zh/memory_search.md) | 使用 BM25、可选向量、RRF 融合、行号范围召回和渐进式链接扩展。 |
|
||||
| [Proactive](docs/zh/proactive.md) | 安全读取兴趣主题,并将其接入宿主 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) | 了解完整产品故事、设计动机、使用示例和评测摘要。 |
|
||||
|
||||
## 🛠️ 常用命令
|
||||
|
||||
运行 `reme help` 可查看完整 job 列表。常用 workspace 与维护命令如下:
|
||||
|
||||
| 命令 | 作用 |
|
||||
| ----------------------------------------- | ------------------------------------------------------------- |
|
||||
| `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 reindex` | 基于已有文件重建检索和 wikilink 索引。 |
|
||||
|
||||
## 🤝 社区与贡献
|
||||
|
||||
- **问题反馈、需求与帮助**:请先查看 [Open Issues](https://github.com/agentscope-ai/ReMe/issues);如无相关讨论,可新建 Issue
|
||||
说明背景、目标行为和影响范围。
|
||||
- **代码贡献**:改动前建议阅读 [贡献指南](docs/zh/contributing.md) 和 [代码框架](docs/zh/framework.md),遵循 CLI /
|
||||
Service / Application / Job / Step / Component 的分层。
|
||||
- **文档贡献**:用户可见的安装、配置、调用或行为变化,请同步更新 `docs/zh/` 或 `README.md`。
|
||||
- **代码贡献**:改动前建议阅读仓库内的[贡献指南](docs/zh/contributing.md)。架构与扩展方式以源码、schema 和测试为准。
|
||||
- **文档贡献**:请直接更新本仓库 `docs/en/`、`docs/zh/` 或对应 package 目录中的规范源文件;文档站点会从这些文件生成。
|
||||
- **提交规范**:建议使用 Conventional Commits,例如 `feat(search): add link expansion option`、
|
||||
`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)。
|
||||
|
||||
### 贡献者
|
||||
|
||||
|
|
@ -250,8 +375,8 @@ ReMe 通过统一的 CLI / Service Job 接口操作 workspace。Agent 通常只
|
|||
## 📄 引用
|
||||
|
||||
```bibtex
|
||||
@software{AgentscopeReMe2026,
|
||||
title = {AgentscopeReMe: Memory Management Kit for Agents},
|
||||
@software{ReMe2026,
|
||||
title = {Remember me, Refine me: Memory Management Kit for Agents},
|
||||
author = {ReMe Team},
|
||||
url = {https://reme.agentscope.io},
|
||||
year = {2026}
|
||||
|
|
@ -261,7 +386,3 @@ ReMe 通过统一的 CLI / Service Job 接口操作 workspace。Agent 通常只
|
|||
## ⚖️ 许可证
|
||||
|
||||
本项目基于 Apache License 2.0 开源,详情参见 [LICENSE](./LICENSE) 文件。
|
||||
|
||||
## 📈 Star 历史
|
||||
|
||||
[](https://www.star-history.com/#agentscope-ai/ReMe&Date)
|
||||
|
|
|
|||
124
benchmark/beam/README.md
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
[中文版 / Chinese version](./README_ZH.md)
|
||||
|
||||
# BEAM Benchmark
|
||||
|
||||
BEAM is a benchmark for **memory capability over long-context chat cases**. Each
|
||||
case contains a very long chat history split into batches; ReMe converts each
|
||||
batch into a session, ingests them in chronological order, then answers probing
|
||||
questions via an agentic (ReAct) mode. Answers are scored with BEAM's
|
||||
rubric-based `answer_judge` job, which produces both a graded score and a binary
|
||||
verdict, and per-type averages are reported.
|
||||
|
||||
BEAM ships dataset variants by chat size — `100K` / `500K` / `1M` / `10M` — so
|
||||
memory systems can be stressed at different context lengths. Question types
|
||||
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).
|
||||
|
||||
## 1. Get the Dataset
|
||||
|
||||
BEAM is a public repository, cloned into `benchmark/beam/dataset/`:
|
||||
|
||||
```bash
|
||||
mkdir -p benchmark/beam/dataset
|
||||
cd benchmark/beam/dataset
|
||||
git clone https://github.com/mohammadtavakoli78/BEAM.git
|
||||
```
|
||||
|
||||
After cloning, `benchmark/beam/dataset/BEAM/` should contain `chats/`, `src/`,
|
||||
`topics/` and other subdirectories.
|
||||
|
||||
## 2. Run
|
||||
|
||||
From the repository root:
|
||||
|
||||
```bash
|
||||
python benchmark/beam/run.py
|
||||
python benchmark/beam/run.py --config benchmark/beam/config.yaml
|
||||
python benchmark/beam/run.py -q # quiet
|
||||
python benchmark/beam/run.py --eval_only # reuse existing workspaces, query + judge only
|
||||
```
|
||||
|
||||
## 3. Pipeline
|
||||
|
||||
1. For each case, load `chat.json` and convert each batch into a ReMe session.
|
||||
2. Ingest sessions in chronological order into an isolated workspace, then `digest_update`.
|
||||
3. Answer each probing question via agentic (ReAct) mode.
|
||||
4. Score answers with BEAM's rubric-based `answer_judge` job and print per-type averages.
|
||||
|
||||
## 4. Key config — `benchmark/beam/config.yaml`
|
||||
|
||||
| Key | Meaning |
|
||||
| --- | --- |
|
||||
| `dataset.beam_root` | BEAM dataset root (`benchmark/beam/dataset/BEAM`). |
|
||||
| `dataset.chat_size` | Variant to run: `100K` / `500K` / `1M` / `10M`. |
|
||||
| `dataset.case_ids` | Specific cases (e.g. `["1","2"]`); empty = all cases. |
|
||||
| `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`). |
|
||||
| `output.dir` | Results directory (`benchmark/beam/results`). |
|
||||
|
||||
## 5. Outputs
|
||||
|
||||
Results are JSON files written to `output.dir` as
|
||||
`results_<chat_size>_<timestamp>.json`, with a per-type score summary also
|
||||
printed to the console. Logging conventions are shared across benchmarks — see
|
||||
the [top-level README](../README.md#outputs--logs).
|
||||
|
||||
## 6. Reference Results
|
||||
|
||||
> The results below use the longmemeval-version prompt.
|
||||
|
||||
### 100K
|
||||
|
||||
agentscope==2.0.4.post1, conda reme env, 20 workers, eval-only (reusing prebuilt memory)
|
||||
(2026-08-05, 20 cases / 400 Qs, total 46.0 min)
|
||||
|
||||
| Type | Agentic | Binary | input tok/q | output tok/q | total tok/q | tool calls/q |
|
||||
|---|---|---|---|---|---|---|
|
||||
| abstention | 0.550 | 0.550 | 96,031 | 1,070 | 97,101 | 4.58 |
|
||||
| contradiction_resolution | 0.438 | 0.412 | 32,263 | 872 | 33,135 | 2.48 |
|
||||
| event_ordering | 0.501 | 0.423 | 140,195 | 5,163 | 145,358 | 4.70 |
|
||||
| information_extraction | 0.873 | 0.832 | 50,245 | 883 | 51,128 | 3.15 |
|
||||
| instruction_following | 0.750 | 0.725 | 37,986 | 848 | 38,834 | 2.67 |
|
||||
| knowledge_update | 0.688 | 0.675 | 31,198 | 651 | 31,849 | 2.27 |
|
||||
| multi_session_reasoning | 0.626 | 0.584 | 85,038 | 4,563 | 89,601 | 4.28 |
|
||||
| preference_following | 0.925 | 0.912 | 34,281 | 989 | 35,270 | 2.50 |
|
||||
| summarization | 0.623 | 0.461 | 89,657 | 2,056 | 91,713 | 4.12 |
|
||||
| temporal_reasoning | 0.637 | 0.625 | 34,563 | 1,049 | 35,612 | 2.52 |
|
||||
| **OVERALL** | **0.661** | **0.620** | **63,146** | **1,814** | **64,960** | **3.33** |
|
||||
|
||||
Memory Construction average token consumption (default agent, full build over 20 cases):
|
||||
|
||||
| Agent | input tok/case | output tok/case | total tok/case |
|
||||
|---|---|---|---|
|
||||
| default | 2,172,316 | 136,697 | 2,309,013 |
|
||||
|
||||
### 1M
|
||||
|
||||
agentscope==2.0.4.post1, conda reme env, 20 workers, full memory build
|
||||
(2026-08-05, 35 cases / 700 Qs, total 459.2 min)
|
||||
|
||||
| Type | Agentic | Binary | input tok/q | output tok/q | total tok/q | tool calls/q |
|
||||
|---|---|---|---|---|---|---|
|
||||
| abstention | 0.429 | 0.429 | 118,707 | 1,178 | 119,886 | 4.20 |
|
||||
| contradiction_resolution | 0.391 | 0.364 | 49,787 | 810 | 50,597 | 2.50 |
|
||||
| event_ordering | 0.558 | 0.456 | 201,514 | 3,889 | 205,403 | 4.79 |
|
||||
| information_extraction | 0.809 | 0.772 | 78,950 | 894 | 79,844 | 3.00 |
|
||||
| instruction_following | 0.852 | 0.832 | 55,757 | 924 | 56,681 | 2.81 |
|
||||
| knowledge_update | 0.779 | 0.771 | 45,981 | 665 | 46,646 | 2.37 |
|
||||
| multi_session_reasoning | 0.658 | 0.612 | 138,133 | 2,873 | 141,006 | 4.40 |
|
||||
| preference_following | 0.798 | 0.777 | 51,796 | 920 | 52,716 | 2.53 |
|
||||
| summarization | 0.693 | 0.537 | 158,794 | 2,905 | 161,700 | 4.44 |
|
||||
| temporal_reasoning | 0.536 | 0.536 | 100,176 | 3,148 | 103,324 | 3.90 |
|
||||
| **OVERALL** | **0.650** | **0.609** | **99,959** | **1,821** | **101,780** | **3.49** |
|
||||
|
||||
Memory Construction average token consumption (default agent, full build over 35 cases):
|
||||
|
||||
| Agent | input tok/case | output tok/case | total tok/case |
|
||||
|---|---|---|---|
|
||||
| default | 31,943,817 | 1,417,061 | 33,360,878 |
|
||||
119
benchmark/beam/README_ZH.md
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# BEAM 评测
|
||||
|
||||
[English version](./README.md)
|
||||
|
||||
BEAM 是一个面向**长上下文对话场景**的记忆能力评测基准。每个 case 包含一段被切分为多个
|
||||
batch 的超长对话;ReMe 将每个 batch 转换为一个会话,按时间顺序摄入后,以 agentic(ReAct)
|
||||
模式回答探测问题。答案由 BEAM 基于 rubric 的 `answer_judge` 任务打分,同时给出分级分数与二元
|
||||
判定,并输出各类型平均分。
|
||||
|
||||
BEAM 按对话规模提供多种数据变体 —— `100K` / `500K` / `1M` / `10M`,可在不同上下文长度下
|
||||
压测记忆系统。题型包括 abstention(拒答)、contradiction resolution(矛盾消解)、event
|
||||
ordering(事件排序)、information extraction(信息抽取)、instruction following(指令遵循)、
|
||||
knowledge update(知识更新)、multi-session reasoning(多会话推理)、preference following
|
||||
(偏好遵循)、summarization(摘要)与 temporal reasoning(时间推理)。
|
||||
|
||||
> 公共设置(依赖、凭据、日志约定)见[总评测说明](../README_ZH.md)。
|
||||
|
||||
## 1. 获取数据集
|
||||
|
||||
BEAM 是公开仓库,clone 到 `benchmark/beam/dataset/` 下:
|
||||
|
||||
```bash
|
||||
mkdir -p benchmark/beam/dataset
|
||||
cd benchmark/beam/dataset
|
||||
git clone https://github.com/mohammadtavakoli78/BEAM.git
|
||||
```
|
||||
|
||||
clone 完成后,`benchmark/beam/dataset/BEAM/` 目录下应包含 `chats/`、`src/`、`topics/` 等子目录。
|
||||
|
||||
## 2. 运行
|
||||
|
||||
在仓库根目录执行:
|
||||
|
||||
```bash
|
||||
python benchmark/beam/run.py
|
||||
python benchmark/beam/run.py --config benchmark/beam/config.yaml
|
||||
python benchmark/beam/run.py -q # 安静模式
|
||||
python benchmark/beam/run.py --eval_only # 复用已有工作区,仅执行查询 + 评判
|
||||
```
|
||||
|
||||
## 3. 流程
|
||||
|
||||
1. 为每个 case 加载 `chat.json`,将每个 batch 转换为一个 ReMe 会话。
|
||||
2. 按时间顺序将会话摄入独立工作区,随后执行 `digest_update`。
|
||||
3. 以 agentic(ReAct)模式回答每个探测问题。
|
||||
4. 通过 BEAM 基于 rubric 的 `answer_judge` 任务打分,并输出各类型平均分。
|
||||
|
||||
## 4. 关键配置 —— `benchmark/beam/config.yaml`
|
||||
|
||||
| 配置项 | 含义 |
|
||||
| --- | --- |
|
||||
| `dataset.beam_root` | BEAM 数据集根目录(`benchmark/beam/dataset/BEAM`)。 |
|
||||
| `dataset.chat_size` | 运行的变体:`100K` / `500K` / `1M` / `10M`。 |
|
||||
| `dataset.case_ids` | 指定 case(如 `["1","2"]`),空表示全部。 |
|
||||
| `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`)。 |
|
||||
| `output.dir` | 结果目录(`benchmark/beam/results`)。 |
|
||||
|
||||
## 5. 输出
|
||||
|
||||
结果以 JSON 文件写入 `output.dir`,文件名为 `results_<chat_size>_<timestamp>.json`,
|
||||
同时控制台会打印含各类型分数的汇总。日志约定在各基准间通用,见
|
||||
[总说明](../README_ZH.md#输出与日志)。
|
||||
|
||||
## 6. 参考结果
|
||||
|
||||
> 以下结果使用 longmemeval 版本的 prompt。
|
||||
|
||||
### 100K
|
||||
|
||||
agentscope==2.0.4.post1,conda reme 环境,20 并发,eval-only(复用已构建 memory)
|
||||
(2026-08-05,20 cases / 400 Qs,总耗时 46.0 min)
|
||||
|
||||
| 题型 | Agentic | Binary | input tok/q | output tok/q | total tok/q | tool calls/q |
|
||||
|---|---|---|---|---|---|---|
|
||||
| abstention | 0.550 | 0.550 | 96,031 | 1,070 | 97,101 | 4.58 |
|
||||
| contradiction_resolution | 0.438 | 0.412 | 32,263 | 872 | 33,135 | 2.48 |
|
||||
| event_ordering | 0.501 | 0.423 | 140,195 | 5,163 | 145,358 | 4.70 |
|
||||
| information_extraction | 0.873 | 0.832 | 50,245 | 883 | 51,128 | 3.15 |
|
||||
| instruction_following | 0.750 | 0.725 | 37,986 | 848 | 38,834 | 2.67 |
|
||||
| knowledge_update | 0.688 | 0.675 | 31,198 | 651 | 31,849 | 2.27 |
|
||||
| multi_session_reasoning | 0.626 | 0.584 | 85,038 | 4,563 | 89,601 | 4.28 |
|
||||
| preference_following | 0.925 | 0.912 | 34,281 | 989 | 35,270 | 2.50 |
|
||||
| summarization | 0.623 | 0.461 | 89,657 | 2,056 | 91,713 | 4.12 |
|
||||
| temporal_reasoning | 0.637 | 0.625 | 34,563 | 1,049 | 35,612 | 2.52 |
|
||||
| **OVERALL** | **0.661** | **0.620** | **63,146** | **1,814** | **64,960** | **3.33** |
|
||||
|
||||
Memory Construction 平均 token 消耗(default agent,20 cases 全量构建):
|
||||
|
||||
| Agent | input tok/case | output tok/case | total tok/case |
|
||||
|---|---|---|---|
|
||||
| default | 2,172,316 | 136,697 | 2,309,013 |
|
||||
|
||||
### 1M
|
||||
|
||||
agentscope==2.0.4.post1,conda reme 环境,20 并发,全量构建 memory
|
||||
(2026-08-05,35 cases / 700 Qs,总耗时 459.2 min)
|
||||
|
||||
| 题型 | Agentic | Binary | input tok/q | output tok/q | total tok/q | tool calls/q |
|
||||
|---|---|---|---|---|---|---|
|
||||
| abstention | 0.429 | 0.429 | 118,707 | 1,178 | 119,886 | 4.20 |
|
||||
| contradiction_resolution | 0.391 | 0.364 | 49,787 | 810 | 50,597 | 2.50 |
|
||||
| event_ordering | 0.558 | 0.456 | 201,514 | 3,889 | 205,403 | 4.79 |
|
||||
| information_extraction | 0.809 | 0.772 | 78,950 | 894 | 79,844 | 3.00 |
|
||||
| instruction_following | 0.852 | 0.832 | 55,757 | 924 | 56,681 | 2.81 |
|
||||
| knowledge_update | 0.779 | 0.771 | 45,981 | 665 | 46,646 | 2.37 |
|
||||
| multi_session_reasoning | 0.658 | 0.612 | 138,133 | 2,873 | 141,006 | 4.40 |
|
||||
| preference_following | 0.798 | 0.777 | 51,796 | 920 | 52,716 | 2.53 |
|
||||
| summarization | 0.693 | 0.537 | 158,794 | 2,905 | 161,700 | 4.44 |
|
||||
| temporal_reasoning | 0.536 | 0.536 | 100,176 | 3,148 | 103,324 | 3.90 |
|
||||
| **OVERALL** | **0.650** | **0.609** | **99,959** | **1,821** | **101,780** | **3.49** |
|
||||
|
||||
Memory Construction 平均 token 消耗(default agent,35 cases 全量构建):
|
||||
|
||||
| Agent | input tok/case | output tok/case | total tok/case |
|
||||
|---|---|---|---|
|
||||
| default | 31,943,817 | 1,417,061 | 33,360,878 |
|
||||
24
benchmark/beam/config.yaml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# BEAM evaluation configuration
|
||||
# This file controls what/how to evaluate.
|
||||
|
||||
dataset:
|
||||
beam_root: "benchmark/beam/dataset/BEAM" # BEAM dataset root
|
||||
chat_size: "1M" # 100K | 500K | 1M | 10M (dataset variant)
|
||||
case_ids: [] # empty = all cases; or ["1", "2", "3"]
|
||||
start_index: 0 # first case index (for pagination)
|
||||
num_items: 0 # 0 = all cases; >0 = limit
|
||||
workspace_root: "benchmark/beam/workspaces/beam" # workspace root for case workspaces
|
||||
|
||||
evaluation:
|
||||
num_workers: 20 # 0 = auto; 1 = sequential; >1 = parallel (per-case)
|
||||
compress_session: false # true = compress session chunks in search_v2 (query-aware); false = no compression
|
||||
|
||||
reme:
|
||||
config: "beam.yaml" # reme config (in reme/config/)
|
||||
|
||||
output:
|
||||
dir: "benchmark/beam/results"
|
||||
log_dir: "logs" # log directory (relative to project root)
|
||||
log_prefix: "beam" # benchmark name used in log filenames
|
||||
log_to_console: true
|
||||
log_to_file: true
|
||||
76
benchmark/beam/kill.sh
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#!/bin/bash
|
||||
# 杀死指定进程及其所有子进程
|
||||
# Usage: bash kill.sh <PID>
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: bash kill.sh <PID>"
|
||||
echo " 杀死指定进程及其所有子进程"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PID=$1
|
||||
|
||||
# 检查进程是否存在
|
||||
if ! kill -0 "$PID" 2>/dev/null; then
|
||||
echo "进程 $PID 不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 递归收集所有子进程(包括子进程的子进程)
|
||||
collect_children() {
|
||||
local parent=$1
|
||||
local children
|
||||
children=$(ps -o pid= --ppid "$parent" 2>/dev/null | tr -d ' ')
|
||||
for child in $children; do
|
||||
collect_children "$child"
|
||||
done
|
||||
echo "$parent"
|
||||
}
|
||||
|
||||
# 收集进程树(子进程在前,父进程在后,保证先杀子再杀父)
|
||||
PROCESS_TREE=$(collect_children "$PID")
|
||||
TOTAL=$(echo "$PROCESS_TREE" | wc -l | tr -d ' ')
|
||||
|
||||
echo "进程树(共 $TOTAL 个进程):"
|
||||
while read -r p; do
|
||||
cmd=$(ps -o args= -p "$p" 2>/dev/null | head -c 80)
|
||||
printf " PID=%-8s %s\n" "$p" "$cmd"
|
||||
done <<< "$PROCESS_TREE"
|
||||
|
||||
# 先 SIGTERM 优雅终止
|
||||
echo ""
|
||||
echo "发送 SIGTERM..."
|
||||
while read -r p; do
|
||||
kill "$p" 2>/dev/null
|
||||
done <<< "$PROCESS_TREE"
|
||||
|
||||
# 等待最多 5 秒
|
||||
for i in $(seq 1 5); do
|
||||
alive=false
|
||||
while read -r p; do
|
||||
if kill -0 "$p" 2>/dev/null; then
|
||||
alive=true
|
||||
fi
|
||||
done <<< "$PROCESS_TREE"
|
||||
if [ "$alive" = false ]; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# 检查是否还有残留,强制 SIGKILL
|
||||
remaining=false
|
||||
while read -r p; do
|
||||
if kill -0 "$p" 2>/dev/null; then
|
||||
remaining=true
|
||||
fi
|
||||
done <<< "$PROCESS_TREE"
|
||||
|
||||
if [ "$remaining" = true ]; then
|
||||
echo "部分进程未响应,发送 SIGKILL..."
|
||||
while read -r p; do
|
||||
kill -9 "$p" 2>/dev/null
|
||||
done <<< "$PROCESS_TREE"
|
||||
fi
|
||||
|
||||
echo "已终止进程树(根 PID=$PID,共 $TOTAL 个进程)"
|
||||
891
benchmark/beam/run.py
Normal file
|
|
@ -0,0 +1,891 @@
|
|||
"""BEAM evaluation runner for ReMe.
|
||||
|
||||
Evaluates ReMe's memory capability using the BEAM dataset.
|
||||
Each case gets an isolated workspace; chat.json batches are ingested as
|
||||
sessions in chronological order; finally probing questions are answered
|
||||
via an agentic (ReAct) approach, then
|
||||
judged by BEAM's rubric-based LLM-as-judge.
|
||||
|
||||
Usage:
|
||||
python benchmark/beam/run.py
|
||||
python benchmark/beam/run.py --config benchmark/beam/config.yaml
|
||||
python benchmark/beam/run.py -q # quiet: only eval-level logs
|
||||
python benchmark/beam/run.py --log-level WARNING # reduce eval runner logs
|
||||
python benchmark/beam/run.py --reme-log-level WARNING # reduce reme internal logs
|
||||
python benchmark/beam/run.py --eval_only # query+judge only, reuse existing workspace
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import time
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load .env from project root
|
||||
_PROJECT_ROOT = Path(__file__).parent.parent.parent
|
||||
load_dotenv(_PROJECT_ROOT / ".env")
|
||||
|
||||
# Workspace root — read from config.yaml (dataset.workspace_root)
|
||||
_WORKSPACE_ROOT_DEFAULT = "benchmark/beam/workspaces/beam"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Logging
|
||||
# ---------------------------------------------------------------------------
|
||||
_DEFAULT_LOG_FORMAT = "%(asctime)s | %(levelname)s | %(message)s"
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format=_DEFAULT_LOG_FORMAT)
|
||||
logger = logging.getLogger("beam")
|
||||
|
||||
# Noisy library loggers silenced by default
|
||||
_NOISY_LOGGERS = [
|
||||
"httpx",
|
||||
"httpcore",
|
||||
"openai",
|
||||
"uvicorn",
|
||||
"multipart",
|
||||
"asyncio",
|
||||
"watchfiles",
|
||||
"filelock",
|
||||
]
|
||||
|
||||
|
||||
def setup_logging(
|
||||
log_level: str,
|
||||
reme_log_level: str,
|
||||
log_dir: str | None = None,
|
||||
):
|
||||
"""Configure logging for the eval runner and reme internals.
|
||||
|
||||
Args:
|
||||
log_level: Level for the eval runner logger (DEBUG/INFO/WARNING/ERROR).
|
||||
reme_log_level: Level for reme's internal loguru logger.
|
||||
log_dir: Per-run log directory (absolute path). None = no file logging.
|
||||
"""
|
||||
numeric = getattr(logging, log_level.upper(), logging.INFO)
|
||||
# Eval runner logger
|
||||
logging.getLogger().setLevel(numeric)
|
||||
logger.setLevel(numeric)
|
||||
|
||||
# Suppress noisy library loggers when above DEBUG
|
||||
if numeric > logging.DEBUG:
|
||||
for name in _NOISY_LOGGERS:
|
||||
lib_logger = logging.getLogger(name)
|
||||
lib_logger.setLevel(max(numeric, logging.WARNING))
|
||||
|
||||
# Add file handler for eval runner if log_dir is specified
|
||||
if log_dir:
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
log_filepath = os.path.join(log_dir, "runner.log")
|
||||
file_handler = logging.FileHandler(log_filepath, encoding="utf-8")
|
||||
file_handler.setLevel(numeric)
|
||||
file_handler.setFormatter(logging.Formatter(_DEFAULT_LOG_FORMAT))
|
||||
logging.getLogger().addHandler(file_handler)
|
||||
logger.info(f"Eval runner log file: {log_filepath}")
|
||||
|
||||
# Reme internal logger (loguru) — will be applied per-worker via _configure_worker
|
||||
os.environ["REME_LOG_LEVEL"] = reme_log_level.upper()
|
||||
if log_dir:
|
||||
os.environ["REME_LOG_DIR"] = log_dir
|
||||
|
||||
|
||||
def _configure_worker(
|
||||
log_level: str,
|
||||
reme_log_level: str,
|
||||
log_dir: str | None = None,
|
||||
):
|
||||
"""Set up logging inside a multiprocessing worker process.
|
||||
|
||||
Must be called at the top of each worker because child processes inherit
|
||||
parent state but loguru sinks are NOT shared across fork/spawn.
|
||||
"""
|
||||
numeric = getattr(logging, log_level.upper(), logging.INFO)
|
||||
logging.basicConfig(level=numeric, format=_DEFAULT_LOG_FORMAT, force=True)
|
||||
logging.getLogger("beam").setLevel(numeric)
|
||||
if numeric > logging.DEBUG:
|
||||
for name in _NOISY_LOGGERS:
|
||||
logging.getLogger(name).setLevel(max(numeric, logging.WARNING))
|
||||
|
||||
# Add file handler for eval runner in worker process
|
||||
if log_dir:
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
pid = os.getpid()
|
||||
log_filepath = os.path.join(log_dir, f"worker-{pid}.log")
|
||||
file_handler = logging.FileHandler(log_filepath, encoding="utf-8")
|
||||
file_handler.setLevel(numeric)
|
||||
file_handler.setFormatter(logging.Formatter(_DEFAULT_LOG_FORMAT))
|
||||
logging.getLogger().addHandler(file_handler)
|
||||
|
||||
# Re-initialize loguru for reme internals at the desired level
|
||||
from reme.utils import get_logger
|
||||
|
||||
reme_log_dir = log_dir or "logs"
|
||||
get_logger(log_dir=reme_log_dir, level=reme_log_level.upper(), force_init=True)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Config loading
|
||||
# ---------------------------------------------------------------------------
|
||||
def load_eval_config(config_path: str | None = None) -> dict:
|
||||
"""Load evaluation config yaml with env-var expansion."""
|
||||
if config_path is None:
|
||||
config_path = str(Path(__file__).parent / "config.yaml")
|
||||
with open(config_path, encoding="utf-8") as f:
|
||||
raw = f.read()
|
||||
|
||||
# Expand ${VAR} and ${VAR:-default}
|
||||
def _expand(m):
|
||||
expr = m.group(1)
|
||||
if ":-" in expr:
|
||||
key, default = expr.split(":-", 1)
|
||||
return os.environ.get(key, default)
|
||||
return os.environ.get(expr, "")
|
||||
|
||||
raw = re.sub(r"\$\{([^}]+)\}", _expand, raw)
|
||||
return yaml.safe_load(raw)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# BEAM data loading
|
||||
# ---------------------------------------------------------------------------
|
||||
def parse_beam_time_anchor(time_str: str) -> datetime:
|
||||
"""Parse BEAM time_anchor format: 'March-15-2024' -> datetime."""
|
||||
for fmt in ("%B-%d-%Y", "%b-%d-%Y"):
|
||||
try:
|
||||
return datetime.strptime(time_str, fmt)
|
||||
except ValueError:
|
||||
continue
|
||||
raise ValueError(f"Cannot parse time_anchor: {time_str!r}")
|
||||
|
||||
|
||||
def load_beam_chat(chat_path: Path, chat_size: str, case_id: str) -> list[dict]:
|
||||
"""Load BEAM chat.json and convert to ReMe session format.
|
||||
|
||||
Each batch becomes one session with all its turns flattened.
|
||||
Each turn resolves its own time_anchor independently; turns without
|
||||
an explicit time_anchor inherit from the most recent preceding turn.
|
||||
Returns list of sessions, each with:
|
||||
- session_id: str
|
||||
- date: str (YYYY-MM-DD) — derived from the *first* turn's time
|
||||
- messages: list[dict] with name, role, content, created_at
|
||||
"""
|
||||
with open(chat_path, encoding="utf-8") as f:
|
||||
batches = json.load(f)
|
||||
|
||||
sessions = []
|
||||
for batch in batches:
|
||||
batch_num = batch["batch_number"]
|
||||
|
||||
# Resolve batch-level fallback (used when no turn has a time_anchor)
|
||||
batch_anchor = batch.get("time_anchor")
|
||||
if not batch_anchor:
|
||||
batch_anchor = "January-1-2024"
|
||||
|
||||
# Flatten all turns, resolving time_anchor per turn
|
||||
messages = []
|
||||
prev_dt = None # carries forward from previous turn
|
||||
first_dt = None # for session-level date
|
||||
|
||||
for turn in batch["turns"]:
|
||||
# Find this turn's own time_anchor from its messages
|
||||
turn_anchor = None
|
||||
for msg in turn:
|
||||
if msg.get("time_anchor"):
|
||||
turn_anchor = msg["time_anchor"]
|
||||
break
|
||||
|
||||
if turn_anchor:
|
||||
dt = parse_beam_time_anchor(turn_anchor)
|
||||
elif prev_dt is not None:
|
||||
dt = prev_dt # inherit from previous turn
|
||||
else:
|
||||
dt = parse_beam_time_anchor(batch_anchor)
|
||||
|
||||
if first_dt is None:
|
||||
first_dt = dt
|
||||
prev_dt = dt
|
||||
|
||||
for msg in turn:
|
||||
role = msg["role"]
|
||||
messages.append(
|
||||
{
|
||||
"name": role,
|
||||
"role": role,
|
||||
"content": msg["content"],
|
||||
"created_at": dt.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||
},
|
||||
)
|
||||
|
||||
sessions.append(
|
||||
{
|
||||
"session_id": f"beam_{chat_size}_{case_id}_batch{batch_num}",
|
||||
"date": first_dt.strftime("%Y-%m-%d"),
|
||||
"messages": messages,
|
||||
},
|
||||
)
|
||||
|
||||
return sessions
|
||||
|
||||
|
||||
def get_available_cases(beam_root: Path, chat_size: str) -> list[str]:
|
||||
"""Return sorted list of case IDs for a given chat size."""
|
||||
chats_dir = beam_root / "chats" / chat_size
|
||||
if not chats_dir.exists():
|
||||
return []
|
||||
return sorted(
|
||||
[d.name for d in chats_dir.iterdir() if d.is_dir()],
|
||||
key=int,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Answer generation
|
||||
# ---------------------------------------------------------------------------
|
||||
async def answer_question_agentic(app, question: str, compress_session: bool = False) -> tuple[str, dict]:
|
||||
"""Answer a probing question using ReMe's agentic_answer job.
|
||||
|
||||
Returns (answer, metadata)
|
||||
"""
|
||||
from reme.utils.evaluation_interface import track_agent_token_usage, track_job_counts
|
||||
|
||||
with (
|
||||
track_job_counts(["search"], app.context) as tool_counts,
|
||||
track_agent_token_usage(
|
||||
["bench"],
|
||||
app.context,
|
||||
) as token_usages,
|
||||
):
|
||||
query_resp = await app.run_job(
|
||||
"agentic_answer",
|
||||
query=question,
|
||||
compress_session=compress_session,
|
||||
)
|
||||
answer = (query_resp.answer or "").strip()
|
||||
|
||||
return answer, {
|
||||
"mode": "agentic",
|
||||
"tool_counts": tool_counts,
|
||||
"token_usage": token_usages["bench"],
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# BEAM rubric-based LLM-as-Judge
|
||||
# ---------------------------------------------------------------------------
|
||||
async def judge_answer(
|
||||
app,
|
||||
question: str,
|
||||
llm_response: str,
|
||||
rubric: list[str],
|
||||
question_type: str = "",
|
||||
) -> dict:
|
||||
"""Judge an answer via the answer_judge job (beam_rubric_judge_step)."""
|
||||
judge_resp = await app.run_job(
|
||||
"answer_judge",
|
||||
llm_response=llm_response,
|
||||
rubric=rubric,
|
||||
probing_question=question,
|
||||
question_type=question_type,
|
||||
)
|
||||
result = {
|
||||
"llm_judge_score": (judge_resp.metadata or {}).get("llm_judge_score", 0.0),
|
||||
"llm_judge_responses": (judge_resp.metadata or {}).get("llm_judge_responses", []),
|
||||
}
|
||||
# Include event_ordering extra metrics if present
|
||||
eo = (judge_resp.metadata or {}).get("event_ordering")
|
||||
if eo:
|
||||
result["event_ordering"] = eo
|
||||
return result
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main evaluation pipeline
|
||||
# ---------------------------------------------------------------------------
|
||||
async def evaluate_case(eval_config: dict, case_id: str, eval_only: bool = False) -> dict:
|
||||
"""Evaluate a single BEAM case end-to-end.
|
||||
|
||||
Args:
|
||||
eval_config: The evaluation configuration dict.
|
||||
case_id: The case directory name (e.g. "1").
|
||||
eval_only: If True, skip ingestion and only run query+judge
|
||||
using the existing workspace.
|
||||
|
||||
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"]
|
||||
compress_session = bool(eval_config["evaluation"].get("compress_session", False))
|
||||
beam_root = _PROJECT_ROOT / dataset_cfg.get("beam_root", "benchmark/beam/dataset/BEAM")
|
||||
chat_path = beam_root / "chats" / chat_size / case_id / "chat.json"
|
||||
probing_questions_path = beam_root / "chats" / chat_size / case_id / "probing_questions" / "probing_questions.json"
|
||||
|
||||
if not chat_path.exists():
|
||||
raise FileNotFoundError(f"Chat file not found: {chat_path}")
|
||||
if not probing_questions_path.exists():
|
||||
raise FileNotFoundError(f"Probing questions not found: {probing_questions_path}")
|
||||
|
||||
logger.info(
|
||||
"[Case %s] size=%s%s",
|
||||
case_id,
|
||||
chat_size,
|
||||
" [eval_only]" if eval_only else "",
|
||||
)
|
||||
|
||||
# Workspace setup
|
||||
workspace_root = _PROJECT_ROOT / dataset_cfg.get("workspace_root", _WORKSPACE_ROOT_DEFAULT)
|
||||
case_dir = workspace_root / f"{chat_size}_{case_id}"
|
||||
workspace_dir = str(case_dir / ".reme")
|
||||
|
||||
if eval_only:
|
||||
if not case_dir.exists() or not Path(workspace_dir).exists():
|
||||
raise FileNotFoundError(
|
||||
f"[Case {case_id}] eval_only: workspace not found at {case_dir}. "
|
||||
f"Run without --eval_only first to build the workspace.",
|
||||
)
|
||||
else:
|
||||
if case_dir.exists():
|
||||
shutil.rmtree(case_dir)
|
||||
logger.info(f"[Case {case_id}] Cleaned existing workspace: {case_dir}")
|
||||
else:
|
||||
logger.info(f"[Case {case_id}] Workspace not found, creating: {case_dir}")
|
||||
case_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Pre-initialize ReMe's loguru logger with the correct log_dir
|
||||
output_cfg = eval_config.get("output", {})
|
||||
if output_cfg.get("log_to_file", False):
|
||||
reme_log_dir = os.environ.get("REME_LOG_DIR")
|
||||
if reme_log_dir:
|
||||
from reme.utils import get_logger
|
||||
|
||||
get_logger(
|
||||
log_dir=reme_log_dir,
|
||||
level=os.environ.get("REME_LOG_LEVEL", "INFO"),
|
||||
log_to_console=output_cfg.get("log_to_console", True),
|
||||
log_to_file=True,
|
||||
force_init=True,
|
||||
)
|
||||
|
||||
cfg = resolve_app_config(
|
||||
config=eval_config["reme"]["config"],
|
||||
workspace_dir=workspace_dir,
|
||||
log_to_console=output_cfg.get("log_to_console", True),
|
||||
log_to_file=output_cfg.get("log_to_file", False),
|
||||
enable_logo=False,
|
||||
)
|
||||
|
||||
app = Application(**cfg)
|
||||
await app.start()
|
||||
|
||||
from reme.utils.evaluation_interface import check_agent_token_usage # noqa: E402
|
||||
|
||||
_MEM_AGENT_NAMES = ("default", "bench")
|
||||
sessions_ingested = 0
|
||||
memory_token_usage: dict[str, dict[str, int | None]] = {}
|
||||
try:
|
||||
if not eval_only:
|
||||
# ── Phase 1: Ingest sessions (with token tracking) ─────────
|
||||
sessions = load_beam_chat(chat_path, chat_size, case_id)
|
||||
logger.info(f"[Case {case_id}] Loaded {len(sessions)} sessions from chat.json")
|
||||
|
||||
# Snapshot token counters before memory construction
|
||||
mem_token_start = {name: check_agent_token_usage(name, app.context) for name in _MEM_AGENT_NAMES}
|
||||
|
||||
for i, session in enumerate(sessions):
|
||||
logger.info(
|
||||
f"[Case {case_id}] Ingesting session {i+1}/{len(sessions)}: "
|
||||
f"id={session['session_id']} date={session['date']} "
|
||||
f"msgs={len(session['messages'])}",
|
||||
)
|
||||
resp = await app.run_job(
|
||||
"auto_memory",
|
||||
messages=session["messages"],
|
||||
session_id=session["session_id"],
|
||||
date=session["date"],
|
||||
)
|
||||
if not resp.success:
|
||||
logger.warning(f"[Case {case_id}] auto_memory failed: {resp.answer}")
|
||||
else:
|
||||
logger.info(
|
||||
f"[Case {case_id}] auto_memory success: " f"{resp.answer[:100] if resp.answer else ''}",
|
||||
)
|
||||
await app.run_job("index_update")
|
||||
sessions_ingested += 1
|
||||
|
||||
# Final digest update
|
||||
logger.info(f"[Case {case_id}] Running digest_update...")
|
||||
await app.run_job("digest_update")
|
||||
logger.info(f"[Case {case_id}] Ingestion complete.")
|
||||
|
||||
# Compute memory construction token deltas
|
||||
for name in _MEM_AGENT_NAMES:
|
||||
end_usage = check_agent_token_usage(name, app.context)
|
||||
delta: dict[str, int | None] = {}
|
||||
for metric in _TOKEN_USAGE_METRICS:
|
||||
current = end_usage[metric]
|
||||
start = mem_token_start[name][metric]
|
||||
delta[metric] = None if current is None else current - (start or 0)
|
||||
memory_token_usage[name] = delta
|
||||
logger.info(f"[Case {case_id}] Memory construction token usage: {memory_token_usage}")
|
||||
|
||||
# ── Phase 2: Answer + Judge probing questions ───────────────
|
||||
with open(probing_questions_path, encoding="utf-8") as f:
|
||||
probing_questions = json.load(f)
|
||||
|
||||
total_questions = sum(len(v) for v in probing_questions.values())
|
||||
logger.info(f"[Case {case_id}] Total probing questions: {total_questions}")
|
||||
|
||||
all_question_results = []
|
||||
q_idx = 0
|
||||
|
||||
for q_type in probing_questions:
|
||||
logger.info(
|
||||
f"[Case {case_id}] Question type: {q_type} " f"({len(probing_questions[q_type])} questions)",
|
||||
)
|
||||
|
||||
for i, q in enumerate(probing_questions[q_type]):
|
||||
q_idx += 1
|
||||
question = q["question"]
|
||||
rubric = q.get("rubric", [])
|
||||
logger.info(
|
||||
f"[Case {case_id}] [{q_idx}/{total_questions}] " f"{q_type} Q{i+1}: {question[:100]}...",
|
||||
)
|
||||
|
||||
q_result = {
|
||||
"question_type": q_type,
|
||||
"question_index": i,
|
||||
"question": question,
|
||||
"rubric": rubric,
|
||||
}
|
||||
|
||||
# Agentic answer
|
||||
try:
|
||||
agentic_answer, agentic_meta = await answer_question_agentic(
|
||||
app,
|
||||
question,
|
||||
compress_session=compress_session,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"[Case {case_id}] Agentic answer failed: {e}")
|
||||
agentic_answer = f"(error: {e})"
|
||||
agentic_meta = {"error": str(e)}
|
||||
|
||||
if not agentic_answer:
|
||||
agentic_answer = "(no answer generated)"
|
||||
logger.info(f"[Case {case_id}] Agentic answer: {agentic_answer[:200]}...")
|
||||
logger.info(
|
||||
f"[Case {case_id}] Agentic tool calls: {agentic_meta.get('tool_counts', {})}",
|
||||
)
|
||||
logger.info(f"[Case {case_id}] Bench token usage: {agentic_meta.get('token_usage', {})}")
|
||||
|
||||
# Judge agentic answer
|
||||
logger.info(f"[Case {case_id}] Judging agentic ({q_type})...")
|
||||
agentic_judgment = await judge_answer(
|
||||
app,
|
||||
question,
|
||||
agentic_answer,
|
||||
rubric,
|
||||
question_type=q_type,
|
||||
)
|
||||
logger.info(
|
||||
f"[Case {case_id}] Agentic score: " f"{agentic_judgment['llm_judge_score']:.3f}",
|
||||
)
|
||||
|
||||
q_result["agentic_response"] = agentic_answer
|
||||
q_result["agentic_judgment"] = agentic_judgment
|
||||
q_result["agentic_metadata"] = agentic_meta
|
||||
|
||||
all_question_results.append(q_result)
|
||||
|
||||
finally:
|
||||
await app.close()
|
||||
|
||||
return {
|
||||
"case_id": case_id,
|
||||
"chat_size": chat_size,
|
||||
"sessions_ingested": sessions_ingested,
|
||||
"total_questions": len(all_question_results),
|
||||
"questions": all_question_results,
|
||||
"memory_token_usage": memory_token_usage,
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Worker: runs a single case in its own process with its own event loop
|
||||
# ---------------------------------------------------------------------------
|
||||
def _evaluate_case_worker(task_input: tuple) -> dict:
|
||||
"""Worker function for multiprocessing. Each process gets its own event loop."""
|
||||
eval_config, case_id, log_level, reme_log_level, eval_only, log_dir = task_input
|
||||
import asyncio # pylint: disable=import-outside-toplevel
|
||||
|
||||
_configure_worker(log_level, reme_log_level, log_dir=log_dir)
|
||||
|
||||
# Suppress httpx GC noise
|
||||
logging.getLogger("asyncio").setLevel(logging.CRITICAL)
|
||||
|
||||
return asyncio.run(evaluate_case(eval_config, case_id, eval_only=eval_only))
|
||||
|
||||
|
||||
def _indexed_worker(indexed_input: tuple) -> tuple:
|
||||
"""Module-level wrapper for imap_unordered with index tracking."""
|
||||
idx, task_input = indexed_input
|
||||
return idx, _evaluate_case_worker(task_input)
|
||||
|
||||
|
||||
def _resolve_num_workers(configured: int) -> int:
|
||||
"""Resolve num_workers: 0=auto (cpu_count-2, min 1), 1=sequential, >1=parallel."""
|
||||
if configured == 0:
|
||||
return max(1, (os.cpu_count() or 4) - 2)
|
||||
return max(1, configured)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Entry point
|
||||
# ---------------------------------------------------------------------------
|
||||
def main( # pylint: disable=too-many-statements
|
||||
config_path: str | None = None,
|
||||
log_level: str = "INFO",
|
||||
reme_log_level: str = "INFO",
|
||||
eval_only: bool = False,
|
||||
):
|
||||
"""Run the BEAM evaluation pipeline.
|
||||
|
||||
Args:
|
||||
config_path: Path to the YAML config file.
|
||||
log_level: Log level for the eval runner.
|
||||
reme_log_level: Log level for reme internal logs.
|
||||
eval_only: If True, skip ingestion and only run query+judge using
|
||||
existing workspaces.
|
||||
"""
|
||||
from multiprocessing import Pool # pylint: disable=import-outside-toplevel
|
||||
|
||||
# Load config BEFORE logging setup so log_dir is available
|
||||
eval_config = load_eval_config(config_path)
|
||||
|
||||
# Resolve per-run log directory from config
|
||||
output_cfg = eval_config.get("output", {})
|
||||
log_dir_abs = None
|
||||
if output_cfg.get("log_to_file", False):
|
||||
log_dir_raw = output_cfg.get("log_dir", "logs")
|
||||
log_prefix = output_cfg.get("log_prefix", "beam")
|
||||
run_ts = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||
log_dir_abs = str(_PROJECT_ROOT / log_dir_raw / f"{log_prefix}_{run_ts}")
|
||||
|
||||
setup_logging(log_level, reme_log_level, log_dir=log_dir_abs)
|
||||
dataset_cfg = eval_config["dataset"]
|
||||
chat_size = dataset_cfg["chat_size"]
|
||||
beam_root = _PROJECT_ROOT / dataset_cfg.get("beam_root", "benchmark/beam/dataset/BEAM")
|
||||
|
||||
# Determine which cases to run
|
||||
case_ids = dataset_cfg.get("case_ids") or []
|
||||
if not case_ids:
|
||||
case_ids = get_available_cases(beam_root, chat_size)
|
||||
|
||||
# Pagination
|
||||
start = dataset_cfg.get("start_index", 0)
|
||||
num_items = dataset_cfg.get("num_items", 0)
|
||||
if num_items > 0:
|
||||
case_ids = case_ids[start : start + num_items]
|
||||
elif start > 0:
|
||||
case_ids = case_ids[start:]
|
||||
|
||||
if not case_ids:
|
||||
logger.error(f"No cases found for chat_size={chat_size}")
|
||||
return
|
||||
|
||||
logger.info(
|
||||
"Evaluating %d case(s) for chat_size=%s: %s%s",
|
||||
len(case_ids),
|
||||
chat_size,
|
||||
case_ids,
|
||||
" [eval_only: query+judge only]" if eval_only else "",
|
||||
)
|
||||
|
||||
# Resolve parallelism
|
||||
num_workers = _resolve_num_workers(eval_config["evaluation"].get("num_workers", 1))
|
||||
logger.info(f"Using {num_workers} worker(s)")
|
||||
|
||||
# Create output directory
|
||||
output_dir = _PROJECT_ROOT / output_cfg.get("dir", "benchmark/beam/results")
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Create workspace root directory
|
||||
workspace_root = _PROJECT_ROOT / dataset_cfg.get("workspace_root", _WORKSPACE_ROOT_DEFAULT)
|
||||
workspace_root.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Pre-check: verify all workspaces exist in eval_only mode
|
||||
if eval_only:
|
||||
missing_cases = []
|
||||
for case_id in case_ids:
|
||||
case_dir = workspace_root / f"{chat_size}_{case_id}"
|
||||
if not case_dir.exists() or not (case_dir / ".reme").exists():
|
||||
missing_cases.append(case_id)
|
||||
if missing_cases:
|
||||
preview = missing_cases[:10]
|
||||
suffix = "..." if len(missing_cases) > 10 else ""
|
||||
raise FileNotFoundError(
|
||||
f"eval_only: {len(missing_cases)} workspace(s) not found under {workspace_root}. "
|
||||
f"Missing cases: {preview}{suffix}. "
|
||||
f"Run without --eval_only first to build the workspaces.",
|
||||
)
|
||||
|
||||
# Build task args
|
||||
task_args = [(eval_config, case_id, log_level, reme_log_level, eval_only, log_dir_abs) for case_id in case_ids]
|
||||
|
||||
# Progress tracking
|
||||
total_items = len(task_args)
|
||||
completed_count = [0]
|
||||
start_time = time.time()
|
||||
progress_lock = threading.Lock()
|
||||
|
||||
def _print_progress(prefix: str = "PROGRESS"):
|
||||
elapsed = time.time() - start_time
|
||||
elapsed_min = elapsed / 60
|
||||
done = completed_count[0]
|
||||
pct = 100.0 * done / total_items if total_items else 0
|
||||
eta_str = "N/A"
|
||||
if done > 0:
|
||||
eta_sec = elapsed / done * (total_items - done)
|
||||
eta_str = f"{eta_sec/60:.1f}min"
|
||||
print(
|
||||
f"[{prefix}] {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | "
|
||||
f"{done}/{total_items} ({pct:.1f}%) completed | "
|
||||
f"elapsed={elapsed_min:.1f}min | ETA={eta_str}",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
def _progress_timer():
|
||||
"""Background thread: print progress every 10 minutes."""
|
||||
while not _timer_stop.is_set():
|
||||
_timer_stop.wait(600)
|
||||
if not _timer_stop.is_set():
|
||||
with progress_lock:
|
||||
_print_progress()
|
||||
|
||||
_timer_stop = threading.Event()
|
||||
timer_thread = threading.Thread(target=_progress_timer, daemon=True)
|
||||
timer_thread.start()
|
||||
|
||||
# Run evaluation
|
||||
if num_workers == 1:
|
||||
results = []
|
||||
for task_input in task_args:
|
||||
result = _evaluate_case_worker(task_input)
|
||||
results.append(result)
|
||||
with progress_lock:
|
||||
completed_count[0] += 1
|
||||
else:
|
||||
results = [None] * total_items
|
||||
indexed_args = list(enumerate(task_args))
|
||||
|
||||
with Pool(processes=num_workers) as pool:
|
||||
for idx, result in pool.imap_unordered(_indexed_worker, indexed_args):
|
||||
results[idx] = result
|
||||
with progress_lock:
|
||||
completed_count[0] += 1
|
||||
|
||||
# Stop progress timer
|
||||
_timer_stop.set()
|
||||
timer_thread.join(timeout=2)
|
||||
|
||||
# Save results
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
output_file = output_dir / f"results_{chat_size}_{timestamp}.json"
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
json.dump(results, f, ensure_ascii=False, indent=2)
|
||||
logger.info(f"Results saved to {output_file}")
|
||||
|
||||
# Final progress
|
||||
_print_progress("FINAL")
|
||||
|
||||
# Print concise summary
|
||||
print("\n" + "=" * 70)
|
||||
print(f" BEAM EVALUATION RESULTS | size={chat_size} cases={len(results)}")
|
||||
print("=" * 70)
|
||||
|
||||
# Per-type stats (agentic only)
|
||||
type_scores: dict[str, list[float]] = {}
|
||||
type_binary_scores: dict[str, list[float]] = {}
|
||||
all_scores: list[float] = []
|
||||
all_binary_scores: list[float] = []
|
||||
all_tool_call_totals: list[int] = []
|
||||
all_token_usages: list[dict[str, int | None]] = []
|
||||
all_memory_token_usages: list[dict[str, dict[str, int | None]]] = []
|
||||
|
||||
for case_result in results:
|
||||
if "error" in case_result:
|
||||
continue
|
||||
mem_usage = case_result.get("memory_token_usage", {})
|
||||
if mem_usage:
|
||||
all_memory_token_usages.append(mem_usage)
|
||||
for q in case_result.get("questions", []):
|
||||
judgment = q.get("agentic_judgment", {})
|
||||
score = judgment.get("llm_judge_score", 0.0)
|
||||
# Binary: convert each rubric item score to 0/1, then average
|
||||
judge_responses = judgment.get("llm_judge_responses", [])
|
||||
if judge_responses:
|
||||
binary_scores_per_item = [1.0 if r.get("score", 0) >= 1.0 else 0.0 for r in judge_responses]
|
||||
binary_score = sum(binary_scores_per_item) / len(binary_scores_per_item)
|
||||
else:
|
||||
binary_score = 1.0 if score > 0.99 else 0.0
|
||||
qtype = q["question_type"]
|
||||
if qtype not in type_scores:
|
||||
type_scores[qtype] = []
|
||||
type_binary_scores[qtype] = []
|
||||
type_scores[qtype].append(score)
|
||||
type_binary_scores[qtype].append(binary_score)
|
||||
all_scores.append(score)
|
||||
all_binary_scores.append(binary_score)
|
||||
metadata = q.get("agentic_metadata", {})
|
||||
all_tool_call_totals.append(sum(metadata.get("tool_counts", {}).values()))
|
||||
all_token_usages.append(metadata.get("token_usage", {}))
|
||||
|
||||
# Memory construction token usage summary
|
||||
if all_memory_token_usages:
|
||||
print("\n ── Memory Construction Token Usage ──")
|
||||
for agent_name in ("default", "bench"):
|
||||
for metric in _TOKEN_USAGE_METRICS:
|
||||
values = [
|
||||
usage[agent_name][metric]
|
||||
for usage in all_memory_token_usages
|
||||
if usage.get(agent_name, {}).get(metric) is not None
|
||||
]
|
||||
if values:
|
||||
total = sum(values)
|
||||
mean, std = _mean_and_std(values)
|
||||
print(
|
||||
f" {agent_name}/{metric}: total={total} mean={mean:.2f} std={std:.2f} ({len(values)} cases)",
|
||||
)
|
||||
else:
|
||||
print(f" {agent_name}/{metric}: unavailable")
|
||||
print()
|
||||
|
||||
print("\n ── AGENTIC ──")
|
||||
if all_scores:
|
||||
for qtype in sorted(type_scores.keys()):
|
||||
scores = type_scores[qtype]
|
||||
avg = sum(scores) / len(scores) if scores else 0
|
||||
bin_scores = type_binary_scores[qtype]
|
||||
bin_avg = sum(bin_scores) / len(bin_scores) if bin_scores else 0
|
||||
print(f" {qtype:<40s}: {avg:.3f} binary={bin_avg:.3f} ({len(scores)} Qs)")
|
||||
overall = sum(all_scores) / len(all_scores) if all_scores else 0
|
||||
binary_overall = sum(all_binary_scores) / len(all_binary_scores) if all_binary_scores else 0
|
||||
print(f" {'-'*38}")
|
||||
print(f" {'OVERALL':<40s}: {overall:.3f} binary={binary_overall:.3f} ({len(all_scores)} Qs)")
|
||||
tool_call_mean, tool_call_std = _mean_and_std(all_tool_call_totals)
|
||||
print(f" Tool calls/query: mean={tool_call_mean:.2f} std={tool_call_std:.2f}")
|
||||
print(" Bench reported tokens/query:")
|
||||
for metric in _TOKEN_USAGE_METRICS:
|
||||
values = [usage[metric] for usage in all_token_usages if usage.get(metric) is not None]
|
||||
if values:
|
||||
mean, std = _mean_and_std(values)
|
||||
print(f" {metric}: mean={mean:.2f} std={std:.2f}")
|
||||
else:
|
||||
print(f" {metric}: unavailable")
|
||||
else:
|
||||
print(" (no results)")
|
||||
|
||||
# Per-case summary
|
||||
print("\n ── Per-Case Summary ──")
|
||||
for case_result in results:
|
||||
case_id = case_result["case_id"]
|
||||
if "error" in case_result:
|
||||
print(f" Case {case_id}: ERROR — {case_result['error']}")
|
||||
continue
|
||||
n_qs = case_result.get("total_questions", 0)
|
||||
n_sessions = case_result.get("sessions_ingested", 0)
|
||||
mem_usage = case_result.get("memory_token_usage", {})
|
||||
parts = [f"Case {case_id}: {n_sessions} sessions, {n_qs} questions"]
|
||||
# Append memory construction total tokens if available
|
||||
for agent_name in ("default", "bench"):
|
||||
agent_usage = mem_usage.get(agent_name, {})
|
||||
total = agent_usage.get("total_tokens")
|
||||
if total is not None:
|
||||
parts.append(f"mem_{agent_name}_tokens={total}")
|
||||
questions = case_result.get("questions", [])
|
||||
scores = [q.get("agentic_judgment", {}).get("llm_judge_score", 0.0) for q in questions]
|
||||
if scores:
|
||||
avg = sum(scores) / len(scores)
|
||||
# Binary: 0/1 per rubric item, average per question, then across questions
|
||||
bin_scores = []
|
||||
for q in questions:
|
||||
judge_responses = q.get("agentic_judgment", {}).get("llm_judge_responses", [])
|
||||
if judge_responses:
|
||||
item_bins = [1.0 if r.get("score", 0) >= 1.0 else 0.0 for r in judge_responses]
|
||||
bin_scores.append(sum(item_bins) / len(item_bins))
|
||||
else:
|
||||
s = q.get("agentic_judgment", {}).get("llm_judge_score", 0.0)
|
||||
bin_scores.append(1.0 if s > 0.99 else 0.0)
|
||||
bin_avg = sum(bin_scores) / len(bin_scores)
|
||||
parts.append(f"agentic={avg:.3f} binary={bin_avg:.3f}")
|
||||
print(f" {' | '.join(parts)}")
|
||||
|
||||
print("=" * 70)
|
||||
total_elapsed = time.time() - start_time
|
||||
print(f"\n Total time: {total_elapsed/60:.1f} min")
|
||||
print("\n" + "=" * 70)
|
||||
print(" [DONE] BEAM EVALUATION COMPLETED SUCCESSFULLY")
|
||||
print("=" * 70 + "\n")
|
||||
|
||||
|
||||
_TOKEN_USAGE_METRICS = (
|
||||
"input_tokens",
|
||||
"output_tokens",
|
||||
"total_tokens",
|
||||
)
|
||||
|
||||
|
||||
def _mean_and_std(values: list[int]) -> tuple[float, float]:
|
||||
"""Return population mean and standard deviation for one per-question metric."""
|
||||
if not values:
|
||||
return 0.0, 0.0
|
||||
mean = sum(values) / len(values)
|
||||
return mean, (sum((value - mean) ** 2 for value in values) / len(values)) ** 0.5
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="BEAM evaluation runner")
|
||||
parser.add_argument("--config", type=str, default=None, help="Path to config.yaml")
|
||||
parser.add_argument(
|
||||
"--log-level",
|
||||
type=str,
|
||||
default="INFO",
|
||||
choices=["DEBUG", "INFO", "WARNING", "ERROR"],
|
||||
help="Log level for the eval runner (default: INFO)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--reme-log-level",
|
||||
type=str,
|
||||
default="INFO",
|
||||
choices=["DEBUG", "INFO", "WARNING", "ERROR"],
|
||||
help="Log level for reme internal logs — loguru (default: INFO)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-q",
|
||||
"--quiet",
|
||||
action="store_true",
|
||||
help="Shortcut for --log-level WARNING --reme-log-level WARNING",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--eval_only",
|
||||
action="store_true",
|
||||
help="Skip ingestion. Reuse existing workspaces and only run query+judge.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.quiet:
|
||||
args.log_level = "WARNING"
|
||||
args.reme_log_level = "WARNING"
|
||||
|
||||
main(args.config, args.log_level, args.reme_log_level, eval_only=args.eval_only)
|
||||
96
benchmark/longmemeval/README.md
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
[中文版 / Chinese version](./README_ZH.md)
|
||||
|
||||
# LongMemEval Benchmark
|
||||
|
||||
LongMemEval is a benchmark for **long-term memory over multi-session chat
|
||||
histories**. Each item provides a chronologically ordered set of chat sessions
|
||||
between a user and an assistant, followed by a probing question whose answer is
|
||||
only recoverable by reasoning over the user-owned memory. ReMe ingests the
|
||||
sessions into an isolated per-item workspace, answers the question via an
|
||||
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).
|
||||
|
||||
## 1. Get the Dataset
|
||||
|
||||
ReMe uses only the **cleaned-S** split, hosted on HuggingFace:
|
||||
[agentscope-ai/ReMe_longmemeval_clean_s_v2](https://huggingface.co/datasets/agentscope-ai/ReMe_longmemeval_clean_s_v2).
|
||||
The download script fetches it via the hf-mirror.com mirror; to use a different
|
||||
mirror, modify `BASE_URL` in [`download.py`](./download.py).
|
||||
|
||||
```bash
|
||||
cd benchmark/longmemeval
|
||||
python download.py # saves dataset/longmemeval_s_reme_cleaned.json; skips if already present
|
||||
```
|
||||
|
||||
Ground truth is embedded in the data file.
|
||||
|
||||
## 2. Run
|
||||
|
||||
From the repository root:
|
||||
|
||||
```bash
|
||||
python benchmark/longmemeval/run.py
|
||||
python benchmark/longmemeval/run.py --config benchmark/longmemeval/config.yaml
|
||||
python benchmark/longmemeval/run.py -q # quiet: only eval-level logs
|
||||
python benchmark/longmemeval/run.py --log-level WARNING # reduce eval runner logs
|
||||
python benchmark/longmemeval/run.py --reme-log-level WARNING # reduce reme internal logs
|
||||
python benchmark/longmemeval/run.py --eval_only # reuse existing workspaces, query + judge only
|
||||
```
|
||||
|
||||
## 3. Pipeline
|
||||
|
||||
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).
|
||||
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.
|
||||
|
||||
## 4. Key config — `benchmark/longmemeval/config.yaml`
|
||||
|
||||
| Key | Meaning |
|
||||
| --- | --- |
|
||||
| `dataset.path` | Dataset file to evaluate (e.g. `longmemeval_s_reme_cleaned.json`); ground truth is included. |
|
||||
| `dataset.start_index` / `num_items` | Slice of items to evaluate. |
|
||||
| `dataset.question_types` | Filter by question type; empty = all. |
|
||||
| `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.dream_trigger_hour` / `dream_scan_days` / `dream_max_units` | Dream triggering behavior. |
|
||||
| `output.dir` | Results directory (`benchmark/longmemeval/results`). |
|
||||
|
||||
## 5. Outputs
|
||||
|
||||
Results are JSON files written to `output.dir` as `results_<timestamp>.json`,
|
||||
with a per-type accuracy summary also printed to the console. Logging
|
||||
conventions are shared across benchmarks — see the
|
||||
[top-level README](../README.md#outputs--logs).
|
||||
|
||||
## 6. Reference Results
|
||||
|
||||
### cleaned-s
|
||||
|
||||
**Basic settings**
|
||||
|
||||
1. Modified auto-memory prompt, auto-dream disabled.
|
||||
2. All sessions in reme-memory are strictly earlier than the question time.
|
||||
|
||||
**Results**
|
||||
|
||||
agentscope==2.0.4.post1, conda reme env, 32 workers, eval-only (reusing prebuilt memory)
|
||||
(2026-08-06, 500 items, total 10.0 min)
|
||||
|
||||
| Type | Agentic | input tok/q | output tok/q | total tok/q | tool calls/q |
|
||||
|---|---|---|---|---|---|
|
||||
| knowledge-update | 0.910 | 31,581 | 589 | 32,169 | 2.90 |
|
||||
| multi-session | 0.842 | 52,837 | 1,474 | 54,311 | 4.21 |
|
||||
| single-session-assistant | 1.000 | 15,596 | 279 | 15,875 | 1.89 |
|
||||
| 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** |
|
||||
90
benchmark/longmemeval/README_ZH.md
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
# LongMemEval 评测
|
||||
|
||||
[English version](./README.md)
|
||||
|
||||
LongMemEval 是一个面向**多轮多会话历史的长期记忆能力**的评测基准。每个条目提供一组按时间
|
||||
顺序排列的用户与助手之间的会话,以及一个只能通过推理用户自有记忆才能回答的探测问题。ReMe
|
||||
将会话摄入按条目隔离的工作区,以 agentic(ReAct)模式回答问题,最后由 LLM-as-judge 打分。
|
||||
|
||||
题型包括单会话(user / assistant / preference)、多会话推理、知识更新与时间推理等。
|
||||
|
||||
> 公共设置(依赖、凭据、日志约定)见[总评测说明](../README_ZH.md)。
|
||||
|
||||
## 1. 获取数据集
|
||||
|
||||
ReMe 仅使用 **cleaned-S** 版本,数据托管在 HuggingFace:
|
||||
[agentscope-ai/ReMe_longmemeval_clean_s_v2](https://huggingface.co/datasets/agentscope-ai/ReMe_longmemeval_clean_s_v2)。
|
||||
下载脚本经 hf-mirror.com 镜像源获取,如需更换源请修改 [`download.py`](./download.py) 中的
|
||||
`BASE_URL`。
|
||||
|
||||
```bash
|
||||
cd benchmark/longmemeval
|
||||
python download.py # 保存为 dataset/longmemeval_s_reme_cleaned.json,已存在则自动跳过
|
||||
```
|
||||
|
||||
ground truth 已内嵌在数据文件中。
|
||||
|
||||
## 2. 运行
|
||||
|
||||
在仓库根目录执行:
|
||||
|
||||
```bash
|
||||
python benchmark/longmemeval/run.py
|
||||
python benchmark/longmemeval/run.py --config benchmark/longmemeval/config.yaml
|
||||
python benchmark/longmemeval/run.py -q # 安静模式:仅评测级日志
|
||||
python benchmark/longmemeval/run.py --log-level WARNING # 降低评测 runner 日志
|
||||
python benchmark/longmemeval/run.py --reme-log-level WARNING # 降低 reme 内部日志
|
||||
python benchmark/longmemeval/run.py --eval_only # 复用已有工作区,仅执行查询 + 评判
|
||||
```
|
||||
|
||||
## 3. 流程
|
||||
|
||||
1. 加载数据集(ground truth 已内嵌在数据文件中)。
|
||||
2. 为每个条目创建独立工作区,按时间顺序摄入会话。
|
||||
3. 当相邻会话跨越配置的时刻(默认 23:00)时触发 `auto_dream`。
|
||||
4. 以 agentic(ReAct)模式回答每个问题。
|
||||
5. 通过 `answer_judge` 任务对答案做二元(yes/no)评判,并输出各类型准确率。
|
||||
|
||||
## 4. 关键配置 —— `benchmark/longmemeval/config.yaml`
|
||||
|
||||
| 配置项 | 含义 |
|
||||
| --- | --- |
|
||||
| `dataset.path` | 待评测的数据集文件(如 `longmemeval_s_reme_cleaned.json`),已包含 ground truth。 |
|
||||
| `dataset.start_index` / `num_items` | 评测条目的切片范围。 |
|
||||
| `dataset.question_types` | 按问题类型过滤,空表示全部。 |
|
||||
| `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.dream_trigger_hour` / `dream_scan_days` / `dream_max_units` | dream 触发行为。 |
|
||||
| `output.dir` | 结果目录(`benchmark/longmemeval/results`)。 |
|
||||
|
||||
## 5. 输出
|
||||
|
||||
结果以 JSON 文件写入 `output.dir`,文件名为 `results_<timestamp>.json`,
|
||||
同时控制台会打印含各类型准确率的汇总。日志约定在各基准间通用,见
|
||||
[总说明](../README_ZH.md#输出与日志)。
|
||||
|
||||
## 6. 参考结果
|
||||
|
||||
### cleaned-s
|
||||
|
||||
**基础设置**
|
||||
|
||||
1. 使用修改后的 auto-memory prompt,关闭 auto-dream 机制
|
||||
2. reme-memory 中的全部 session 的时间一定早于 question 的时间
|
||||
|
||||
**结果**
|
||||
|
||||
agentscope==2.0.4.post1, conda reme env, 32 workers, eval-only(复用预构建记忆)
|
||||
(2026-08-06,500 题,总计 10.0 min)
|
||||
|
||||
| 类型 | Agentic | input tok/q | output tok/q | total tok/q | tool calls/q |
|
||||
|---|---|---|---|---|---|
|
||||
| knowledge-update | 0.910 | 31,581 | 589 | 32,169 | 2.90 |
|
||||
| multi-session | 0.842 | 52,837 | 1,474 | 54,311 | 4.21 |
|
||||
| single-session-assistant | 1.000 | 15,596 | 279 | 15,875 | 1.89 |
|
||||
| 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** |
|
||||
33
benchmark/longmemeval/config.yaml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# LongMemEval evaluation configuration
|
||||
# This file controls what/how to evaluate.
|
||||
|
||||
dataset:
|
||||
path: "benchmark/longmemeval/dataset/longmemeval_s_reme_cleaned.json"
|
||||
start_index: 0 # first item index
|
||||
num_items: 500 # how many items to evaluate (starting from start_index)
|
||||
max_sessions: 0 # 0 = all sessions; >0 = limit sessions per item for testing
|
||||
question_types: [] # filter by question_type; empty list = no filtering (all types)
|
||||
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
|
||||
# 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
|
||||
filter_future_sessions: true # true = only ingest sessions with timestamp <= question_date
|
||||
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/)
|
||||
# Dream trigger: when gap between consecutive sessions crosses this hour (23:00)
|
||||
dream_trigger_hour: 23
|
||||
# Dream scan_days for each trigger
|
||||
dream_scan_days: 2
|
||||
dream_max_units: 5
|
||||
|
||||
output:
|
||||
dir: "benchmark/longmemeval/results"
|
||||
log_dir: "logs" # log directory (relative to project root)
|
||||
log_prefix: "longmemeval" # benchmark name used in log filenames
|
||||
log_to_console: true
|
||||
log_to_file: true
|
||||
67
benchmark/longmemeval/download.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
"""Download the LongMemEval cleaned-S dataset used by ReMe.
|
||||
|
||||
Source: https://huggingface.co/datasets/agentscope-ai/ReMe_longmemeval_clean_s_v2
|
||||
(downloaded via the hf-mirror.com mirror for reliability).
|
||||
|
||||
The file ``longmemeval_s_reme_cleaned.json`` is saved under ``dataset/`` next to this
|
||||
script using the same name as on the remote (``benchmark/longmemeval/config.yaml``
|
||||
points to it).
|
||||
|
||||
Usage:
|
||||
python download.py # download cleaned-S (skip if it already exists)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import urllib.request
|
||||
|
||||
BASE_URL = "https://hf-mirror.com/datasets/agentscope-ai/ReMe_longmemeval_clean_s_v2/resolve/main"
|
||||
TARGET_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "dataset")
|
||||
|
||||
# Files to download (saved with the same name as on the remote).
|
||||
FILES = [
|
||||
"longmemeval_s_reme_cleaned.json",
|
||||
]
|
||||
|
||||
|
||||
def download_file(filename: str):
|
||||
"""Download a single file from the mirror to the target directory."""
|
||||
url = f"{BASE_URL}/{filename}"
|
||||
dest = os.path.join(TARGET_DIR, filename)
|
||||
|
||||
if os.path.exists(dest):
|
||||
size = os.path.getsize(dest)
|
||||
print(f" [skip] {filename} already exists ({size / 1024 / 1024:.1f} MB)")
|
||||
return
|
||||
|
||||
print(f" [downloading] {filename} ...")
|
||||
try:
|
||||
urllib.request.urlretrieve(url, dest, reporthook=_progress)
|
||||
size = os.path.getsize(dest)
|
||||
print(f"\n [done] {filename} ({size / 1024 / 1024:.1f} MB)")
|
||||
except Exception as e:
|
||||
print(f"\n [error] {filename}: {e}")
|
||||
if os.path.exists(dest):
|
||||
os.remove(dest)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _progress(block_num, block_size, total_size):
|
||||
downloaded = block_num * block_size
|
||||
if total_size > 0:
|
||||
pct = min(100, downloaded * 100 / total_size)
|
||||
mb = downloaded / 1024 / 1024
|
||||
total_mb = total_size / 1024 / 1024
|
||||
sys.stdout.write(f"\r {mb:.1f}/{total_mb:.1f} MB ({pct:.1f}%)")
|
||||
else:
|
||||
mb = downloaded / 1024 / 1024
|
||||
sys.stdout.write(f"\r {mb:.1f} MB downloaded")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.makedirs(TARGET_DIR, exist_ok=True)
|
||||
print(f"Downloading LongMemEval cleaned-S dataset to: {TARGET_DIR}\n")
|
||||
for fname in FILES:
|
||||
download_file(fname)
|
||||
print("\nAll files downloaded successfully!")
|
||||
76
benchmark/longmemeval/kill.sh
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#!/bin/bash
|
||||
# 杀死指定进程及其所有子进程
|
||||
# Usage: bash kill.sh <PID>
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: bash kill.sh <PID>"
|
||||
echo " 杀死指定进程及其所有子进程"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PID=$1
|
||||
|
||||
# 检查进程是否存在
|
||||
if ! kill -0 "$PID" 2>/dev/null; then
|
||||
echo "进程 $PID 不存在"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 递归收集所有子进程(包括子进程的子进程)
|
||||
collect_children() {
|
||||
local parent=$1
|
||||
local children
|
||||
children=$(ps -o pid= --ppid "$parent" 2>/dev/null | tr -d ' ')
|
||||
for child in $children; do
|
||||
collect_children "$child"
|
||||
done
|
||||
echo "$parent"
|
||||
}
|
||||
|
||||
# 收集进程树(子进程在前,父进程在后,保证先杀子再杀父)
|
||||
PROCESS_TREE=$(collect_children "$PID")
|
||||
TOTAL=$(echo "$PROCESS_TREE" | wc -l | tr -d ' ')
|
||||
|
||||
echo "进程树(共 $TOTAL 个进程):"
|
||||
while read -r p; do
|
||||
cmd=$(ps -o args= -p "$p" 2>/dev/null | head -c 80)
|
||||
printf " PID=%-8s %s\n" "$p" "$cmd"
|
||||
done <<< "$PROCESS_TREE"
|
||||
|
||||
# 先 SIGTERM 优雅终止
|
||||
echo ""
|
||||
echo "发送 SIGTERM..."
|
||||
while read -r p; do
|
||||
kill "$p" 2>/dev/null
|
||||
done <<< "$PROCESS_TREE"
|
||||
|
||||
# 等待最多 5 秒
|
||||
for i in $(seq 1 5); do
|
||||
alive=false
|
||||
while read -r p; do
|
||||
if kill -0 "$p" 2>/dev/null; then
|
||||
alive=true
|
||||
fi
|
||||
done <<< "$PROCESS_TREE"
|
||||
if [ "$alive" = false ]; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# 检查是否还有残留,强制 SIGKILL
|
||||
remaining=false
|
||||
while read -r p; do
|
||||
if kill -0 "$p" 2>/dev/null; then
|
||||
remaining=true
|
||||
fi
|
||||
done <<< "$PROCESS_TREE"
|
||||
|
||||
if [ "$remaining" = true ]; then
|
||||
echo "部分进程未响应,发送 SIGKILL..."
|
||||
while read -r p; do
|
||||
kill -9 "$p" 2>/dev/null
|
||||
done <<< "$PROCESS_TREE"
|
||||
fi
|
||||
|
||||
echo "已终止进程树(根 PID=$PID,共 $TOTAL 个进程)"
|
||||
816
benchmark/longmemeval/run.py
Normal file
|
|
@ -0,0 +1,816 @@
|
|||
"""LongMemEval evaluation runner for ReMe.
|
||||
|
||||
Evaluates ReMe's long-term memory capability using the LongMemEval dataset.
|
||||
Each item gets an isolated workspace; sessions are ingested in chronological order;
|
||||
dream is triggered when sessions cross midnight (23:00); finally questions are
|
||||
answered via an agentic (ReAct) approach and judged by an LLM.
|
||||
|
||||
Usage:
|
||||
python benchmark/longmemeval/run.py
|
||||
python benchmark/longmemeval/run.py --config benchmark/longmemeval/config.yaml
|
||||
python benchmark/longmemeval/run.py -q # quiet: only eval-level logs
|
||||
python benchmark/longmemeval/run.py --log-level WARNING # reduce eval runner logs
|
||||
python benchmark/longmemeval/run.py --reme-log-level WARNING # reduce reme internal logs
|
||||
python benchmark/longmemeval/run.py --eval_only # query+judge only, reuse existing workspace
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import time
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load .env from project root
|
||||
_PROJECT_ROOT = Path(__file__).parent.parent.parent
|
||||
load_dotenv(_PROJECT_ROOT / ".env")
|
||||
|
||||
# Workspace root for evaluation items — read from config.yaml (dataset.workspace_root)
|
||||
_WORKSPACE_ROOT_DEFAULT = "benchmark/longmemeval/workspaces/longmemeval-s"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Logging
|
||||
# ---------------------------------------------------------------------------
|
||||
_DEFAULT_LOG_FORMAT = "%(asctime)s | %(levelname)s | %(message)s"
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format=_DEFAULT_LOG_FORMAT)
|
||||
logger = logging.getLogger("longmemeval")
|
||||
|
||||
# Noisy library loggers silenced by default
|
||||
_NOISY_LOGGERS = [
|
||||
"httpx",
|
||||
"httpcore",
|
||||
"openai",
|
||||
"uvicorn",
|
||||
"multipart",
|
||||
"asyncio",
|
||||
"watchfiles",
|
||||
"filelock",
|
||||
]
|
||||
|
||||
|
||||
def setup_logging(
|
||||
log_level: str,
|
||||
reme_log_level: str,
|
||||
log_dir: str | None = None,
|
||||
):
|
||||
"""Configure logging for the eval runner and reme internals.
|
||||
|
||||
Args:
|
||||
log_level: Level for the eval runner logger (DEBUG/INFO/WARNING/ERROR).
|
||||
reme_log_level: Level for reme's internal loguru logger.
|
||||
log_dir: Per-run log directory (absolute path). None = no file logging.
|
||||
"""
|
||||
numeric = getattr(logging, log_level.upper(), logging.INFO)
|
||||
# Eval runner logger
|
||||
logging.getLogger().setLevel(numeric)
|
||||
logger.setLevel(numeric)
|
||||
|
||||
# Suppress noisy library loggers when above DEBUG
|
||||
if numeric > logging.DEBUG:
|
||||
for name in _NOISY_LOGGERS:
|
||||
lib_logger = logging.getLogger(name)
|
||||
lib_logger.setLevel(max(numeric, logging.WARNING))
|
||||
|
||||
# Add file handler for eval runner if log_dir is specified
|
||||
if log_dir:
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
log_filepath = os.path.join(log_dir, "runner.log")
|
||||
file_handler = logging.FileHandler(log_filepath, encoding="utf-8")
|
||||
file_handler.setLevel(numeric)
|
||||
file_handler.setFormatter(logging.Formatter(_DEFAULT_LOG_FORMAT))
|
||||
logging.getLogger().addHandler(file_handler)
|
||||
logger.info(f"Eval runner log file: {log_filepath}")
|
||||
|
||||
# Reme internal logger (loguru) — will be applied per-worker via _configure_worker
|
||||
os.environ["REME_LOG_LEVEL"] = reme_log_level.upper()
|
||||
if log_dir:
|
||||
os.environ["REME_LOG_DIR"] = log_dir
|
||||
|
||||
|
||||
def _configure_worker(
|
||||
log_level: str,
|
||||
reme_log_level: str,
|
||||
log_dir: str | None = None,
|
||||
):
|
||||
"""Set up logging inside a multiprocessing worker process.
|
||||
|
||||
Must be called at the top of each worker because child processes inherit
|
||||
parent state but loguru sinks are NOT shared across fork/spawn.
|
||||
"""
|
||||
numeric = getattr(logging, log_level.upper(), logging.INFO)
|
||||
logging.basicConfig(level=numeric, format=_DEFAULT_LOG_FORMAT, force=True)
|
||||
logging.getLogger("longmemeval").setLevel(numeric)
|
||||
if numeric > logging.DEBUG:
|
||||
for name in _NOISY_LOGGERS:
|
||||
logging.getLogger(name).setLevel(max(numeric, logging.WARNING))
|
||||
|
||||
# Add file handler for eval runner in worker process
|
||||
if log_dir:
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
pid = os.getpid()
|
||||
log_filepath = os.path.join(log_dir, f"worker-{pid}.log")
|
||||
file_handler = logging.FileHandler(log_filepath, encoding="utf-8")
|
||||
file_handler.setLevel(numeric)
|
||||
file_handler.setFormatter(logging.Formatter(_DEFAULT_LOG_FORMAT))
|
||||
logging.getLogger().addHandler(file_handler)
|
||||
|
||||
# Re-initialize loguru for reme internals at the desired level
|
||||
from reme.utils import get_logger
|
||||
|
||||
reme_log_dir = log_dir or "logs"
|
||||
get_logger(log_dir=reme_log_dir, level=reme_log_level.upper(), force_init=True)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Config loading
|
||||
# ---------------------------------------------------------------------------
|
||||
def load_eval_config(config_path: str | None = None) -> dict:
|
||||
"""Load evaluation config yaml with env-var expansion."""
|
||||
if config_path is None:
|
||||
config_path = str(Path(__file__).parent / "config.yaml")
|
||||
with open(config_path, encoding="utf-8") as f:
|
||||
raw = f.read()
|
||||
|
||||
# Expand ${VAR} and ${VAR:-default}
|
||||
def _expand(m):
|
||||
expr = m.group(1)
|
||||
if ":-" in expr:
|
||||
key, default = expr.split(":-", 1)
|
||||
return os.environ.get(key, default)
|
||||
return os.environ.get(expr, "")
|
||||
|
||||
raw = re.sub(r"\$\{([^}]+)\}", _expand, raw)
|
||||
return yaml.safe_load(raw)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Date utilities
|
||||
# ---------------------------------------------------------------------------
|
||||
def parse_haystack_date(date_str: str) -> datetime:
|
||||
"""Parse LongMemEval date format: '2023/05/20 (Sat) 02:21' -> datetime."""
|
||||
m = re.match(r"(\d{4}/\d{2}/\d{2})\s+\(\w+\)\s+(\d{2}:\d{2})", date_str)
|
||||
if not m:
|
||||
raise ValueError(f"Cannot parse haystack date: {date_str!r}")
|
||||
return datetime.strptime(f"{m.group(1)} {m.group(2)}", "%Y/%m/%d %H:%M")
|
||||
|
||||
|
||||
def to_iso(dt: datetime) -> str:
|
||||
"""Convert datetime to ISO-8601 string precise to seconds."""
|
||||
return dt.strftime("%Y-%m-%dT%H:%M:%S")
|
||||
|
||||
|
||||
def should_trigger_dream(prev_dt: datetime, curr_dt: datetime, _trigger_hour: int = 23) -> bool:
|
||||
"""Check if the time gap between two sessions crosses trigger_hour (e.g. 23:00)."""
|
||||
if prev_dt.date() == curr_dt.date():
|
||||
return False
|
||||
# There's at least one midnight crossing; check if trigger_hour is between them
|
||||
# Simple heuristic: if dates differ, dream should run for the previous day
|
||||
return True
|
||||
|
||||
|
||||
def sessions_sorted_by_time(item: dict) -> list[tuple[int, datetime, str, list[dict]]]:
|
||||
"""Return (original_index, parsed_datetime, session_id, messages) sorted by time."""
|
||||
entries = []
|
||||
for i, (date_str, sid, msgs) in enumerate(
|
||||
zip(item["haystack_dates"], item["haystack_session_ids"], item["haystack_sessions"]),
|
||||
):
|
||||
dt = parse_haystack_date(date_str)
|
||||
entries.append((i, dt, sid, msgs))
|
||||
# Sort by time (ascending)
|
||||
entries.sort(key=lambda x: x[1])
|
||||
return entries
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Message formatting
|
||||
# ---------------------------------------------------------------------------
|
||||
def format_messages_for_reme(messages: list[dict], session_dt: datetime) -> list[dict]:
|
||||
"""Convert LongMemEval messages to ReMe auto_memory format.
|
||||
|
||||
Adds: name, created_at (ISO seconds). All messages in a session share the
|
||||
same created_at (the session timestamp).
|
||||
"""
|
||||
formatted = []
|
||||
for msg in messages:
|
||||
role = msg["role"]
|
||||
formatted.append(
|
||||
{
|
||||
"name": role,
|
||||
"role": role,
|
||||
"content": msg["content"],
|
||||
"created_at": to_iso(session_dt),
|
||||
},
|
||||
)
|
||||
return formatted
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# LLM-as-Judge (delegated to answer_judge_step via app.run_job)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
async def judge_response_via_job(
|
||||
app,
|
||||
question: str,
|
||||
ground_truth: str,
|
||||
response: str,
|
||||
question_type: str,
|
||||
) -> dict:
|
||||
"""Use the answer_judge_step to evaluate a response against the golden answer."""
|
||||
judge_resp = await app.run_job(
|
||||
"answer_judge",
|
||||
query=question,
|
||||
agent_answer=response,
|
||||
golden_answer=ground_truth,
|
||||
question_type=question_type,
|
||||
)
|
||||
|
||||
verdict = (judge_resp.answer or "").strip().lower()
|
||||
raw_answer = (judge_resp.metadata or {}).get("raw_answer_judgement", "")
|
||||
|
||||
return {
|
||||
"verdict": verdict,
|
||||
"reason": raw_answer if verdict not in ("yes", "no") else "",
|
||||
"metric": "binary",
|
||||
"question_type": question_type,
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main evaluation pipeline
|
||||
# ---------------------------------------------------------------------------
|
||||
async def evaluate_item(item: dict, eval_config: dict, item_index: int, eval_only: bool = False) -> dict:
|
||||
"""Evaluate a single LongMemEval item end-to-end.
|
||||
|
||||
Args:
|
||||
item: The dataset item containing question, answer, sessions, etc.
|
||||
eval_config: The evaluation configuration dict.
|
||||
item_index: The index of this item in the dataset.
|
||||
eval_only: If True, skip ingestion (phases 1-3) and only run query+judge
|
||||
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"]
|
||||
dream_trigger_hour = reme_cfg.get("dream_trigger_hour", 23)
|
||||
dream_scan_days = reme_cfg.get("dream_scan_days", 2)
|
||||
dream_max_units = reme_cfg.get("dream_max_units", 5)
|
||||
|
||||
# Sort sessions by time
|
||||
sorted_sessions = sessions_sorted_by_time(item)
|
||||
|
||||
# Filter out sessions that occur after question_date (if enabled)
|
||||
filter_future = eval_config["evaluation"].get("filter_future_sessions", True)
|
||||
if filter_future and item.get("question_date"):
|
||||
question_dt = parse_haystack_date(item["question_date"])
|
||||
total_before_filter = len(sorted_sessions)
|
||||
sorted_sessions = [(i, dt, sid, msgs) for i, dt, sid, msgs in sorted_sessions if dt <= question_dt]
|
||||
if len(sorted_sessions) < total_before_filter:
|
||||
logger.info(
|
||||
f"[Item {item_index}] Filtered sessions: {total_before_filter} -> {len(sorted_sessions)} "
|
||||
f"(removed {total_before_filter - len(sorted_sessions)} future sessions "
|
||||
f"after question_date={item['question_date']})",
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"[Item %s] question_id=%s type=%s sessions=%d%s",
|
||||
item_index,
|
||||
item["question_id"],
|
||||
item["question_type"],
|
||||
len(sorted_sessions),
|
||||
" [eval_only]" if eval_only else "",
|
||||
)
|
||||
|
||||
# Use fixed workspace directory (clean it for fresh evaluation)
|
||||
workspace_root = _PROJECT_ROOT / eval_config["dataset"].get("workspace_root", _WORKSPACE_ROOT_DEFAULT)
|
||||
item_dir = workspace_root / f"item_{item_index}"
|
||||
workspace_dir = str(item_dir / ".reme")
|
||||
if eval_only:
|
||||
if not item_dir.exists() or not Path(workspace_dir).exists():
|
||||
raise FileNotFoundError(
|
||||
f"[Item {item_index}] eval_only: workspace not found at {item_dir}. "
|
||||
f"Run without --eval_only first to build the workspace.",
|
||||
)
|
||||
else:
|
||||
if item_dir.exists():
|
||||
shutil.rmtree(item_dir)
|
||||
logger.info(f"[Item {item_index}] Cleaned existing workspace: {item_dir}")
|
||||
else:
|
||||
logger.info(f"[Item {item_index}] Workspace not found, creating: {item_dir}")
|
||||
item_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Pre-initialize ReMe's loguru logger with the correct log_dir
|
||||
# (singleton — Application.__init__ will reuse this instance)
|
||||
output_cfg = eval_config.get("output", {})
|
||||
if output_cfg.get("log_to_file", False):
|
||||
reme_log_dir = os.environ.get("REME_LOG_DIR")
|
||||
if reme_log_dir:
|
||||
from reme.utils import get_logger
|
||||
|
||||
get_logger(
|
||||
log_dir=reme_log_dir,
|
||||
level=os.environ.get("REME_LOG_LEVEL", "INFO"),
|
||||
log_to_console=output_cfg.get("log_to_console", True),
|
||||
log_to_file=True,
|
||||
force_init=True,
|
||||
)
|
||||
|
||||
cfg = resolve_app_config(
|
||||
config=reme_cfg["config"],
|
||||
workspace_dir=workspace_dir,
|
||||
log_to_console=output_cfg.get("log_to_console", True),
|
||||
log_to_file=output_cfg.get("log_to_file", False),
|
||||
enable_logo=False,
|
||||
)
|
||||
|
||||
app = Application(**cfg)
|
||||
await app.start()
|
||||
|
||||
try:
|
||||
dream_dates_triggered = set()
|
||||
dream_available = True # Set to False if auto_dream job is not found
|
||||
|
||||
if not eval_only:
|
||||
# ── Phase 1: Ingest sessions ──────────────────────────────
|
||||
prev_dt = None
|
||||
|
||||
for idx, (_, session_dt, session_id, messages) in enumerate(sorted_sessions):
|
||||
# Check if dream should be triggered before this session
|
||||
if (
|
||||
dream_available
|
||||
and prev_dt is not None
|
||||
and should_trigger_dream(prev_dt, session_dt, dream_trigger_hour)
|
||||
):
|
||||
dream_date = prev_dt.strftime("%Y-%m-%d")
|
||||
if dream_date not in dream_dates_triggered:
|
||||
logger.info(f"[Item {item_index}] Triggering dream for date={dream_date}")
|
||||
try:
|
||||
dream_resp = await app.run_job(
|
||||
"auto_dream",
|
||||
date=dream_date,
|
||||
scan_days=dream_scan_days,
|
||||
max_units=dream_max_units,
|
||||
)
|
||||
logger.info(
|
||||
f"[Item {item_index}] Dream done: success={dream_resp.success} "
|
||||
f"answer={dream_resp.answer[:100] if dream_resp.answer else ''}",
|
||||
)
|
||||
except Exception as e:
|
||||
if "not found" in str(e).lower():
|
||||
dream_available = False
|
||||
logger.warning(f"[Item {item_index}] auto_dream job not found, skipping all dreams")
|
||||
else:
|
||||
logger.warning(f"[Item {item_index}] Dream failed for {dream_date}: {e}")
|
||||
dream_dates_triggered.add(dream_date)
|
||||
# Index update after dream to pick up new digest nodes
|
||||
await app.run_job("index_update")
|
||||
|
||||
# Format and ingest the session
|
||||
formatted_msgs = format_messages_for_reme(messages, session_dt)
|
||||
date_str = session_dt.strftime("%Y-%m-%d")
|
||||
|
||||
logger.info(
|
||||
f"[Item {item_index}] Ingesting session {idx+1}/{len(sorted_sessions)} "
|
||||
f"id={session_id} date={date_str} msgs={len(formatted_msgs)}",
|
||||
)
|
||||
resp = await app.run_job(
|
||||
"auto_memory",
|
||||
messages=formatted_msgs,
|
||||
session_id=session_id,
|
||||
date=date_str,
|
||||
)
|
||||
if not resp.success:
|
||||
logger.warning(
|
||||
f"[Item {item_index}] auto_memory failed for session {session_id}: {resp.answer}",
|
||||
)
|
||||
|
||||
# Manual index update after each session
|
||||
await app.run_job("index_update")
|
||||
|
||||
prev_dt = session_dt
|
||||
|
||||
# ── Phase 2: Final dream for the last day ─────────────────
|
||||
if dream_available and prev_dt is not None:
|
||||
last_dream_date = prev_dt.strftime("%Y-%m-%d")
|
||||
if last_dream_date not in dream_dates_triggered:
|
||||
logger.info(f"[Item {item_index}] Final dream for date={last_dream_date}")
|
||||
try:
|
||||
await app.run_job(
|
||||
"auto_dream",
|
||||
date=last_dream_date,
|
||||
scan_days=dream_scan_days,
|
||||
max_units=dream_max_units,
|
||||
)
|
||||
except Exception as e:
|
||||
if "not found" in str(e).lower():
|
||||
dream_available = False
|
||||
logger.warning(f"[Item {item_index}] auto_dream job not found, skipping all dreams")
|
||||
else:
|
||||
logger.warning(f"[Item {item_index}] Final dream failed: {e}")
|
||||
dream_dates_triggered.add(last_dream_date)
|
||||
# Index update after final dream
|
||||
await app.run_job("index_update")
|
||||
|
||||
# ── Phase 3: Digest update ────────────────────────────────
|
||||
await app.run_job("digest_update")
|
||||
|
||||
# ── Phase 4: Ask question via agentic_answer job (ReAct agent) ──
|
||||
question = item["question"]
|
||||
compress_session = bool(eval_config["evaluation"].get("compress_session", False))
|
||||
question_date_raw = item.get("question_date", "")
|
||||
question_dt = parse_haystack_date(question_date_raw) if question_date_raw else None
|
||||
query_time = to_iso(question_dt) if question_dt else ""
|
||||
logger.info(
|
||||
f"[Item {item_index}] Asking (agentic): {question[:80]}... query_time={query_time}",
|
||||
)
|
||||
|
||||
with (
|
||||
track_job_counts(["search"], app.context) as tool_counts,
|
||||
track_agent_token_usage(
|
||||
["bench"],
|
||||
app.context,
|
||||
) as token_usages,
|
||||
):
|
||||
query_resp = await app.run_job(
|
||||
"agentic_answer",
|
||||
query=question,
|
||||
query_time=query_time,
|
||||
compress_session=compress_session,
|
||||
)
|
||||
agentic_tool_counts = tool_counts
|
||||
agentic_token_usage = token_usages["bench"]
|
||||
agentic_response = (query_resp.answer or "").strip()
|
||||
if not agentic_response:
|
||||
agentic_response = "(no answer generated)"
|
||||
|
||||
logger.info(f"[Item {item_index}] Agentic response: {agentic_response[:200]}...")
|
||||
logger.info(f"[Item {item_index}] Agentic tool calls: {agentic_tool_counts}")
|
||||
logger.info(f"[Item {item_index}] Bench token usage: {agentic_token_usage}")
|
||||
|
||||
# ── Phase 5: Judge agentic response (via answer_judge_step) ──────────
|
||||
logger.info(f"[Item {item_index}] Judging agentic (binary, type={item['question_type']})...")
|
||||
agentic_judgment = await judge_response_via_job(
|
||||
app=app,
|
||||
question=question,
|
||||
ground_truth=item["answer"],
|
||||
response=agentic_response,
|
||||
question_type=item["question_type"],
|
||||
)
|
||||
logger.info(f"[Item {item_index}] agentic binary result: {agentic_judgment}")
|
||||
|
||||
finally:
|
||||
await app.close()
|
||||
|
||||
return {
|
||||
"question_id": item["question_id"],
|
||||
"question_type": item["question_type"],
|
||||
"question": question,
|
||||
"ground_truth": item["answer"],
|
||||
"agentic_response": agentic_response,
|
||||
"agentic_judgment": agentic_judgment,
|
||||
"agentic_tool_counts": agentic_tool_counts,
|
||||
"agentic_token_usage": agentic_token_usage,
|
||||
"sessions_ingested": len(sorted_sessions),
|
||||
"dreams_triggered": len(dream_dates_triggered),
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Worker: runs a single item in its own process with its own event loop
|
||||
# ---------------------------------------------------------------------------
|
||||
def _evaluate_item_worker(task_input: tuple) -> dict:
|
||||
"""Worker function for multiprocessing. Each process gets its own event loop."""
|
||||
item, eval_config, item_index, log_level, reme_log_level, eval_only, log_dir = task_input
|
||||
import asyncio # pylint: disable=import-outside-toplevel
|
||||
|
||||
_configure_worker(log_level, reme_log_level, log_dir=log_dir)
|
||||
|
||||
# Permanently suppress "Task exception was never retrieved" /
|
||||
# "Event loop is closed" noise from httpx AsyncClient GC cleanup.
|
||||
# These fire AFTER asyncio.run() closes the loop, during Python's
|
||||
# garbage collection of httpx connection-pool tasks — harmless.
|
||||
logging.getLogger("asyncio").setLevel(logging.CRITICAL)
|
||||
|
||||
return asyncio.run(evaluate_item(item, eval_config, item_index, eval_only=eval_only))
|
||||
|
||||
|
||||
def _indexed_worker(indexed_input: tuple) -> tuple:
|
||||
"""Module-level wrapper for imap_unordered with index tracking."""
|
||||
idx, task_input = indexed_input
|
||||
return idx, _evaluate_item_worker(task_input)
|
||||
|
||||
|
||||
def _resolve_num_workers(configured: int) -> int:
|
||||
"""Resolve num_workers: 0=auto (cpu_count-2, min 1), 1=sequential, >1=parallel."""
|
||||
if configured == 0:
|
||||
return max(1, (os.cpu_count() or 4) - 2)
|
||||
return max(1, configured)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Entry point
|
||||
# ---------------------------------------------------------------------------
|
||||
def main(
|
||||
config_path: str | None = None,
|
||||
log_level: str = "INFO",
|
||||
reme_log_level: str = "INFO",
|
||||
eval_only: bool = False,
|
||||
):
|
||||
"""Run the LongMemEval evaluation pipeline.
|
||||
|
||||
Args:
|
||||
config_path: Path to the YAML config file.
|
||||
log_level: Log level for the eval runner.
|
||||
reme_log_level: Log level for reme internal logs.
|
||||
eval_only: If True, skip ingestion and only run query+judge using
|
||||
existing workspaces.
|
||||
"""
|
||||
from multiprocessing import Pool # pylint: disable=import-outside-toplevel
|
||||
|
||||
# Load config BEFORE logging setup so log_dir is available
|
||||
eval_config = load_eval_config(config_path)
|
||||
|
||||
# Resolve per-run log directory from config
|
||||
output_cfg = eval_config.get("output", {})
|
||||
log_dir_abs = None
|
||||
if output_cfg.get("log_to_file", False):
|
||||
log_dir_raw = output_cfg.get("log_dir", "logs")
|
||||
log_prefix = output_cfg.get("log_prefix", "longmemeval")
|
||||
run_ts = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||
log_dir_abs = str(_PROJECT_ROOT / log_dir_raw / f"{log_prefix}_{run_ts}")
|
||||
|
||||
setup_logging(log_level, reme_log_level, log_dir=log_dir_abs)
|
||||
dataset_cfg = eval_config["dataset"]
|
||||
|
||||
# Load dataset
|
||||
dataset_path = _PROJECT_ROOT / dataset_cfg["path"]
|
||||
logger.info(f"Loading dataset from {dataset_path}")
|
||||
with open(dataset_path, encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
start = dataset_cfg.get("start_index", 0)
|
||||
num_items = dataset_cfg.get("num_items", 0)
|
||||
if num_items > 0:
|
||||
raw_items = data[start : start + num_items]
|
||||
else:
|
||||
raw_items = data[start:]
|
||||
|
||||
# Build item list
|
||||
items_with_idx = [(start + i, item) for i, item in enumerate(raw_items)]
|
||||
|
||||
# Filter by question_type if specified
|
||||
question_types = dataset_cfg.get("question_types") or []
|
||||
if question_types:
|
||||
before_filter = len(items_with_idx)
|
||||
items_with_idx = [(idx, item) for idx, item in items_with_idx if item.get("question_type") in question_types]
|
||||
logger.info(
|
||||
f"Filtered by question_types={question_types}: {before_filter} -> {len(items_with_idx)} items",
|
||||
)
|
||||
|
||||
# Filter by question_id if specified
|
||||
question_ids = dataset_cfg.get("question_ids") or []
|
||||
if question_ids:
|
||||
qid_set = set(question_ids)
|
||||
before_filter = len(items_with_idx)
|
||||
items_with_idx = [(idx, item) for idx, item in items_with_idx if item.get("question_id") in qid_set]
|
||||
logger.info(
|
||||
f"Filtered by question_ids ({len(qid_set)} ids): {before_filter} -> {len(items_with_idx)} items",
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"Evaluating %d item(s) starting from index %d%s",
|
||||
len(items_with_idx),
|
||||
start,
|
||||
" [eval_only: query+judge only]" if eval_only else "",
|
||||
)
|
||||
|
||||
# Resolve parallelism
|
||||
num_workers = _resolve_num_workers(eval_config["evaluation"].get("num_workers", 1))
|
||||
logger.info(f"Using {num_workers} worker(s)")
|
||||
|
||||
# Create output directory
|
||||
output_dir = _PROJECT_ROOT / output_cfg.get("dir", "benchmark/longmemeval/results")
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Create workspace root directory
|
||||
workspace_root = _PROJECT_ROOT / dataset_cfg.get("workspace_root", _WORKSPACE_ROOT_DEFAULT)
|
||||
workspace_root.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Pre-check: verify all workspaces exist in eval_only mode
|
||||
if eval_only:
|
||||
missing_items = []
|
||||
for orig_idx, _ in items_with_idx:
|
||||
item_dir = workspace_root / f"item_{orig_idx}"
|
||||
if not item_dir.exists() or not (item_dir / ".reme").exists():
|
||||
missing_items.append(orig_idx)
|
||||
if missing_items:
|
||||
preview = missing_items[:10]
|
||||
suffix = "..." if len(missing_items) > 10 else ""
|
||||
raise FileNotFoundError(
|
||||
f"eval_only: {len(missing_items)} workspace(s) not found under {workspace_root}. "
|
||||
f"Missing item indices: {preview}{suffix}. "
|
||||
f"Run without --eval_only first to build the workspaces.",
|
||||
)
|
||||
|
||||
# Build task args — include log levels, eval_only flag, and log paths (use original index for workspace lookup)
|
||||
task_args = [
|
||||
(item, eval_config, orig_idx, log_level, reme_log_level, eval_only, log_dir_abs)
|
||||
for orig_idx, item in items_with_idx
|
||||
]
|
||||
|
||||
# Progress tracking (force print regardless of log level, every 10 minutes)
|
||||
total_items = len(task_args)
|
||||
completed_count = [0] # use list for mutability in closure
|
||||
start_time = time.time()
|
||||
progress_lock = threading.Lock()
|
||||
|
||||
def _print_progress(prefix: str = "PROGRESS"):
|
||||
elapsed = time.time() - start_time
|
||||
elapsed_min = elapsed / 60
|
||||
done = completed_count[0]
|
||||
pct = 100.0 * done / total_items if total_items else 0
|
||||
eta_str = "N/A"
|
||||
if done > 0:
|
||||
eta_sec = elapsed / done * (total_items - done)
|
||||
eta_str = f"{eta_sec/60:.1f}min"
|
||||
print(
|
||||
f"[{prefix}] {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | "
|
||||
f"{done}/{total_items} ({pct:.1f}%) completed | "
|
||||
f"elapsed={elapsed_min:.1f}min | ETA={eta_str}",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
def _progress_timer():
|
||||
"""Background thread: print progress every 10 minutes."""
|
||||
while not _timer_stop.is_set():
|
||||
_timer_stop.wait(600) # 10 minutes
|
||||
if not _timer_stop.is_set():
|
||||
with progress_lock:
|
||||
_print_progress()
|
||||
|
||||
_timer_stop = threading.Event()
|
||||
timer_thread = threading.Thread(target=_progress_timer, daemon=True)
|
||||
timer_thread.start()
|
||||
|
||||
# Run evaluation
|
||||
if num_workers == 1:
|
||||
# Sequential mode
|
||||
results = []
|
||||
for task_input in task_args:
|
||||
result = _evaluate_item_worker(task_input)
|
||||
results.append(result)
|
||||
with progress_lock:
|
||||
completed_count[0] += 1
|
||||
else:
|
||||
# Parallel mode — use imap_unordered for progress tracking
|
||||
results = [None] * total_items
|
||||
indexed_args = list(enumerate(task_args))
|
||||
|
||||
with Pool(processes=num_workers) as pool:
|
||||
for idx, result in pool.imap_unordered(_indexed_worker, indexed_args):
|
||||
results[idx] = result
|
||||
with progress_lock:
|
||||
completed_count[0] += 1
|
||||
|
||||
# Stop progress timer
|
||||
_timer_stop.set()
|
||||
timer_thread.join(timeout=2)
|
||||
|
||||
# Save results
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
output_file = output_dir / f"results_{timestamp}.json"
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
json.dump(results, f, ensure_ascii=False, indent=2)
|
||||
logger.info(f"Results saved to {output_file}")
|
||||
|
||||
# Final progress
|
||||
_print_progress("FINAL")
|
||||
|
||||
_print_summary(results, start_time)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Summary printing
|
||||
# ---------------------------------------------------------------------------
|
||||
def _print_summary(results: list[dict], start_time: float) -> None:
|
||||
"""Print per-item verdicts and per-type accuracy."""
|
||||
print("\n" + "=" * 60)
|
||||
print("EVALUATION RESULTS")
|
||||
print("=" * 60)
|
||||
|
||||
def _accumulate(judgment_key):
|
||||
correct = 0
|
||||
stats: dict = {} # {question_type: {correct: int, total: int}}
|
||||
for r in results:
|
||||
qtype = r["question_type"]
|
||||
verdict = r.get(judgment_key, {}).get("verdict", "N/A")
|
||||
if qtype not in stats:
|
||||
stats[qtype] = {"correct": 0, "total": 0}
|
||||
stats[qtype]["total"] += 1
|
||||
if verdict == "yes":
|
||||
correct += 1
|
||||
stats[qtype]["correct"] += 1
|
||||
return correct, stats
|
||||
|
||||
agentic_correct, agentic_type_stats = _accumulate("agentic_judgment")
|
||||
|
||||
total = len(results)
|
||||
|
||||
# Per-item verdict rows
|
||||
for r in results:
|
||||
a_verdict = r.get("agentic_judgment", {}).get("verdict", "N/A")
|
||||
print(f" [{r['question_id']}] type={r['question_type']} agentic={a_verdict}")
|
||||
|
||||
print("\n" + "-" * 60)
|
||||
print(f" Items: {total}")
|
||||
|
||||
# Agentic stats
|
||||
print("\n ── Agentic (ReAct) ──")
|
||||
print(f" Overall accuracy: {agentic_correct}/{total} ({100*agentic_correct/total:.1f}%)")
|
||||
tool_call_totals = [sum(r.get("agentic_tool_counts", {}).values()) for r in results]
|
||||
tool_call_mean, tool_call_std = _mean_and_std(tool_call_totals)
|
||||
print(f" Tool calls/query: mean={tool_call_mean:.2f} std={tool_call_std:.2f}")
|
||||
token_usages = [r.get("agentic_token_usage", {}) for r in results]
|
||||
print(" Bench reported tokens/query:")
|
||||
for metric in _TOKEN_USAGE_METRICS:
|
||||
values = [usage[metric] for usage in token_usages if usage.get(metric) is not None]
|
||||
if values:
|
||||
mean, std = _mean_and_std(values)
|
||||
print(f" {metric}: mean={mean:.2f} std={std:.2f}")
|
||||
else:
|
||||
print(f" {metric}: unavailable")
|
||||
print(" Per-type accuracy:")
|
||||
for qtype, stats in sorted(agentic_type_stats.items()):
|
||||
acc = 100 * stats["correct"] / stats["total"] if stats["total"] else 0
|
||||
print(f" {qtype}: {stats['correct']}/{stats['total']} ({acc:.1f}%)")
|
||||
|
||||
print("=" * 60)
|
||||
total_elapsed = time.time() - start_time
|
||||
print(f"\n Total time: {total_elapsed/60:.1f} min")
|
||||
print("\n" + "=" * 60)
|
||||
print(" [DONE] EVALUATION COMPLETED SUCCESSFULLY")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
|
||||
_TOKEN_USAGE_METRICS = (
|
||||
"input_tokens",
|
||||
"output_tokens",
|
||||
"total_tokens",
|
||||
)
|
||||
|
||||
|
||||
def _mean_and_std(values: list[int]) -> tuple[float, float]:
|
||||
"""Return population mean and standard deviation for one per-query metric."""
|
||||
if not values:
|
||||
return 0.0, 0.0
|
||||
mean = sum(values) / len(values)
|
||||
return mean, (sum((value - mean) ** 2 for value in values) / len(values)) ** 0.5
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="LongMemEval evaluation runner")
|
||||
parser.add_argument("--config", type=str, default=None, help="Path to config.yaml")
|
||||
parser.add_argument(
|
||||
"--log-level",
|
||||
type=str,
|
||||
default="INFO",
|
||||
choices=["DEBUG", "INFO", "WARNING", "ERROR"],
|
||||
help="Log level for the eval runner (default: INFO)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--reme-log-level",
|
||||
type=str,
|
||||
default="INFO",
|
||||
choices=["DEBUG", "INFO", "WARNING", "ERROR"],
|
||||
help="Log level for reme internal logs — loguru (default: INFO)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-q",
|
||||
"--quiet",
|
||||
action="store_true",
|
||||
help="Shortcut for --log-level WARNING --reme-log-level WARNING",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--eval_only",
|
||||
action="store_true",
|
||||
help="Skip ingestion (phases 1-3). Reuse existing workspaces and only run query+judge.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.quiet:
|
||||
args.log_level = "WARNING"
|
||||
args.reme_log_level = "WARNING"
|
||||
|
||||
main(args.config, args.log_level, args.reme_log_level, eval_only=args.eval_only)
|
||||
14
benchmark/pibench/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# 含真实 API key,绝不入库
|
||||
env.sh
|
||||
|
||||
# 运行时产物(含对话内容,勿入库)
|
||||
logs/
|
||||
outputs/
|
||||
reme_workspace/
|
||||
nanobot_workspace/
|
||||
|
||||
# 数据符号链接(指向外部 π-Bench 仓库)
|
||||
data
|
||||
|
||||
__pycache__/
|
||||
*.pyc
|
||||
327
benchmark/pibench/README.md
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
[中文版 / Chinese version](./README_ZH.md)
|
||||
|
||||
# π-Bench Evaluation Suite
|
||||
|
||||
A glue layer that connects the **ReMe agent (with persistent memory)** to
|
||||
**π-Bench** (Proactive Personal Assistant Benchmark). This directory contains
|
||||
only the minimal code and configuration needed for the integration: the
|
||||
π-Bench framework (`src/`), evaluation data (`data/`), the AppWorld tool
|
||||
environment, and ReMe itself are all **external third-party dependencies**,
|
||||
referenced in place via symlink and environment variables and never bundled
|
||||
with this suite.
|
||||
|
||||
- π-Bench: https://github.com/Simplified-Reasoning/Pi-Bench (arXiv: 2605.14678)
|
||||
- ReMe: the root of the ReMe repository this suite lives in (recommended
|
||||
location: `ReMe/benchmark/pibench/`)
|
||||
|
||||
## 1. Architecture
|
||||
|
||||
```
|
||||
π-Bench runner (src.main --mode run)
|
||||
│ user_agent (simulated-user LLM) walks data/{persona}/episode.yaml
|
||||
│ task by task, chatting with the agent over multiple turns and judging
|
||||
│ hidden intents (PROC) during the run phase
|
||||
▼
|
||||
test server (π-Bench scripts/test_server.py, HTTP long-polling)
|
||||
▲ /send │ /poll
|
||||
│ ▼
|
||||
bridge_reme.py ──────────────► ReMe Application (embedded as a library)
|
||||
│ ├─ agent_wrapper: agent under test (AgentScope)
|
||||
│ ├─ jobs: search / auto_memory / daily_write
|
||||
│ └─ workspace: reme_workspace/{persona}/
|
||||
│ (isolated persistent memory per persona)
|
||||
└──── MCP ────► AppWorld MCP ────► AppWorld APIs (tool/app environment)
|
||||
|
||||
π-Bench runner (src.main --mode eval)
|
||||
judger (judge LLM) reads the traces and scores each checklist item (COMP)
|
||||
```
|
||||
|
||||
Key points:
|
||||
- The bridge runs on **ReMe's own venv python** and uses ReMe as a library
|
||||
(`resolve_app_config` + `Application`); **no ReMe source modification** is
|
||||
required.
|
||||
- Every incoming user message automatically triggers a ReMe memory `search`
|
||||
and injects the matched memories (tuning knobs in §8); on task end (reset)
|
||||
the session is distilled into daily notes by `auto_memory`.
|
||||
- Tool calls executed by the agent (AppWorld MCP + ReMe job tools) are
|
||||
captured per turn into the trace as `tool_steps`, so π-Bench
|
||||
`tools_evaluation_path` scripts can score tool behavior (§7).
|
||||
- π-Bench's `data/`, `src/` and AppWorld are not part of this suite; install
|
||||
π-Bench first (§3.1).
|
||||
|
||||
## 2. Directory layout
|
||||
|
||||
```
|
||||
pibench/
|
||||
├── README.md / README_ZH.md # this document (English / Chinese)
|
||||
├── env.sh.example # environment template (copy to env.sh, fill TODOs)
|
||||
├── bridge_reme.py # ReMe ↔ test server bridge (memory inject/save,
|
||||
│ # profile injection, tool-trace capture)
|
||||
├── run_persona.sh # full pipeline for ONE persona (5 services + run + eval)
|
||||
├── run_all.sh # batch over 5 personas (fresh/resume, default parallel=2)
|
||||
├── resume.py # checkpoint resume: completion detection + surgical
|
||||
│ # cleanup of interrupted tasks' residual memory
|
||||
├── fix_trace_logs.py # run outputs → ~/.nanobot/trace_logs conversion,
|
||||
│ # merging tool sidecars into turn files (pre-eval)
|
||||
├── .gitignore # excludes env.sh and all runtime artifacts
|
||||
└── config/
|
||||
├── models/reme.yaml # runner model config (model_id=reme)
|
||||
└── bench/evaluation/trace_history.yaml # trace render policy (shipped with
|
||||
# the suite; passed via --history-config-path)
|
||||
```
|
||||
|
||||
Generated at runtime (all git-ignored): `data` (symlink), `logs/`, `outputs/`,
|
||||
`reme_workspace/`, `nanobot_workspace/`.
|
||||
|
||||
## 3. Prerequisites (third-party, install first)
|
||||
|
||||
### 3.1 π-Bench repository (with AppWorld)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Simplified-Reasoning/Pi-Bench.git <pi-bench-dir>
|
||||
cd <pi-bench-dir>
|
||||
python3.11 -m venv .venv # scripts expect exactly this venv name
|
||||
source .venv/bin/activate
|
||||
pip install -e . # pibench runner (src.main)
|
||||
bash scripts/setup_appworld.sh # install AppWorld and download its data (large)
|
||||
```
|
||||
|
||||
Post-install sanity checks:
|
||||
```bash
|
||||
ls data/ # should contain researcher marketer pharmacist law_trainee Financier
|
||||
.venv/bin/python -c "import src" && echo OK
|
||||
.venv/bin/appworld --help >/dev/null && echo OK
|
||||
```
|
||||
|
||||
### 3.2 ReMe repository
|
||||
|
||||
```bash
|
||||
cd <reme-dir> # ReMe repository root (contains the reme/ package)
|
||||
python3.11 -m venv .venv # scripts expect exactly this venv name
|
||||
source .venv/bin/activate
|
||||
pip install -e . # or ReMe's own install flow; `import reme` must work
|
||||
```
|
||||
|
||||
Sanity check: `.venv/bin/python -c "import reme; print('ok')"`
|
||||
|
||||
## 4. Install this suite (step by step)
|
||||
|
||||
1. **Place the suite** (recommended inside the ReMe repo so `REME_DIR` is
|
||||
inferred automatically):
|
||||
```bash
|
||||
cp -r pibench <reme-dir>/benchmark/pibench
|
||||
cd <reme-dir>/benchmark/pibench
|
||||
```
|
||||
If placed elsewhere, set `REME_DIR` explicitly in env.sh later.
|
||||
|
||||
2. **Create the environment file and fill in the custom parameters**:
|
||||
```bash
|
||||
cp env.sh.example env.sh
|
||||
```
|
||||
Open `env.sh`; required items (marked TODO):
|
||||
| Variable | Description |
|
||||
|---|---|
|
||||
| `PI_BENCH_ROOT` | π-Bench repo root (contains `src/` `data/` `.venv` `third_party/appworld`) |
|
||||
| `USER_API_KEY` | API key of the simulated-user LLM (run phase, hidden-intent judging) |
|
||||
| `JUDGER_API_KEY` | API key of the judger LLM (eval phase, checklist scoring) |
|
||||
| `BRAVE_SEARCH_API_KEY` | optional; for the agent's web_search tool, `dummy` when unused |
|
||||
|
||||
Optional tuning: `REME_MODEL_NAME` (base model of the agent under test),
|
||||
`REME_DIR`, `REME_LLM_BASE_URL` (default: DashScope OpenAI-compatible
|
||||
endpoint).
|
||||
|
||||
3. **Link the evaluation data** (referenced in place, never copied):
|
||||
```bash
|
||||
ln -s "$PI_BENCH_ROOT/data" data
|
||||
```
|
||||
|
||||
4. **(Optional) adjust model config** `config/models/reme.yaml`:
|
||||
- `user_agent.model` / `judger.model`: model names for the simulated user
|
||||
and the judger (literal values; π-Bench only expands `${ENV}` in
|
||||
base_url/api_key).
|
||||
- `run.turn_timeout`, `max_tool_iterations`, etc. as needed.
|
||||
|
||||
5. **Smoke check** (does not start the evaluation):
|
||||
```bash
|
||||
bash -n run_all.sh && bash -n run_persona.sh
|
||||
source env.sh && "$REME_DIR/.venv/bin/python" -c "import reme; print('reme ok')"
|
||||
```
|
||||
|
||||
## 5. Run the evaluation
|
||||
|
||||
> ⚠️ For long runs use `screen`, **not nohup** (nohup loses the permission
|
||||
> context in sandboxed/restricted environments and breaks child processes).
|
||||
|
||||
```bash
|
||||
# Full official run: wipe ALL personas' memory/outputs/traces first (default
|
||||
# fresh mode, parallel=2)
|
||||
mkdir -p logs # on a fresh deployment logs/ does not exist yet
|
||||
screen -dmS pibench_suite bash -c "cd $(pwd) && bash run_all.sh > logs/run_all_master.log 2>&1"
|
||||
|
||||
# Checkpoint continuation (after an interruption; no wipe, completed tasks skipped)
|
||||
bash run_all.sh --resume
|
||||
|
||||
# Other usages
|
||||
bash run_all.sh --parallel 1 # sequential
|
||||
bash run_all.sh --resume --skip-eval # run phase only
|
||||
bash run_persona.sh researcher # single persona (default --resume semantics)
|
||||
bash run_persona.sh researcher --fresh
|
||||
```
|
||||
|
||||
Time reference: 5 personas × 20 tasks, parallel=2, fresh full run ≈ 12–14 hours.
|
||||
|
||||
`run_all.sh` exits non-zero when any persona fails, so upstream automation
|
||||
cannot mistake a partially failed suite run for a success.
|
||||
|
||||
## 6. Port allocation (parallel personas never collide)
|
||||
|
||||
| persona | AppWorld API | AppWorld MCP | Test Server | ReMe internal service |
|
||||
|-------------|------|-------|------|-------|
|
||||
| marketer | 9001 | 10001 | 9998 | 18766 |
|
||||
| law_trainee | 9002 | 10002 | 9997 | 18767 |
|
||||
| pharmacist | 9003 | 10003 | 9996 | 18768 |
|
||||
| researcher | 9004 | 10004 | 9995 | 18765 |
|
||||
| Financier | 9005 | 10005 | 9994 | 18769 |
|
||||
|
||||
## 7. Outputs and scores
|
||||
|
||||
- **Results**: `outputs/reme/{persona}/{task}/eval/results/*_result.json`
|
||||
- `overall_average_score`: checklist completeness (COMP; the judger scores
|
||||
each criterion YES/NO, weighted across dependency groups)
|
||||
- `overall_proactiveness_average_score`: proactiveness (PROC; the
|
||||
user_agent judges hidden-intent coverage during the run phase; each task
|
||||
file also carries the global average)
|
||||
- **Traces**: `~/.nanobot/trace_logs/reme/{persona}/{task}/...` (the scoring
|
||||
input of the eval phase)
|
||||
- **Logs**: `logs/` (`suite_<persona>.log` per persona; `bridge_*`,
|
||||
`runner_run/eval_*`, `appworld_*`, `test_server_*` per service)
|
||||
- **Memory store**: `reme_workspace/{persona}/` (daily/digest notes, raw
|
||||
session dialogs, BM25 index, etc.; persistent across runs, wiped only in
|
||||
fresh mode)
|
||||
|
||||
Score summary:
|
||||
```bash
|
||||
grep -h "overall_average_score\|overall_proactiveness" \
|
||||
outputs/reme/*/*/eval/results/*_result.json | head
|
||||
```
|
||||
|
||||
### Tool-trace capture (tools_evaluation support)
|
||||
|
||||
Some tasks define `objectives.tools_evaluation_path`: Python scripts that
|
||||
score tool behavior (e.g. "the temporary Todoist board was created and
|
||||
removed"). They need the executed tool calls in the trace. The pipeline:
|
||||
|
||||
1. During `reply()`, the bridge reads the persisted AgentScope session state
|
||||
after each turn and extracts the new `tool_call` / `tool_result` blocks
|
||||
(tool name, arguments, result).
|
||||
2. Records are appended to
|
||||
`outputs/reme/{persona}/{task}/history/{ts}-tools.jsonl`, tagged with the
|
||||
turn number; AgentScope MCP names (`mcp__AppWorld__<tool>`) are normalized
|
||||
to the π-Bench convention (`mcp_appworld_<tool>`).
|
||||
3. `fix_trace_logs.py` pairs each `{ts}-messages.jsonl` run with the
|
||||
temporally closest tools sidecar and merges the records into the generated
|
||||
`turn_N.json` files under the `tool_steps` key — one of the two
|
||||
tool-history formats understood by π-Bench's `collect_tool_history()`.
|
||||
4. The eval phase then feeds `tool_steps` to both the tools_evaluation
|
||||
scripts and the rendered `<tool_trace_extracts>` seen by the judger.
|
||||
|
||||
## 8. Memory mechanism (core design of this suite)
|
||||
|
||||
- **Persona isolation**: each persona has its own workspace
|
||||
(`reme_workspace/{persona}/`); the bridge takes an exclusive
|
||||
`.bridge.lock` on it at startup, so two bridges can never share one memory
|
||||
store, and one persona's memory search can never reach another's memories.
|
||||
- **Writes**: on task end (runner sends reset), the session is distilled by
|
||||
the `auto_memory` job into daily notes and indexed by the background
|
||||
watcher (BM25). Saves are non-blocking background tasks; the first message
|
||||
of a new session waits for in-flight writes before searching.
|
||||
- **Reads**: on every incoming user message the bridge runs one `search` and
|
||||
injects matched memories (`[Relevant memories from previous sessions]`
|
||||
prefix); without matches the message passes through unchanged. Retrieval
|
||||
tuning (bridge CLI flags, adjustable in run_persona.sh):
|
||||
- `--search-limit 3`: at most 3 memory chunks injected per message;
|
||||
- `--search-min-score 2.0`: weak BM25 hits are filtered out;
|
||||
- `tool_context_id` rotates per task: chunks already injected within the
|
||||
same task are not re-injected (ReMe's seen-chunk dedup, 24h TTL); normal
|
||||
recall resumes after task boundaries.
|
||||
- **No self-leakage**: the in-progress session is not in the store yet
|
||||
(saves happen on reset), so a task can never retrieve its own unfinished
|
||||
content.
|
||||
- The agent also holds `search`/`daily_write` tools and can retrieve/record
|
||||
proactively.
|
||||
- **System prompt**: `bridge_reme.py:build_system_prompt()` embeds the
|
||||
HIDDEN-NEEDS protocol (proactiveness-oriented) and injects the persona
|
||||
profile from `data/{persona}/profile.yaml` into every turn's system prompt.
|
||||
|
||||
## 9. Checkpoint resume and memory-cleanup semantics
|
||||
|
||||
- **Completion detection** (resume.py): scans
|
||||
`outputs/reme/{persona}/**/history/*-log.jsonl` and
|
||||
`outputs/reme/{persona}/run/*-log.jsonl` for
|
||||
`Task finished task_id=X status=Y`. The status with the **newest event
|
||||
timestamp** wins per task (record `timestamp`, falling back to
|
||||
`timestamp_iso`, then to the timestamp embedded in the log file name) —
|
||||
file category and read order alone can never override a newer record, so an
|
||||
old run-level SUCCESS cannot mask a newer per-task ERROR. `SUCCESS /
|
||||
MAX_TURNS / TIMEOUT` count as completed; `ERROR` and never-started tasks
|
||||
are re-run (passed to the runner as repeated `--task-id` flags in episode
|
||||
order).
|
||||
- **Answer-leak prevention**: an interrupted task may already have been
|
||||
distilled into daily notes during graceful shutdown; re-running it with
|
||||
that memory injected would inflate scores. Before resuming,
|
||||
`resume.py cleanup` therefore removes residual memory **only for tasks
|
||||
about to be re-run** (daily/digest notes, session/dialog, mem_session;
|
||||
matched via `session_id = pibench_{task}_*`). Completed tasks' memories are
|
||||
never touched. Daily index files are refreshed **only for the dates that
|
||||
lost notes**, by full workspace-relative wikilink path — and when the ReMe
|
||||
package is importable, the refresh reuses ReMe's own daily-index rebuild
|
||||
logic (`refresh_day_index`), so same-named notes on other dates are never
|
||||
modified.
|
||||
- **fresh vs resume are mutually exclusive**: a full memory wipe belongs to
|
||||
fresh mode only (`run_all.sh` default, executed before any service starts);
|
||||
resume never wipes.
|
||||
|
||||
## 10. Customization entry points
|
||||
|
||||
| Goal | Location |
|
||||
|---|---|
|
||||
| Base model of the agent under test | `REME_MODEL_NAME` in `env.sh` |
|
||||
| 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`) |
|
||||
| Turn timeout / tool iteration cap | `config/models/reme.yaml` `run.turn_timeout`, `model.max_tool_iterations` |
|
||||
|
||||
## 11. Troubleshooting
|
||||
|
||||
- **Port already in use**: the scripts auto-kill residual processes on the
|
||||
four port groups above; if another suite (e.g. a different π-Bench
|
||||
experiment) holds them, stop it first or change the port table in
|
||||
run_persona.sh.
|
||||
- **Bridge exits immediately with workspace locked**: another bridge already
|
||||
holds the same workspace; make sure each persona uses its own
|
||||
`--workspace-dir` (the scripts allocate one per persona).
|
||||
- **Runner reports `${USER_API_KEY} ... empty`**: env.sh is unfilled or not
|
||||
sourced; run_persona.sh sources env.sh automatically — when running the
|
||||
runner manually, `source env.sh` first.
|
||||
- **`Cannot import 'reme'`**: the bridge must run with
|
||||
`${REME_DIR}/.venv/bin/python` (run_persona.sh already does); otherwise
|
||||
check that `REME_DIR` points at the ReMe repository root.
|
||||
- **AppWorld fails to start**: run `bash scripts/setup_appworld.sh` in the
|
||||
π-Bench repo first (downloads data); inspect
|
||||
`logs/appworld_*_<persona>.log`.
|
||||
- **trace_history.yaml not found**: the runner needs
|
||||
`config/bench/evaluation/trace_history.yaml`; this suite ships the file and
|
||||
passes it explicitly via `--history-config-path`, and run_persona.sh fails
|
||||
fast with a clear error if it is missing. Always launch run_persona.sh /
|
||||
run_all.sh from the suite directory.
|
||||
|
||||
## 12. Privacy and security
|
||||
|
||||
- The suite code and config templates contain **no real API keys, user names
|
||||
or absolute paths**; real keys live only in your local `env.sh`
|
||||
(git-ignored).
|
||||
- `logs/`, `outputs/`, `reme_workspace/` and `nanobot_workspace/` contain
|
||||
full conversations and model outputs; never commit or share them.
|
||||
- The `data` symlink points at the official π-Bench evaluation data; respect
|
||||
its data license terms.
|
||||
284
benchmark/pibench/README_ZH.md
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
# π-Bench 评测说明
|
||||
|
||||
[English version](./README.md)
|
||||
|
||||
将 **ReMe agent(带持久记忆)** 接入 **π-Bench**(Proactive Personal Assistant
|
||||
Benchmark)的胶水层评测套件。只含对接所需的最小代码与配置;π-Bench 框架
|
||||
(`src/`)、评测数据(`data/`)、AppWorld 工具环境、ReMe 本体均为**外部第三方
|
||||
依赖**,通过符号链接与环境变量原位引用,不随本套件分发。
|
||||
|
||||
- π-Bench: https://github.com/Simplified-Reasoning/Pi-Bench (arXiv: 2605.14678)
|
||||
- ReMe: 你所在 ReMe 仓库的根目录(本套件推荐放在 `ReMe/benchmark/pibench/`)
|
||||
|
||||
## 1. 架构总览
|
||||
|
||||
```
|
||||
π-Bench runner (src.main --mode run)
|
||||
│ user_agent(模拟用户 LLM)按 data/{persona}/episode.yaml 顺序
|
||||
│ 逐任务、多轮地与 agent 对话,并在 run 阶段判定隐藏意图(PROC)
|
||||
▼
|
||||
test server (π-Bench scripts/test_server.py, HTTP 长轮询)
|
||||
▲ /send │ /poll
|
||||
│ ▼
|
||||
bridge_reme.py ──────────────► ReMe Application(以库方式内嵌启动)
|
||||
│ ├─ agent_wrapper: 被测 agent(AgentScope)
|
||||
│ ├─ jobs: search / auto_memory / daily_write
|
||||
│ └─ workspace: reme_workspace/{persona}/
|
||||
│ (每 persona 独立持久记忆库,互不可见)
|
||||
└──── MCP ────► AppWorld MCP ────► AppWorld API(工具/应用环境)
|
||||
|
||||
π-Bench runner (src.main --mode eval)
|
||||
judger(裁判 LLM)读取 trace,按 checklist 逐条 YES/NO 打分(COMP)
|
||||
```
|
||||
|
||||
要点:
|
||||
- bridge 用 **ReMe 自己的 venv python** 运行,把 ReMe 当库用(`resolve_app_config`
|
||||
+ `Application`),**ReMe 源码零改动**。
|
||||
- 每条用户消息都会自动触发一次 ReMe memory `search` 并把命中记忆注入当前消息
|
||||
(参数见 §8);任务结束(reset)时会话被 `auto_memory` 提炼为 daily 笔记落盘。
|
||||
- agent 执行的每一轮工具调用(AppWorld MCP + ReMe job 工具)都会被采集并以
|
||||
`tool_steps` 形式写入 trace,供 π-Bench 的 `tools_evaluation_path` 脚本
|
||||
对工具行为评分(§7)。
|
||||
- π-Bench 的 `data/`、`src/`、AppWorld 均不属于本套件,需先装好 π-Bench(§3.1)。
|
||||
|
||||
## 2. 目录结构
|
||||
|
||||
```
|
||||
pibench/
|
||||
├── README.md / README_ZH.md # 本文档(英文 / 中文)
|
||||
├── env.sh.example # 环境配置模板(复制为 env.sh 后填写 TODO 项)
|
||||
├── bridge_reme.py # ReMe ↔ test server 桥接(记忆注入/保存、
|
||||
│ # profile 注入、工具调用轨迹采集)
|
||||
├── run_persona.sh # 单 persona 全流程(5 个服务 + run + eval)
|
||||
├── run_all.sh # 5 个 persona 批跑(fresh/resume,默认 2 并行)
|
||||
├── resume.py # 断点续跑:完成判定 + 中断任务残留记忆的外科清理
|
||||
├── fix_trace_logs.py # run 输出 → ~/.nanobot/trace_logs 转换,
|
||||
│ # 并把工具轨迹合并进 turn 文件(eval 前置)
|
||||
├── .gitignore # 排除 env.sh 与全部运行产物
|
||||
└── config/
|
||||
├── models/reme.yaml # runner 模型配置(model_id=reme)
|
||||
└── bench/evaluation/trace_history.yaml # trace 渲染策略(随套件提供,
|
||||
# 经 --history-config-path 显式传入)
|
||||
```
|
||||
|
||||
运行时自动生成(均被 .gitignore 排除):`data`(符号链接)、`logs/`、
|
||||
`outputs/`、`reme_workspace/`、`nanobot_workspace/`。
|
||||
|
||||
## 3. 前置依赖(第三方,先装好)
|
||||
|
||||
### 3.1 π-Bench 仓库(含 AppWorld)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Simplified-Reasoning/Pi-Bench.git <pi-bench-dir>
|
||||
cd <pi-bench-dir>
|
||||
python3.11 -m venv .venv # 脚本约定使用 .venv 这个目录名
|
||||
source .venv/bin/activate
|
||||
pip install -e . # pibench runner(src.main)
|
||||
bash scripts/setup_appworld.sh # 安装 AppWorld 并下载其数据(体积较大,需网络)
|
||||
```
|
||||
|
||||
装完自检:
|
||||
```bash
|
||||
ls data/ # 应含 researcher marketer pharmacist law_trainee Financier
|
||||
.venv/bin/python -c "import src" && echo OK
|
||||
.venv/bin/appworld --help >/dev/null && echo OK
|
||||
```
|
||||
|
||||
### 3.2 ReMe 仓库
|
||||
|
||||
```bash
|
||||
cd <reme-dir> # ReMe 仓库根目录(含 reme/ 包)
|
||||
python3.11 -m venv .venv # 脚本约定使用 .venv 这个目录名
|
||||
source .venv/bin/activate
|
||||
pip install -e . # 或按 ReMe 自身安装方式,保证 `import reme` 可用
|
||||
```
|
||||
|
||||
自检:`.venv/bin/python -c "import reme; print('ok')"`
|
||||
|
||||
## 4. 安装本套件(逐步)
|
||||
|
||||
1. **放置套件**(推荐放进 ReMe 仓库,`REME_DIR` 可自动推断):
|
||||
```bash
|
||||
cp -r pibench <reme-dir>/benchmark/pibench
|
||||
cd <reme-dir>/benchmark/pibench
|
||||
```
|
||||
若放在其他位置,稍后在 env.sh 中显式设置 `REME_DIR`。
|
||||
|
||||
2. **创建环境文件并填写自定义参数**:
|
||||
```bash
|
||||
cp env.sh.example env.sh
|
||||
```
|
||||
打开 `env.sh`,必填项(标 TODO 的):
|
||||
| 变量 | 说明 |
|
||||
|---|---|
|
||||
| `PI_BENCH_ROOT` | π-Bench 仓库根目录(含 `src/` `data/` `.venv` `third_party/appworld`) |
|
||||
| `USER_API_KEY` | 模拟用户 LLM 的 API key(run 阶段判定隐藏意图) |
|
||||
| `JUDGER_API_KEY` | 裁判 LLM 的 API key(eval 阶段 checklist 打分) |
|
||||
| `BRAVE_SEARCH_API_KEY` | 可选;agent 的 web_search 工具用,不用填 `dummy` |
|
||||
|
||||
可选调整:`REME_MODEL_NAME`(被测 agent 基模)、`REME_DIR`、
|
||||
`REME_LLM_BASE_URL`(默认 DashScope OpenAI 兼容端点)。
|
||||
|
||||
3. **链接评测数据**(π-Bench 数据原位引用,不复制):
|
||||
```bash
|
||||
ln -s "$PI_BENCH_ROOT/data" data
|
||||
```
|
||||
|
||||
4. **(可选)调整模型配置** `config/models/reme.yaml`:
|
||||
- `user_agent.model` / `judger.model`:模拟用户与裁判的模型名(字面量,
|
||||
π-Bench 仅对 base_url/api_key 做 `${ENV}` 展开)。
|
||||
- `run.turn_timeout`、`max_tool_iterations` 等按需。
|
||||
|
||||
5. **冒烟自检**(不启动评测):
|
||||
```bash
|
||||
bash -n run_all.sh && bash -n run_persona.sh
|
||||
source env.sh && "$REME_DIR/.venv/bin/python" -c "import reme; print('reme ok')"
|
||||
```
|
||||
|
||||
## 5. 运行评测
|
||||
|
||||
> ⚠️ 长时间运行请放进 `screen`,**不要用 nohup**(nohup 在沙箱/受限环境下
|
||||
> 会丢失权限上下文导致子进程异常)。
|
||||
|
||||
```bash
|
||||
# 完整正式评测:先清空全部 persona 的记忆/输出/trace,再从头跑(默认 fresh,2 并行)
|
||||
mkdir -p logs # 全新部署时 logs/ 尚不存在,先建再重定向
|
||||
screen -dmS pibench_suite bash -c "cd $(pwd) && bash run_all.sh > logs/run_all_master.log 2>&1"
|
||||
|
||||
# 断点续跑(中断后继续;不清记忆,跳过已完成任务)
|
||||
bash run_all.sh --resume
|
||||
|
||||
# 其他用法
|
||||
bash run_all.sh --parallel 1 # 串行
|
||||
bash run_all.sh --resume --skip-eval # 只跑 run 阶段
|
||||
bash run_persona.sh researcher # 单 persona(默认 --resume 语义)
|
||||
bash run_persona.sh researcher --fresh
|
||||
```
|
||||
|
||||
耗时参考:5 persona × 20 任务、2 并行,fresh 全量约 12–14 小时。
|
||||
|
||||
任一 persona 失败时 `run_all.sh` 以非零状态退出,上层自动化不会把部分失败
|
||||
的评测误判为成功。
|
||||
|
||||
## 6. 端口分配(多 persona 并行互不冲突)
|
||||
|
||||
| persona | AppWorld API | AppWorld MCP | Test Server | ReMe 内部服务 |
|
||||
|-------------|------|-------|------|-------|
|
||||
| marketer | 9001 | 10001 | 9998 | 18766 |
|
||||
| law_trainee | 9002 | 10002 | 9997 | 18767 |
|
||||
| pharmacist | 9003 | 10003 | 9996 | 18768 |
|
||||
| researcher | 9004 | 10004 | 9995 | 18765 |
|
||||
| Financier | 9005 | 10005 | 9994 | 18769 |
|
||||
|
||||
## 7. 输出与分数
|
||||
|
||||
- **结果**:`outputs/reme/{persona}/{task}/eval/results/*_result.json`
|
||||
- `overall_average_score`:checklist 完整度(COMP,judger 逐条 YES/NO 按依赖组加权)
|
||||
- `overall_proactiveness_average_score`:主动性(PROC,run 阶段 user_agent
|
||||
判定隐藏意图覆盖率;每个任务文件同时携带全局均值)
|
||||
- **trace**:`~/.nanobot/trace_logs/reme/{persona}/{task}/...`(eval 的判分输入)
|
||||
- **日志**:`logs/`(`suite_<persona>.log` 为每 persona 总日志,`bridge_*`、
|
||||
`runner_run/eval_*`、`appworld_*`、`test_server_*` 分服务)
|
||||
- **记忆库**:`reme_workspace/{persona}/`(daily/digest 笔记、session 原始对话、
|
||||
BM25 索引等;跨运行持久,fresh 才清空)
|
||||
|
||||
查看汇总:
|
||||
```bash
|
||||
grep -h "overall_average_score\|overall_proactiveness" \
|
||||
outputs/reme/*/*/eval/results/*_result.json | head
|
||||
```
|
||||
|
||||
### 工具轨迹采集(tools_evaluation 支持)
|
||||
|
||||
部分任务定义了 `objectives.tools_evaluation_path`:用 Python 脚本对工具行为
|
||||
打分(例如"临时 Todoist 看板已创建并被删除")。这些脚本需要 trace 里有真实
|
||||
的工具调用记录。采集链路:
|
||||
|
||||
1. 每轮 `reply()` 之后,bridge 读取 AgentScope 落盘的会话状态,提取本轮新增
|
||||
的 `tool_call` / `tool_result` 块(工具名、参数、结果)。
|
||||
2. 记录按 turn 编号追加写入
|
||||
`outputs/reme/{persona}/{task}/history/{ts}-tools.jsonl`;AgentScope 的
|
||||
MCP 工具名(`mcp__AppWorld__<tool>`)会规范化为 π-Bench 约定
|
||||
(`mcp_appworld_<tool>`)。
|
||||
3. `fix_trace_logs.py` 将每个 `{ts}-messages.jsonl` 运行与时间上最接近的
|
||||
tools 旁路文件配对,把记录合并进生成的 `turn_N.json` 的 `tool_steps`
|
||||
字段——这是 π-Bench `collect_tool_history()` 支持的两种工具轨迹格式之一。
|
||||
4. eval 阶段 `tool_steps` 既提供给 tools_evaluation 脚本,也会被渲染为
|
||||
judger 可见的 `<tool_trace_extracts>`。
|
||||
|
||||
## 8. 记忆机制(本套件的核心设计)
|
||||
|
||||
- **persona 隔离**:每个 persona 独立 workspace(`reme_workspace/{persona}/`),
|
||||
bridge 启动时对 workspace 加 `.bridge.lock` 排他锁,两个 bridge 不可能共用
|
||||
同一记忆库;一个 persona 的 memory search 永远接触不到其他 persona 的记忆。
|
||||
- **写入**:任务结束(runner 发送 reset)时,会话经 `auto_memory` job 提炼为
|
||||
daily 笔记落盘,后台 watcher 建 BM25 索引。保存为非阻塞后台任务,
|
||||
新会话首条消息会先等待在途写入完成再检索。
|
||||
- **读取**:bridge 每收到一条用户消息自动 `search` 一次并注入命中记忆
|
||||
(`[Relevant memories from previous sessions]` 前缀),无命中则原样透传。
|
||||
检索参数(bridge 命令行,可在 run_persona.sh 中调整):
|
||||
- `--search-limit 3`:每条消息最多注入 3 个记忆块;
|
||||
- `--search-min-score 2.0`:过滤弱 BM25 命中;
|
||||
- `tool_context_id` 按任务轮换:同一任务内已注入的记忆块不重复注入
|
||||
(ReMe 自带 seen-chunk 去重,24h TTL),任务边界后恢复正常召回。
|
||||
- **无自泄漏**:进行中的会话尚未入库(save 发生在 reset),任务不会检索到
|
||||
自己未完成的内容。
|
||||
- agent 同时持有 `search`/`daily_write` 工具,可主动检索/记录。
|
||||
- **system prompt**:`bridge_reme.py:build_system_prompt()` 内置
|
||||
HIDDEN-NEEDS 协议(面向 proactiveness),并把 `data/{persona}/profile.yaml`
|
||||
的 persona profile 注入每轮 system prompt。
|
||||
|
||||
## 9. 断点续跑与记忆清理语义
|
||||
|
||||
- **完成判定**(resume.py):扫描 `outputs/reme/{persona}/**/history/*-log.jsonl`
|
||||
与 `outputs/reme/{persona}/run/*-log.jsonl` 中的
|
||||
`Task finished task_id=X status=Y`。每个任务以**事件时间最新**的记录为准
|
||||
(优先取记录的 `timestamp`,回退 `timestamp_iso`,再回退日志文件名中的
|
||||
时间戳)——文件类别与读取顺序本身不能覆盖更新的记录,因此旧的 run 级
|
||||
SUCCESS 不会掩盖更新的 per-task ERROR。`SUCCESS/MAX_TURNS/TIMEOUT` 记为
|
||||
完成,`ERROR`/未开始的任务重跑(按 episode 顺序以 `--task-id` 传给 runner)。
|
||||
- **防答案泄漏**:被中断的任务可能已在优雅退出时提炼成 daily 笔记,直接重跑会
|
||||
把答案注入、抬高分数。因此 resume 启动前 `resume.py cleanup` **只删除待重跑
|
||||
任务**的残留记忆(daily/digest 笔记、session/dialog、mem_session,按
|
||||
`session_id = pibench_{task}_*` 匹配),已完成任务的记忆一律不动。daily
|
||||
索引**只刷新实际发生删除的日期**,按完整的 workspace 相对 wikilink 路径
|
||||
匹配;当 ReMe 包可导入时,刷新直接复用 ReMe 自带的 daily 索引重建逻辑
|
||||
(`refresh_day_index`),不会误改其他日期下的同名笔记条目。
|
||||
- **fresh vs resume 互斥**:全量清记忆只属于 fresh 模式(`run_all.sh` 默认,
|
||||
在任何服务启动前执行);resume 永不清全量。
|
||||
|
||||
## 10. 自定义与调优入口
|
||||
|
||||
| 目标 | 位置 |
|
||||
|---|---|
|
||||
| 被测 agent 基模 | `env.sh` 的 `REME_MODEL_NAME` |
|
||||
| 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`) |
|
||||
| 轮超时/工具迭代上限 | `config/models/reme.yaml` `run.turn_timeout`、`model.max_tool_iterations` |
|
||||
|
||||
## 11. 故障排查
|
||||
|
||||
- **端口被占用**:脚本会自动 kill 上述 4 组端口上的残留进程;若与其他套件
|
||||
(如别的 π-Bench 实验)冲突,请先停掉对方或改 run_persona.sh 的端口表。
|
||||
- **bridge 启动即退出,提示 workspace locked**:另一个 bridge 正占用同一
|
||||
workspace;确认每个 persona 用各自的 `--workspace-dir`(脚本已按 persona 分配)。
|
||||
- **runner 报 `${USER_API_KEY} ... empty`**:env.sh 未填写或未生效;
|
||||
run_persona.sh 会自动 source env.sh,手动运行 runner 时请先 `source env.sh`。
|
||||
- **`Cannot import 'reme'`**:bridge 必须用 `${REME_DIR}/.venv/bin/python` 运行
|
||||
(run_persona.sh 已如此),或检查 `REME_DIR` 是否指向 ReMe 仓库根目录。
|
||||
- **AppWorld 启动失败**:先在 π-Bench 仓库执行 `bash scripts/setup_appworld.sh`
|
||||
下载数据;查看 `logs/appworld_*_<persona>.log`。
|
||||
- **trace_history.yaml 找不到**:runner 需要
|
||||
`config/bench/evaluation/trace_history.yaml`;本套件已随附该文件并通过
|
||||
`--history-config-path` 显式传入,run_persona.sh 启动前会做存在性检查,
|
||||
缺失时立即报出清晰错误。请始终从套件目录启动 run_persona.sh / run_all.sh。
|
||||
|
||||
## 12. 隐私与安全
|
||||
|
||||
- 套件代码与配置模板中**不含任何真实 API key、用户名或绝对路径**;
|
||||
真实 key 只存在于你本地的 `env.sh`(已被 .gitignore 排除)。
|
||||
- `logs/`、`outputs/`、`reme_workspace/`、`nanobot_workspace/` 含完整对话内容
|
||||
与模型输出,请勿提交仓库或外传。
|
||||
- `data` 符号链接指向 π-Bench 官方评测数据,请遵守其数据许可条款。
|
||||
1039
benchmark/pibench/bridge_reme.py
Executable file
53
benchmark/pibench/config/bench/evaluation/trace_history.yaml
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
version: 1
|
||||
|
||||
format:
|
||||
root_tag: trace
|
||||
turn_tag: turn
|
||||
message_tag: message
|
||||
file_tag: file
|
||||
tool_call_tag_prefix: tool_call
|
||||
tool_result_tag_prefix: tool_result
|
||||
|
||||
text_policy:
|
||||
default:
|
||||
truncate_chars: 1200
|
||||
mask_newlines: false
|
||||
field_overrides:
|
||||
files_read:
|
||||
truncate_chars: 40000
|
||||
assistant_content:
|
||||
truncate_chars: 40000
|
||||
tool_result_content:
|
||||
truncate_chars: 40000
|
||||
|
||||
fields:
|
||||
turn:
|
||||
include_session_key: false
|
||||
|
||||
files:
|
||||
enabled: true
|
||||
|
||||
messages:
|
||||
enabled: true
|
||||
include_message_role_attr: true
|
||||
include_message_index_attr: false
|
||||
include_system: false
|
||||
include_user: true
|
||||
include_assistant_thinking_content: false
|
||||
include_assistant_thinking_reasoning: false
|
||||
include_assistant_content: true
|
||||
include_assistant_reasoning: false
|
||||
include_assistant_tool_calls: false
|
||||
require_matching_tool_call: true
|
||||
|
||||
tool_calls:
|
||||
include_tool_call_id: false
|
||||
tools:
|
||||
web_fetch:
|
||||
enabled: true
|
||||
include_tool_call_keys: [url]
|
||||
include_tool_result: false
|
||||
web_search:
|
||||
enabled: true
|
||||
include_tool_call_keys: [query]
|
||||
include_tool_result: false
|
||||
40
benchmark/pibench/config/models/reme.yaml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# ReMe model configuration for Pi-Bench
|
||||
# Uses ReMe's AgentScope agent with Dashscope as the LLM backend
|
||||
|
||||
model:
|
||||
model: reme
|
||||
base_url: "http://localhost:8088"
|
||||
api_key: "dummy"
|
||||
provider: custom
|
||||
max_tokens: 16384
|
||||
max_tool_iterations: 120
|
||||
memory_window: 100
|
||||
|
||||
user_agent:
|
||||
model: qwen3.8-max
|
||||
base_url: "${USER_BASE_URL}"
|
||||
api_key: "${USER_API_KEY}"
|
||||
temperature: 0.0
|
||||
request_timeout: 360.0
|
||||
|
||||
judger:
|
||||
model: qwen3.8-max
|
||||
base_url: "${JUDGER_BASE_URL}"
|
||||
api_key: "${JUDGER_API_KEY}"
|
||||
temperature: 0.0
|
||||
request_timeout: 360.0
|
||||
|
||||
tools:
|
||||
brave_search_api_key: "${BRAVE_SEARCH_API_KEY}"
|
||||
web_search_max_results: 10
|
||||
|
||||
nanobot:
|
||||
trace_logs_dir: "~/.nanobot/trace_logs"
|
||||
workspace_dir: "~/.nanobot/workspace"
|
||||
copy_task_assets_to_workspace: true
|
||||
|
||||
run:
|
||||
output_dir: outputs
|
||||
log_level: INFO
|
||||
user_mode: llm
|
||||
turn_timeout: 2400.0
|
||||
57
benchmark/pibench/env.sh.example
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#!/bin/bash
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# pibench evaluation suite - environment configuration template
|
||||
# Usage: cp env.sh.example env.sh, then fill in the TODO items below.
|
||||
# ⚠️ env.sh contains real API keys; never commit or share it
|
||||
# (already excluded via .gitignore).
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
SUITE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# ─── TODO: π-Bench repository root ────────────────────────────────────
|
||||
# Must contain src/, data/, scripts/test_server.py, third_party/appworld
|
||||
# and .venv (see README setup).
|
||||
export PI_BENCH_ROOT=""
|
||||
|
||||
# ─── ReMe repository ──────────────────────────────────────────────────
|
||||
# Defaults to two levels above this directory (the layout this suite uses
|
||||
# when placed at ReMe/benchmark/pibench); point it at the actual ReMe
|
||||
# repository root if the suite lives elsewhere.
|
||||
export REME_DIR="${REME_DIR:-$(cd "${SUITE_DIR}/../.." && pwd)}"
|
||||
|
||||
# ─── Base model of the agent under test (LLM used by the ReMe agent) ──
|
||||
export REME_MODEL_NAME="${REME_MODEL_NAME:-qwen3.6-plus}"
|
||||
|
||||
# ─── LLM service endpoint (default: DashScope OpenAI-compatible; any
|
||||
# OpenAI-compatible endpoint works) ────────────────────────────────
|
||||
DASHSCOPE_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
export REME_LLM_BASE_URL="${REME_LLM_BASE_URL:-${DASHSCOPE_BASE_URL}}"
|
||||
|
||||
# ─── TODO: API keys ───────────────────────────────────────────────────
|
||||
# USER_API_KEY : drives the simulated user LLM (run phase; judges whether
|
||||
# hidden intents are satisfied and asks follow-ups)
|
||||
# JUDGER_API_KEY: drives the judger LLM (eval phase; scores the checklist)
|
||||
# The two may be identical; one strong model is recommended for both.
|
||||
export USER_BASE_URL="${DASHSCOPE_BASE_URL}"
|
||||
export USER_API_KEY="TODO-fill-in-user-agent-api-key"
|
||||
|
||||
export JUDGER_BASE_URL="${DASHSCOPE_BASE_URL}"
|
||||
export JUDGER_API_KEY="TODO-fill-in-judger-api-key"
|
||||
|
||||
# The ReMe agent's key reuses USER_API_KEY by default (no need to repeat
|
||||
# it when both use the same service and key).
|
||||
export REME_LLM_API_KEY="${REME_LLM_API_KEY:-${USER_API_KEY}}"
|
||||
|
||||
# Brave Search (optional; used by the agent's web_search tool - use
|
||||
# "dummy" when not needed).
|
||||
export BRAVE_SEARCH_API_KEY="TODO-optional-brave-search-key-or-dummy"
|
||||
|
||||
# ─── Persistent memory workspaces (one subdirectory per persona,
|
||||
# created automatically) ───────────────────────────────────────────
|
||||
export REME_WORKSPACE_ROOT="${REME_WORKSPACE_ROOT:-${SUITE_DIR}/reme_workspace}"
|
||||
|
||||
# ─── Variables consumed by ReMe's default.yaml model config expansion;
|
||||
# do not remove ────────────────────────────────────────────────────
|
||||
export LLM_MODEL_NAME="${REME_MODEL_NAME}"
|
||||
export LLM_BASE_URL="${REME_LLM_BASE_URL}"
|
||||
export LLM_API_KEY="${REME_LLM_API_KEY}"
|
||||
198
benchmark/pibench/fix_trace_logs.py
Executable file
|
|
@ -0,0 +1,198 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Convert reme_eval run outputs into eval-compatible trace logs.
|
||||
|
||||
outputs/{model_id}/{user_id}/{task_id}/history/{ts}-messages.jsonl
|
||||
-> ~/.nanobot/trace_logs/{model_id}/{user_id}/{task_id}/{ts}/turn_N.json
|
||||
|
||||
The bridge additionally writes {ts}-tools.jsonl sidecar files next to the
|
||||
message histories: one JSON object per executed tool call with fields
|
||||
{turn, name, arguments, result}. Each messages run is paired with the
|
||||
temporally closest sidecar, and the records are merged into the generated
|
||||
turn files under the "tool_steps" key, which is one of the tool-history
|
||||
formats π-Bench's collect_tool_history() understands. Without this step,
|
||||
tools_evaluation scripts would see no tool evidence at all.
|
||||
|
||||
Usage: python fix_trace_logs.py [user_id ...] (no args = all users)
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
SUITE_DIR = Path(__file__).resolve().parent
|
||||
OUTPUTS_DIR = SUITE_DIR / "outputs"
|
||||
TRACE_LOGS_DIR = Path.home() / ".nanobot" / "trace_logs"
|
||||
|
||||
MESSAGES_FILE_RE = re.compile(r"^(\d{8}_\d{6})-messages\.jsonl$")
|
||||
TOOLS_FILE_RE = re.compile(r"^(\d{8}_\d{6})-tools\.jsonl$")
|
||||
TIME_FORMAT = "%Y%m%d_%H%M%S"
|
||||
# A tool sidecar belongs to the messages run that started at most this many
|
||||
# seconds earlier (the bridge stamps the sidecar when the task's first user
|
||||
# message arrives, shortly after the runner opened the messages file).
|
||||
MAX_PAIR_DELTA_SECONDS = 6 * 3600
|
||||
|
||||
|
||||
def _to_epoch(timestamp: str) -> float:
|
||||
"""Parse a YYYYMMDD_HHMMSS timestamp into epoch seconds."""
|
||||
try:
|
||||
return datetime.strptime(timestamp, TIME_FORMAT).timestamp()
|
||||
except ValueError:
|
||||
return 0.0
|
||||
|
||||
|
||||
def load_tool_records(tools_file: Path) -> dict:
|
||||
"""Group sidecar tool records by turn number."""
|
||||
by_turn: dict = {}
|
||||
try:
|
||||
with open(tools_file, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
record = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
if not isinstance(record, dict) or not record.get("name"):
|
||||
continue
|
||||
turn = int(record.get("turn") or 0)
|
||||
by_turn.setdefault(turn, []).append(
|
||||
{
|
||||
"name": record["name"],
|
||||
"arguments": record.get("arguments", {}),
|
||||
"result": record.get("result", ""),
|
||||
},
|
||||
)
|
||||
except OSError as exc:
|
||||
print(f" WARNING: cannot read tool sidecar {tools_file}: {exc}")
|
||||
return by_turn
|
||||
|
||||
|
||||
def pair_tool_sidecars(message_runs: list, tool_runs: list) -> dict:
|
||||
"""Pair each messages run with the temporally closest unused tool sidecar.
|
||||
|
||||
Fresh runs produce exactly one messages file and one sidecar per task;
|
||||
re-runs append matching pairs, so sorted greedy nearest-timestamp
|
||||
matching is stable. Sidecars farther away than MAX_PAIR_DELTA_SECONDS
|
||||
(e.g. leftovers of a crashed bridge) stay unpaired.
|
||||
"""
|
||||
pairing: dict = {}
|
||||
unused = list(tool_runs)
|
||||
for msg_ts, _ in message_runs:
|
||||
best_delta = None
|
||||
best_item = None
|
||||
for tool_ts, tool_path in unused:
|
||||
delta = abs(_to_epoch(tool_ts) - _to_epoch(msg_ts))
|
||||
if best_delta is None or delta < best_delta:
|
||||
best_delta = delta
|
||||
best_item = (tool_ts, tool_path)
|
||||
if best_delta is not None and best_item is not None and best_delta <= MAX_PAIR_DELTA_SECONDS:
|
||||
pairing[msg_ts] = best_item[1]
|
||||
unused.remove(best_item)
|
||||
return pairing
|
||||
|
||||
|
||||
def build_turns(messages: list) -> list:
|
||||
"""Split the flat message list into per-turn [user, assistant] groups."""
|
||||
turns = []
|
||||
i = 0
|
||||
while i < len(messages):
|
||||
turn_msgs = []
|
||||
if messages[i]["role"] == "user":
|
||||
turn_msgs.append({"role": "user", "content": messages[i]["message"]})
|
||||
i += 1
|
||||
if i < len(messages) and messages[i]["role"] == "assistant":
|
||||
turn_msgs.append({"role": "assistant", "content": messages[i]["message"]})
|
||||
i += 1
|
||||
if not turn_msgs:
|
||||
i += 1 # defensive: never spin on unexpected roles
|
||||
continue
|
||||
turns.append(turn_msgs)
|
||||
return turns
|
||||
|
||||
|
||||
def convert_task(model_id: str, user_id: str, task_dir: Path) -> None:
|
||||
"""Convert one task's history dir into trace turn files with tool_steps."""
|
||||
history_dir = task_dir / "history"
|
||||
if not history_dir.is_dir():
|
||||
return
|
||||
|
||||
message_runs = []
|
||||
tool_runs = []
|
||||
for msg_file in history_dir.glob("*-messages.jsonl"):
|
||||
match = MESSAGES_FILE_RE.match(msg_file.name)
|
||||
if match:
|
||||
message_runs.append((match.group(1), msg_file))
|
||||
for tools_file in history_dir.glob("*-tools.jsonl"):
|
||||
match = TOOLS_FILE_RE.match(tools_file.name)
|
||||
if match:
|
||||
tool_runs.append((match.group(1), tools_file))
|
||||
if not message_runs:
|
||||
return
|
||||
|
||||
message_runs.sort(key=lambda item: item[0])
|
||||
tool_runs.sort(key=lambda item: item[0])
|
||||
pairing = pair_tool_sidecars(message_runs, tool_runs)
|
||||
|
||||
print(f"\n{model_id}/{user_id}/{task_dir.name}")
|
||||
for timestamp, msg_file in message_runs:
|
||||
trace_dir = TRACE_LOGS_DIR / model_id / user_id / task_dir.name / timestamp
|
||||
trace_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
messages = []
|
||||
with open(msg_file, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
msg = json.loads(line)
|
||||
if msg.get("role") == "user" and msg.get("message") == "/new":
|
||||
continue
|
||||
messages.append(msg)
|
||||
|
||||
tools_file = pairing.get(timestamp)
|
||||
tools_by_turn = load_tool_records(tools_file) if tools_file else {}
|
||||
if tools_file is not None:
|
||||
print(f" {timestamp}: paired tool sidecar {tools_file.name}")
|
||||
|
||||
turns = build_turns(messages)
|
||||
for turn_idx, turn_msgs in enumerate(turns, start=1):
|
||||
turn_data = {"messages": turn_msgs}
|
||||
tool_steps = tools_by_turn.get(turn_idx)
|
||||
if tool_steps:
|
||||
turn_data["tool_steps"] = tool_steps
|
||||
turn_file = trace_dir / f"turn_{turn_idx}.json"
|
||||
with open(turn_file, "w", encoding="utf-8") as f:
|
||||
json.dump(turn_data, f, indent=2, ensure_ascii=False)
|
||||
tool_total = sum(len(steps) for steps in tools_by_turn.values())
|
||||
print(f" {timestamp}: {len(turns)} turns, {tool_total} tool step(s) -> {trace_dir}")
|
||||
|
||||
|
||||
def convert_outputs(user_filter=None):
|
||||
"""Convert message history JSONL files into per-turn trace JSON files."""
|
||||
if not OUTPUTS_DIR.exists():
|
||||
print(f"outputs dir not found: {OUTPUTS_DIR}")
|
||||
return
|
||||
|
||||
for model_dir in sorted(OUTPUTS_DIR.iterdir()):
|
||||
if not model_dir.is_dir():
|
||||
continue
|
||||
model_id = model_dir.name
|
||||
|
||||
for user_dir in sorted(model_dir.iterdir()):
|
||||
if not user_dir.is_dir():
|
||||
continue
|
||||
user_id = user_dir.name
|
||||
if user_filter and user_id not in user_filter:
|
||||
continue
|
||||
|
||||
for task_dir in sorted(user_dir.iterdir()):
|
||||
if task_dir.is_dir():
|
||||
convert_task(model_id, user_id, task_dir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
convert_outputs(set(sys.argv[1:]) or None)
|
||||
print("\ndone")
|
||||
332
benchmark/pibench/resume.py
Executable file
|
|
@ -0,0 +1,332 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Checkpoint-resume support for the reme_eval suite.
|
||||
|
||||
Completion source of truth:
|
||||
- outputs/reme/<persona>/<task_id>/history/*-log.jsonl (per-task logs,
|
||||
flushed incrementally, survive mid-run kills)
|
||||
- outputs/reme/<persona>/run/*-log.jsonl (run-level logs,
|
||||
may be truncated if the process was killed before flush)
|
||||
lines: "Task finished task_id=<id> status=<STATUS>"
|
||||
A task counts as COMPLETED when its latest terminal status is one of
|
||||
SUCCESS / MAX_TURNS / TIMEOUT. ERROR or never-started tasks stay pending.
|
||||
|
||||
"Latest" is decided by EVENT TIME, not by file category or read order:
|
||||
each record's "timestamp" (epoch seconds, or "timestamp_iso" as fallback)
|
||||
is compared across per-task and run-level logs alike, with the timestamp
|
||||
embedded in the log file name as a last-resort fallback. This keeps an
|
||||
old run-level SUCCESS from overriding a newer per-task ERROR when the
|
||||
re-run died before the new run-level log captured the task.
|
||||
|
||||
Commands:
|
||||
remaining <persona> [--json]
|
||||
Print task_ids still to run, in data/<persona>/episode.yaml order
|
||||
(one per line; --json prints {"completed": [...], "remaining": [...]}).
|
||||
|
||||
cleanup <persona> [--dry-run]
|
||||
Surgically remove residual memory artifacts of tasks that are about
|
||||
to be RE-RUN (i.e. pending tasks that left partial state because a
|
||||
previous run was interrupted). This prevents answer leakage: an
|
||||
interrupted task's conversation may already have been distilled into
|
||||
daily notes during graceful shutdown, and re-running the task with
|
||||
that memory injected would inflate scores.
|
||||
|
||||
Removed artifacts (only for pending tasks with residual state):
|
||||
- daily/<date>/<note>.md whose frontmatter session_id matches
|
||||
pibench_<task_id>_*, plus a refresh of ONLY the daily index of
|
||||
the affected date(s) (daily/<date>.md), matched by the full
|
||||
workspace-relative note path, never by bare file name
|
||||
- digest notes with matching session_id
|
||||
- session/dialog/pibench_<task_id>_*.jsonl
|
||||
- mem_session/**.jsonl files containing pibench_<task_id>_
|
||||
When the ReMe package is importable, the daily index refresh reuses
|
||||
ReMe's own rebuild logic (reme.steps.file_io._daily_index.
|
||||
refresh_day_index); otherwise index lines are dropped by exact
|
||||
wikilink path match. Either way, indexes of other dates are never
|
||||
touched. The ReMe watcher (init_changes_step) detects the deleted
|
||||
daily notes on next bridge startup and removes them from the BM25
|
||||
index itself.
|
||||
|
||||
Completed tasks' memories are NEVER touched by this command.
|
||||
|
||||
Design note (resume vs memory-wipe conflict):
|
||||
A full memory wipe is a suite-level action of fresh mode (run_all.sh
|
||||
without --resume) and happens before any service starts. Resume mode
|
||||
never wipes; it only performs the surgical cleanup above. The two modes
|
||||
are mutually exclusive, so a resumed run can never lose the cross-session
|
||||
memory accumulated by completed tasks.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
try: # Reuse ReMe's daily-index rebuild when running inside the ReMe venv.
|
||||
from reme.steps.file_io._daily_index import refresh_day_index
|
||||
except ImportError: # pragma: no cover - depends on runtime venv
|
||||
refresh_day_index = None
|
||||
|
||||
SUITE_DIR = Path(__file__).resolve().parent
|
||||
DATA_DIR = Path(os.environ.get("REME_EVAL_DATA_DIR", SUITE_DIR / "data")).resolve()
|
||||
OUTPUTS_DIR = Path(os.environ.get("REME_EVAL_OUTPUTS_DIR", SUITE_DIR / "outputs")) / "reme"
|
||||
WORKSPACE_ROOT = Path(
|
||||
os.environ.get("REME_WORKSPACE_ROOT", SUITE_DIR / "reme_workspace"),
|
||||
).resolve()
|
||||
|
||||
COMPLETED_STATUSES = {"SUCCESS", "MAX_TURNS", "TIMEOUT"}
|
||||
TASK_FINISHED_RE = re.compile(r"Task finished task_id=(\S+) status=(\S+)")
|
||||
SESSION_ID_RE = re.compile(r"^session_id:\s*(\S+)", re.MULTILINE)
|
||||
NOTE_COUNT_RE = re.compile(r"(description:\s*)\d+(\s*note\(s\) today)")
|
||||
LOG_FILE_TS_RE = re.compile(r"^(\d{8}_\d{6})-log\.jsonl$")
|
||||
TIME_FORMAT = "%Y%m%d_%H%M%S"
|
||||
|
||||
|
||||
def log(msg: str) -> None:
|
||||
"""Print a status message to stderr."""
|
||||
print(msg, file=sys.stderr)
|
||||
|
||||
|
||||
def episode_task_order(persona: str) -> list[str]:
|
||||
"""Return the ordered task ids from the persona's episode.yaml."""
|
||||
episode_path = DATA_DIR / persona / "episode.yaml"
|
||||
with open(episode_path, "r", encoding="utf-8") as f:
|
||||
episode = yaml.safe_load(f)
|
||||
return [task["task_id"] for task in episode.get("tasks", [])]
|
||||
|
||||
|
||||
def _event_time(record: dict, file_ts: str) -> float:
|
||||
"""Best-effort event time (epoch seconds) of one log record.
|
||||
|
||||
Prefers the record's own timestamp fields; falls back to the timestamp
|
||||
embedded in the log file name so that even stripped records keep a
|
||||
meaningful order. Returns 0.0 when nothing is parseable.
|
||||
"""
|
||||
timestamp = record.get("timestamp")
|
||||
if isinstance(timestamp, (int, float)) and not isinstance(timestamp, bool):
|
||||
return float(timestamp)
|
||||
iso = record.get("timestamp_iso")
|
||||
if isinstance(iso, str):
|
||||
try:
|
||||
return datetime.fromisoformat(iso).timestamp()
|
||||
except ValueError:
|
||||
pass
|
||||
if file_ts:
|
||||
try:
|
||||
return datetime.strptime(file_ts, TIME_FORMAT).timestamp()
|
||||
except ValueError:
|
||||
pass
|
||||
return 0.0
|
||||
|
||||
|
||||
def latest_task_statuses(persona: str) -> dict[str, str]:
|
||||
"""Scan per-task and run-level logs; the newest EVENT TIME wins per task.
|
||||
|
||||
Every "Task finished" record across both log categories is keyed by
|
||||
(event_time, file timestamp, file order, line number); the record with
|
||||
the highest key decides the task's status. File category and read order
|
||||
alone can never override a newer record from the other category.
|
||||
"""
|
||||
persona_dir = OUTPUTS_DIR / persona
|
||||
if not persona_dir.is_dir():
|
||||
return {}
|
||||
|
||||
log_files = sorted(persona_dir.glob("*/history/*-log.jsonl"))
|
||||
log_files += sorted(persona_dir.glob("run/*-log.jsonl"))
|
||||
|
||||
best: dict[str, tuple[tuple, str]] = {}
|
||||
for file_order, log_file in enumerate(log_files):
|
||||
ts_match = LOG_FILE_TS_RE.match(log_file.name)
|
||||
file_ts = ts_match.group(1) if ts_match else ""
|
||||
try:
|
||||
with open(log_file, "r", encoding="utf-8") as f:
|
||||
for line_no, line in enumerate(f):
|
||||
if "Task finished" not in line:
|
||||
continue
|
||||
try:
|
||||
record = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
match = TASK_FINISHED_RE.search(str(record.get("message", "")))
|
||||
if not match:
|
||||
continue
|
||||
task_id, status = match.group(1), match.group(2)
|
||||
sort_key = (_event_time(record, file_ts), file_ts, file_order, line_no)
|
||||
current = best.get(task_id)
|
||||
if current is None or sort_key > current[0]:
|
||||
best[task_id] = (sort_key, status)
|
||||
except OSError:
|
||||
continue
|
||||
return {task_id: status for task_id, (_, status) in best.items()}
|
||||
|
||||
|
||||
def split_tasks(persona: str) -> tuple[list[str], list[str]]:
|
||||
"""Split the episode task order into completed and remaining tasks."""
|
||||
order = episode_task_order(persona)
|
||||
statuses = latest_task_statuses(persona)
|
||||
completed = [t for t in order if statuses.get(t) in COMPLETED_STATUSES]
|
||||
remaining = [t for t in order if t not in set(completed)]
|
||||
return completed, remaining
|
||||
|
||||
|
||||
def _daily_note_session_id(note_path: Path) -> str:
|
||||
try:
|
||||
text = note_path.read_text(encoding="utf-8")
|
||||
except OSError:
|
||||
return ""
|
||||
match = SESSION_ID_RE.search(text)
|
||||
return match.group(1) if match else ""
|
||||
|
||||
|
||||
class _WorkspaceFileStoreShim:
|
||||
"""Structural stand-in for ReMe's file store; only workspace_path is read."""
|
||||
|
||||
def __init__(self, workspace_path: Path):
|
||||
self.workspace_path = workspace_path
|
||||
|
||||
|
||||
def _refresh_daily_indexes(
|
||||
workspace: Path,
|
||||
removed_by_date: dict[str, set[str]],
|
||||
removed: list[str],
|
||||
) -> None:
|
||||
"""Rebuild the daily index of each affected date via ReMe's own logic."""
|
||||
for date in sorted(removed_by_date):
|
||||
result = asyncio.run(
|
||||
refresh_day_index(_WorkspaceFileStoreShim(workspace), date, "daily"),
|
||||
)
|
||||
if result.get("error"):
|
||||
log(f"[resume] WARNING: daily index refresh failed for {date}: {result['error']}")
|
||||
continue
|
||||
removed.append(f"daily/{date}.md (refreshed, {len(removed_by_date[date])} note(s) removed)")
|
||||
|
||||
|
||||
def _strip_index_lines(
|
||||
workspace: Path,
|
||||
removed_by_date: dict[str, set[str]],
|
||||
removed: list[str],
|
||||
dry_run: bool,
|
||||
) -> None:
|
||||
"""Fallback index edit: drop lines that reference removed notes by full
|
||||
workspace-relative wikilink path, and fix the note count. Only the index
|
||||
files of affected dates are touched."""
|
||||
for date in sorted(removed_by_date):
|
||||
index_path = workspace / "daily" / f"{date}.md"
|
||||
if not index_path.is_file():
|
||||
continue
|
||||
wikilinks = [f"[[{rel_path}]]" for rel_path in sorted(removed_by_date[date])]
|
||||
lines = index_path.read_text(encoding="utf-8").splitlines()
|
||||
kept = [line for line in lines if not any(link in line for link in wikilinks)]
|
||||
if len(kept) == len(lines):
|
||||
continue
|
||||
note_count = sum(1 for line in kept if line.startswith("- [[daily/"))
|
||||
kept = [NOTE_COUNT_RE.sub(rf"\g<1>{note_count}\2", line) for line in kept]
|
||||
removed.append(f"{index_path.relative_to(workspace)} (rewritten)")
|
||||
if not dry_run:
|
||||
index_path.write_text("\n".join(kept) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def cleanup_partial_memory(persona: str, remaining: list[str], dry_run: bool = False) -> list[str]:
|
||||
"""Remove partial memory artifacts of remaining tasks so they can be re-run cleanly."""
|
||||
workspace = WORKSPACE_ROOT / persona
|
||||
removed: list[str] = []
|
||||
if not workspace.is_dir() or not remaining:
|
||||
return removed
|
||||
|
||||
prefixes = tuple(f"pibench_{task_id}_" for task_id in remaining)
|
||||
|
||||
def act(path: Path, label: str) -> None:
|
||||
removed.append(label)
|
||||
if not dry_run:
|
||||
path.unlink()
|
||||
|
||||
# 1) daily / digest notes distilled from interrupted sessions. For daily
|
||||
# notes, remember the full workspace-relative path grouped by date so only
|
||||
# the affected daily indexes are refreshed below.
|
||||
removed_by_date: dict[str, set[str]] = {}
|
||||
for section in ("daily", "digest"):
|
||||
section_root = workspace / section
|
||||
if not section_root.is_dir():
|
||||
continue
|
||||
for note_path in section_root.rglob("*.md"):
|
||||
if note_path.parent == section_root:
|
||||
continue # index files handled below
|
||||
session_id = _daily_note_session_id(note_path)
|
||||
if session_id.startswith(prefixes):
|
||||
rel_path = note_path.relative_to(workspace).as_posix()
|
||||
act(note_path, rel_path)
|
||||
if section == "daily":
|
||||
removed_by_date.setdefault(note_path.parent.name, set()).add(rel_path)
|
||||
|
||||
# 2) daily index files: refresh only the dates that lost notes, matching
|
||||
# notes by their full wikilink path instead of their bare file name.
|
||||
if removed_by_date:
|
||||
if dry_run:
|
||||
for date in sorted(removed_by_date):
|
||||
removed.append(f"daily/{date}.md (would refresh index)")
|
||||
elif refresh_day_index is not None:
|
||||
_refresh_daily_indexes(workspace, removed_by_date, removed)
|
||||
else:
|
||||
_strip_index_lines(workspace, removed_by_date, removed, dry_run)
|
||||
|
||||
# 3) raw dialog logs of interrupted sessions
|
||||
dialog_dir = workspace / "session" / "dialog"
|
||||
if dialog_dir.is_dir():
|
||||
for task_id in remaining:
|
||||
for dialog_path in dialog_dir.glob(f"pibench_{task_id}_*.jsonl"):
|
||||
act(dialog_path, str(dialog_path.relative_to(workspace)))
|
||||
|
||||
# 4) agent-scope session states that contain interrupted-task sessions
|
||||
mem_session_dir = workspace / "mem_session"
|
||||
if mem_session_dir.is_dir():
|
||||
for session_path in mem_session_dir.rglob("*.jsonl"):
|
||||
try:
|
||||
content = session_path.read_text(encoding="utf-8", errors="ignore")
|
||||
except OSError:
|
||||
continue
|
||||
if any(prefix in content for prefix in prefixes):
|
||||
act(session_path, str(session_path.relative_to(workspace)))
|
||||
|
||||
return removed
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""CLI entrypoint: run 'remaining' or 'cleanup' action for a persona."""
|
||||
args = sys.argv[1:]
|
||||
if len(args) < 2 or args[0] not in {"remaining", "cleanup"}:
|
||||
print(__doc__, file=sys.stderr)
|
||||
return 2
|
||||
|
||||
command, persona = args[0], args[1]
|
||||
completed, remaining = split_tasks(persona)
|
||||
|
||||
if command == "remaining":
|
||||
if "--json" in args:
|
||||
print(json.dumps({"completed": completed, "remaining": remaining}))
|
||||
else:
|
||||
for task_id in remaining:
|
||||
print(task_id)
|
||||
log(
|
||||
f"[resume] {persona}: completed={len(completed)} "
|
||||
f"({', '.join(completed) if completed else '-'}) remaining={len(remaining)}",
|
||||
)
|
||||
return 0
|
||||
|
||||
dry_run = "--dry-run" in args
|
||||
removed = cleanup_partial_memory(persona, remaining, dry_run=dry_run)
|
||||
if removed:
|
||||
verb = "would remove" if dry_run else "removed"
|
||||
log(f"[resume] {persona}: {verb} {len(removed)} partial-memory artifact(s):")
|
||||
for item in removed:
|
||||
log(f" - {item}")
|
||||
else:
|
||||
log(f"[resume] {persona}: no partial-memory artifacts to clean")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
119
benchmark/pibench/run_all.sh
Executable file
|
|
@ -0,0 +1,119 @@
|
|||
#!/bin/bash
|
||||
# Run all 5 personas with the ReMe agent, PARALLEL at a time (default 2).
|
||||
# Each persona's tasks follow data/{persona}/episode.yaml order.
|
||||
#
|
||||
# Usage:
|
||||
# bash run_all.sh # FRESH official run: wipes ALL personas'
|
||||
# # ReMe memory/outputs/trace logs first,
|
||||
# # then runs everything from scratch.
|
||||
# bash run_all.sh --resume # Checkpoint continuation: no wipe; every
|
||||
# # persona skips already-completed tasks.
|
||||
# bash run_all.sh --parallel 1 # sequential (original behavior)
|
||||
# bash run_all.sh --skip-eval # run phase only
|
||||
#
|
||||
# Memory-wipe vs resume conflict resolution:
|
||||
# The full ReMe memory wipe happens ONLY here, ONLY in fresh mode (the
|
||||
# default), and ONLY before any service/bridge starts. --resume never
|
||||
# wipes; run_persona.sh then additionally performs a surgical cleanup of
|
||||
# residual memory belonging to interrupted (to-be-re-run) tasks, so a
|
||||
# resumed run keeps all completed-task memory but never inherits a partial
|
||||
# task's own answer. The two modes are mutually exclusive.
|
||||
set -uo pipefail
|
||||
|
||||
SUITE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PERSONAS=(researcher marketer law_trainee pharmacist Financier)
|
||||
TRACE_ROOT="${HOME}/.nanobot/trace_logs"
|
||||
|
||||
PARALLEL=2
|
||||
MODE="fresh"
|
||||
PASS_ARGS=()
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--parallel)
|
||||
PARALLEL="${2:-}"; shift 2 || true
|
||||
case "$PARALLEL" in (""|*[!0-9]*) echo "--parallel needs a positive integer"; exit 2 ;; esac
|
||||
[ "$PARALLEL" -lt 1 ] && PARALLEL=1
|
||||
[ "$PARALLEL" -gt ${#PERSONAS[@]} ] && PARALLEL=${#PERSONAS[@]}
|
||||
;;
|
||||
--resume)
|
||||
if [ "$MODE" = "fresh_set" ]; then echo "--fresh and --resume are mutually exclusive"; exit 2; fi
|
||||
MODE="resume"; shift ;;
|
||||
--fresh)
|
||||
if [ "$MODE" = "resume" ]; then echo "--fresh and --resume are mutually exclusive"; exit 2; fi
|
||||
MODE="fresh_set"; shift ;;
|
||||
--skip-eval) PASS_ARGS+=(--skip-eval); shift ;;
|
||||
*) echo "Unknown option: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
[ "$MODE" = "fresh_set" ] && MODE="fresh"
|
||||
|
||||
START_TS=$(date +%Y%m%d_%H%M%S)
|
||||
SUMMARY_LOG="${SUITE_DIR}/logs/run_all_${START_TS}.summary"
|
||||
mkdir -p "${SUITE_DIR}/logs"
|
||||
|
||||
echo "############################################################"
|
||||
echo "# reme_eval suite | mode=${MODE} parallel=${PARALLEL} | ${START_TS}"
|
||||
echo "############################################################"
|
||||
|
||||
# ─── Fresh mode: suite-level wipe BEFORE anything starts ──────────────
|
||||
if [ "$MODE" = "fresh" ]; then
|
||||
echo "[fresh] wiping ALL personas' memory workspaces, outputs and trace logs..."
|
||||
for persona in "${PERSONAS[@]}"; do
|
||||
rm -rf "${SUITE_DIR}/reme_workspace/${persona}"
|
||||
rm -rf "${SUITE_DIR}/outputs/reme/${persona}"
|
||||
rm -rf "${TRACE_ROOT}/reme/${persona}"
|
||||
rm -rf "${SUITE_DIR}/nanobot_workspace/${persona}"
|
||||
done
|
||||
echo "[fresh] wipe done."
|
||||
else
|
||||
echo "[resume] no memory wipe; personas resume after their last completed task."
|
||||
fi
|
||||
|
||||
# ─── Run personas in batches of PARALLEL ──────────────────────────────
|
||||
STATUS_LIST=()
|
||||
ANY_FAILED=0
|
||||
OVERALL_START=$(date +%s)
|
||||
TOTAL=${#PERSONAS[@]}
|
||||
|
||||
for ((i = 0; i < TOTAL; i += PARALLEL)); do
|
||||
BATCH=("${PERSONAS[@]:i:PARALLEL}")
|
||||
BATCH_PIDS=()
|
||||
BATCH_NAMES=()
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo "# BATCH $(( i / PARALLEL + 1 )): ${BATCH[*]} started $(date '+%F %T')"
|
||||
echo "============================================================"
|
||||
for persona in "${BATCH[@]}"; do
|
||||
bash "${SUITE_DIR}/run_persona.sh" "${persona}" --resume ${PASS_ARGS[@]+"${PASS_ARGS[@]}"} \
|
||||
> "${SUITE_DIR}/logs/suite_${persona}.log" 2>&1 &
|
||||
BATCH_PIDS+=($!)
|
||||
BATCH_NAMES+=("$persona")
|
||||
done
|
||||
for j in $(seq 0 $(( ${#BATCH[@]} - 1 ))); do
|
||||
pid=${BATCH_PIDS[$j]}
|
||||
persona=${BATCH_NAMES[$j]}
|
||||
if wait "$pid"; then
|
||||
STATUS_LIST+=("${persona}: OK")
|
||||
else
|
||||
rc=$?
|
||||
ANY_FAILED=1
|
||||
STATUS_LIST+=("${persona}: FAILED rc=${rc}")
|
||||
echo "[run_all] ${persona} FAILED (rc=${rc}); see logs/suite_${persona}.log"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
total=$(( $(date +%s) - OVERALL_START ))
|
||||
echo ""
|
||||
echo "================ FINAL SUMMARY (${total}s total) ================" | tee -a "${SUMMARY_LOG}"
|
||||
for line in "${STATUS_LIST[@]}"; do
|
||||
echo " ${line}" | tee -a "${SUMMARY_LOG}"
|
||||
done
|
||||
echo "Summary: ${SUMMARY_LOG}"
|
||||
|
||||
if [ "${ANY_FAILED}" -ne 0 ]; then
|
||||
FAILED_COUNT=$(printf '%s\n' "${STATUS_LIST[@]}" | grep -c "FAILED")
|
||||
echo "[run_all] ${FAILED_COUNT} persona(s) FAILED; suite run is marked as failed." | tee -a "${SUMMARY_LOG}"
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
301
benchmark/pibench/run_persona.sh
Executable file
|
|
@ -0,0 +1,301 @@
|
|||
#!/bin/bash
|
||||
# Run the full pi-bench evaluation for ONE persona with the ReMe agent.
|
||||
# Tasks follow data/{persona}/episode.yaml order (runner-native).
|
||||
#
|
||||
# Usage: bash run_persona.sh <persona> [--fresh|--resume] [--skip-eval]
|
||||
#
|
||||
# Modes (default: --resume):
|
||||
# --resume Checkpoint continuation. Never wipes memory. Tasks already
|
||||
# finished (SUCCESS/MAX_TURNS/TIMEOUT in the task history logs)
|
||||
# are skipped via repeated --task-id flags. Before starting, any
|
||||
# residual memory of tasks that are about to be RE-RUN (partial
|
||||
# sessions from an interrupted run) is surgically removed by
|
||||
# resume.py cleanup, so re-runs don't inherit leaked answers.
|
||||
# --fresh Wipes THIS persona's ReMe memory, outputs and trace logs first,
|
||||
# then runs all tasks from scratch.
|
||||
# The two flags are mutually exclusive. A full multi-persona memory wipe is a
|
||||
# suite-level action of `run_all.sh` (fresh mode), never done here implicitly.
|
||||
set -uo pipefail
|
||||
|
||||
SUITE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TRACE_ROOT="${HOME}/.nanobot/trace_logs"
|
||||
|
||||
# ─── External dependencies (pi-bench / ReMe are NOT bundled; see README) ──
|
||||
if [ ! -f "${SUITE_DIR}/env.sh" ]; then
|
||||
echo "env.sh not found. Run: cp env.sh.example env.sh (then fill in the TODO items)"
|
||||
exit 1
|
||||
fi
|
||||
source "${SUITE_DIR}/env.sh"
|
||||
|
||||
PIBENCH_DIR="${PI_BENCH_ROOT:-}"
|
||||
if [ -z "${PIBENCH_DIR}" ] || [ ! -f "${PIBENCH_DIR}/src/main.py" ]; then
|
||||
echo "PI_BENCH_ROOT is unset or invalid (src/main.py not found). Set it in env.sh."
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -x "${PIBENCH_DIR}/.venv/bin/python" ] || [ ! -x "${PIBENCH_DIR}/.venv/bin/appworld" ]; then
|
||||
echo "pi-bench venv incomplete: ${PIBENCH_DIR}/.venv must provide python + appworld (see README setup)."
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -x "${REME_DIR}/.venv/bin/python" ]; then
|
||||
echo "ReMe venv not found: ${REME_DIR}/.venv/bin/python (check REME_DIR in env.sh)"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -e "${SUITE_DIR}/data" ]; then
|
||||
echo 'Benchmark data not linked. Run: ln -s "$PI_BENCH_ROOT/data" data'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ─── Pre-flight: files the runner needs before any service starts ─────
|
||||
MODEL_CONFIG="${SUITE_DIR}/config/models/reme.yaml"
|
||||
HISTORY_CONFIG="${SUITE_DIR}/config/bench/evaluation/trace_history.yaml"
|
||||
if [ ! -f "${MODEL_CONFIG}" ]; then
|
||||
echo "Model config not found: ${MODEL_CONFIG} (see README directory layout)."
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "${HISTORY_CONFIG}" ]; then
|
||||
echo "Trace history config not found: ${HISTORY_CONFIG}"
|
||||
echo "pi-bench requires config/bench/evaluation/trace_history.yaml; see README."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
APPWORLD_DIR="${PIBENCH_DIR}/third_party/appworld"
|
||||
PI_PYTHON="${PIBENCH_DIR}/.venv/bin/python"
|
||||
APPWORLD_BIN="${PIBENCH_DIR}/.venv/bin/appworld"
|
||||
# resume.py runs on the ReMe venv so it can reuse ReMe's daily-index rebuild.
|
||||
REME_PYTHON="${REME_DIR}/.venv/bin/python"
|
||||
|
||||
PERSONA="${1:-}"
|
||||
if [ -z "$PERSONA" ]; then
|
||||
echo "Usage: $0 <persona> [--fresh|--resume] [--skip-eval]"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
|
||||
MODE="resume"
|
||||
SKIP_EVAL=false
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--fresh)
|
||||
if [ "$MODE" = "resume_set" ]; then echo "--fresh and --resume are mutually exclusive"; exit 2; fi
|
||||
MODE="fresh"; shift ;;
|
||||
--resume)
|
||||
if [ "$MODE" = "fresh" ]; then echo "--fresh and --resume are mutually exclusive"; exit 2; fi
|
||||
MODE="resume_set"; shift ;;
|
||||
--skip-eval) SKIP_EVAL=true; shift ;;
|
||||
*) echo "Unknown option: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
[ "$MODE" = "resume_set" ] && MODE="resume"
|
||||
|
||||
# ─── Per-persona ports (pi-bench AGENTS.md convention) ────────────────
|
||||
# REME_PORT: ReMe's internal HTTP service; must be unique per concurrent bridge.
|
||||
case "$PERSONA" in
|
||||
marketer) API_PORT=9001; MCP_PORT=10001; TEST_PORT=9998; REME_PORT=18766 ;;
|
||||
law_trainee) API_PORT=9002; MCP_PORT=10002; TEST_PORT=9997; REME_PORT=18767 ;;
|
||||
pharmacist) API_PORT=9003; MCP_PORT=10003; TEST_PORT=9996; REME_PORT=18768 ;;
|
||||
researcher) API_PORT=9004; MCP_PORT=10004; TEST_PORT=9995; REME_PORT=18765 ;;
|
||||
Financier) API_PORT=9005; MCP_PORT=10005; TEST_PORT=9994; REME_PORT=18769 ;;
|
||||
*) echo "Unknown persona: $PERSONA"; exit 1 ;;
|
||||
esac
|
||||
|
||||
API_URL="http://127.0.0.1:${API_PORT}"
|
||||
MCP_URL="http://127.0.0.1:${MCP_PORT}/mcp"
|
||||
TEST_URL="http://127.0.0.1:${TEST_PORT}"
|
||||
LOG_DIR="${SUITE_DIR}/logs"
|
||||
mkdir -p "${LOG_DIR}"
|
||||
|
||||
# ─── Environment (env.sh already sourced at the top) ──────────────────
|
||||
WORKSPACE_DIR="${REME_WORKSPACE_ROOT}/${PERSONA}"
|
||||
NANOBOT_WORKSPACE_DIR="${SUITE_DIR}/nanobot_workspace/${PERSONA}"
|
||||
mkdir -p "${WORKSPACE_DIR}" "${NANOBOT_WORKSPACE_DIR}"
|
||||
|
||||
echo "========================================="
|
||||
echo "ReMe x Pi-Bench | persona=${PERSONA} | mode=${MODE}"
|
||||
echo " api=${API_PORT} mcp=${MCP_PORT} test=${TEST_PORT} reme=${REME_PORT}"
|
||||
echo " model=${REME_MODEL_NAME}"
|
||||
echo " memory workspace=${WORKSPACE_DIR} (persistent)"
|
||||
echo "========================================="
|
||||
|
||||
# ─── Fresh mode: wipe this persona's state ────────────────────────────
|
||||
if [ "$MODE" = "fresh" ]; then
|
||||
echo "[fresh] wiping persona state: memory workspace, outputs, trace logs"
|
||||
rm -rf "${WORKSPACE_DIR}"
|
||||
rm -rf "${SUITE_DIR}/outputs/reme/${PERSONA}"
|
||||
rm -rf "${TRACE_ROOT}/reme/${PERSONA}"
|
||||
rm -rf "${NANOBOT_WORKSPACE_DIR}"
|
||||
mkdir -p "${WORKSPACE_DIR}" "${NANOBOT_WORKSPACE_DIR}"
|
||||
fi
|
||||
|
||||
# ─── Resume: determine remaining tasks + clean partial memories ───────
|
||||
TASK_ARGS=()
|
||||
RUN_PHASE_NEEDED=true
|
||||
if [ "$MODE" = "resume" ]; then
|
||||
REMAINING_JSON="$("${REME_PYTHON}" "${SUITE_DIR}/resume.py" remaining "${PERSONA}" --json)"
|
||||
if [ -z "$REMAINING_JSON" ]; then
|
||||
echo "Failed to compute remaining tasks"; exit 1
|
||||
fi
|
||||
echo "[resume] ${REMAINING_JSON}"
|
||||
REMAINING_TASKS=()
|
||||
while IFS= read -r tid_line; do
|
||||
[ -n "$tid_line" ] && REMAINING_TASKS+=("$tid_line")
|
||||
done < <("${REME_PYTHON}" "${SUITE_DIR}/resume.py" remaining "${PERSONA}" 2>/dev/null)
|
||||
if [ ${#REMAINING_TASKS[@]} -eq 0 ]; then
|
||||
RUN_PHASE_NEEDED=false
|
||||
echo "[resume] all tasks already completed; skipping run phase"
|
||||
else
|
||||
# Remove residual memory of interrupted (to-be-re-run) tasks so
|
||||
# re-runs don't get their own partial answers injected.
|
||||
"${REME_PYTHON}" "${SUITE_DIR}/resume.py" cleanup "${PERSONA}"
|
||||
for tid in "${REMAINING_TASKS[@]}"; do
|
||||
TASK_ARGS+=(--task-id "$tid")
|
||||
done
|
||||
echo "[resume] running ${#REMAINING_TASKS[@]} remaining task(s): ${REMAINING_TASKS[*]}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─── Port cleanup from previous runs ──────────────────────────────────
|
||||
for port in ${API_PORT} ${MCP_PORT} ${TEST_PORT} ${REME_PORT}; do
|
||||
pids=$(lsof -ti :${port} 2>/dev/null || true)
|
||||
if [ -n "$pids" ]; then
|
||||
echo "Killing stale processes on port ${port}: ${pids}"
|
||||
kill -9 $pids 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
sleep 2
|
||||
|
||||
PIDS=()
|
||||
cleanup() {
|
||||
echo "[${PERSONA}] cleaning up services..."
|
||||
for pid in "${PIDS[@]:-}"; do
|
||||
kill "$pid" 2>/dev/null || true
|
||||
done
|
||||
wait 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
wait_for_service() {
|
||||
local url="$1" name="$2" port="$3" timeout="${4:-180}"
|
||||
echo -n " waiting for ${name}..."
|
||||
local start=$(date +%s)
|
||||
while true; do
|
||||
if curl -sf --max-time 5 "${url}" > /dev/null 2>&1; then
|
||||
echo " ready"; return 0
|
||||
fi
|
||||
if [ -n "$port" ] && lsof -ti :${port} > /dev/null 2>&1; then
|
||||
local elapsed=$(( $(date +%s) - start ))
|
||||
if [ "$elapsed" -ge 10 ]; then echo " ready (port)"; return 0; fi
|
||||
fi
|
||||
if [ $(( $(date +%s) - start )) -ge "$timeout" ]; then
|
||||
echo " TIMEOUT"; return 1
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
}
|
||||
|
||||
# ─── [1/5] AppWorld API ────────────────────────────────────────────────
|
||||
echo "[1/5] AppWorld API (:${API_PORT})"
|
||||
(cd "${APPWORLD_DIR}" && exec "${APPWORLD_BIN}" serve apis --root . \
|
||||
--port ${API_PORT}) > "${LOG_DIR}/appworld_api_${PERSONA}.log" 2>&1 &
|
||||
PIDS+=($!)
|
||||
if ! wait_for_service "${API_URL}/docs" "AppWorld API" "${API_PORT}" 180; then
|
||||
tail -20 "${LOG_DIR}/appworld_api_${PERSONA}.log"; exit 1
|
||||
fi
|
||||
|
||||
# ─── [2/5] AppWorld MCP ────────────────────────────────────────────────
|
||||
echo "[2/5] AppWorld MCP (:${MCP_PORT})"
|
||||
TOOLS_CONFIG="${SUITE_DIR}/data/${PERSONA}/tools.yaml"
|
||||
(cd "${APPWORLD_DIR}" && exec "${APPWORLD_BIN}" serve mcp http --root . \
|
||||
--remote-apis-url "${API_URL}" --port ${MCP_PORT} \
|
||||
--tools-config-file "${TOOLS_CONFIG}") > "${LOG_DIR}/appworld_mcp_${PERSONA}.log" 2>&1 &
|
||||
PIDS+=($!)
|
||||
if ! wait_for_service "${MCP_URL}" "AppWorld MCP" "${MCP_PORT}" 180; then
|
||||
tail -20 "${LOG_DIR}/appworld_mcp_${PERSONA}.log"; exit 1
|
||||
fi
|
||||
|
||||
# ─── [3/5] Test Server ─────────────────────────────────────────────────
|
||||
echo "[3/5] Test Server (:${TEST_PORT})"
|
||||
PORT=${TEST_PORT} "${PI_PYTHON}" "${PIBENCH_DIR}/scripts/test_server.py" \
|
||||
> "${LOG_DIR}/test_server_${PERSONA}.log" 2>&1 &
|
||||
PIDS+=($!)
|
||||
if ! wait_for_service "${TEST_URL}/sent?after=-1" "Test Server" "${TEST_PORT}" 30; then
|
||||
tail -20 "${LOG_DIR}/test_server_${PERSONA}.log"; exit 1
|
||||
fi
|
||||
|
||||
# ─── [4/5] ReMe Bridge (ReMe venv) ─────────────────────────────────────
|
||||
echo "[4/5] ReMe Bridge (reme service port ${REME_PORT})"
|
||||
"${REME_DIR}/.venv/bin/python" "${SUITE_DIR}/bridge_reme.py" \
|
||||
--test-server-url "${TEST_URL}" \
|
||||
--appworld-mcp-url "${MCP_URL}" \
|
||||
--reme-dir "${REME_DIR}" \
|
||||
--data-root "${SUITE_DIR}/data" \
|
||||
--user-id "${PERSONA}" \
|
||||
--workspace-dir "${WORKSPACE_DIR}" \
|
||||
--reme-port "${REME_PORT}" \
|
||||
--model-name "${REME_MODEL_NAME}" \
|
||||
--model-base-url "${REME_LLM_BASE_URL}" \
|
||||
--model-api-key "${REME_LLM_API_KEY}" \
|
||||
> "${LOG_DIR}/bridge_${PERSONA}.log" 2>&1 &
|
||||
BRIDGE_PID=$!
|
||||
PIDS+=(${BRIDGE_PID})
|
||||
sleep 5
|
||||
if ! kill -0 "${BRIDGE_PID}" 2>/dev/null; then
|
||||
echo "Bridge failed to start:"; tail -30 "${LOG_DIR}/bridge_${PERSONA}.log"; exit 1
|
||||
fi
|
||||
for i in $(seq 1 12); do
|
||||
if grep -q "Bridge started:" "${LOG_DIR}/bridge_${PERSONA}.log" 2>/dev/null; then
|
||||
echo " bridge initialized"; break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
grep -q "Bridge started:" "${LOG_DIR}/bridge_${PERSONA}.log" 2>/dev/null || {
|
||||
echo "WARNING: bridge may not be ready:"; tail -20 "${LOG_DIR}/bridge_${PERSONA}.log"; }
|
||||
|
||||
# ─── [5/5] Runner (run phase) ──────────────────────────────────────────
|
||||
if [ "$RUN_PHASE_NEEDED" = true ]; then
|
||||
echo "[5/5] Runner: run phase (episode order from data/${PERSONA}/episode.yaml)"
|
||||
cd "${SUITE_DIR}"
|
||||
BENCH_TEST_SERVER_URL="${TEST_URL}" PYTHONPATH="${PIBENCH_DIR}" \
|
||||
"${PI_PYTHON}" -m src.main \
|
||||
--model-config "${MODEL_CONFIG}" \
|
||||
--history-config-path "${HISTORY_CONFIG}" \
|
||||
--mode run --user-id "${PERSONA}" \
|
||||
--workspace-dir "${NANOBOT_WORKSPACE_DIR}" \
|
||||
${TASK_ARGS[@]+"${TASK_ARGS[@]}"} \
|
||||
2>&1 | tee "${LOG_DIR}/runner_run_${PERSONA}.log"
|
||||
RUN_EXIT=${PIPESTATUS[0]}
|
||||
if [ ${RUN_EXIT} -ne 0 ]; then
|
||||
echo "Run phase failed (exit ${RUN_EXIT}). Logs: ${LOG_DIR}/"
|
||||
exit ${RUN_EXIT}
|
||||
fi
|
||||
else
|
||||
echo "[5/5] Runner: run phase skipped (all tasks completed)"
|
||||
fi
|
||||
|
||||
if [ "$SKIP_EVAL" = true ]; then
|
||||
echo "Skipping eval (--skip-eval)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ─── Trace conversion + eval phase (always over all available traces) ──
|
||||
echo "Converting trace logs..."
|
||||
"${PI_PYTHON}" "${SUITE_DIR}/fix_trace_logs.py" "${PERSONA}"
|
||||
|
||||
echo "Runner: eval phase"
|
||||
cd "${SUITE_DIR}"
|
||||
BENCH_TEST_SERVER_URL="${TEST_URL}" PYTHONPATH="${PIBENCH_DIR}" \
|
||||
"${PI_PYTHON}" -m src.main \
|
||||
--model-config "${MODEL_CONFIG}" \
|
||||
--history-config-path "${HISTORY_CONFIG}" \
|
||||
--mode eval --user-id "${PERSONA}" \
|
||||
--workspace-dir "${NANOBOT_WORKSPACE_DIR}" \
|
||||
2>&1 | tee "${LOG_DIR}/runner_eval_${PERSONA}.log"
|
||||
EVAL_EXIT=${PIPESTATUS[0]}
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "persona=${PERSONA} finished (eval exit=${EVAL_EXIT})"
|
||||
echo " results : ${SUITE_DIR}/outputs/reme/${PERSONA}/"
|
||||
echo " memory : ${WORKSPACE_DIR}/"
|
||||
echo " logs : ${LOG_DIR}/"
|
||||
echo "========================================="
|
||||
exit ${EVAL_EXIT}
|
||||
98
benchmark/toolmemory/README.md
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
## Towards Robust Tool Use in Agents via Experience-Driven Adaptive Guidance
|
||||
|
||||
**Language**: English (default) / [中文](./README_ZH.md)
|
||||
|
||||
> Paper: [arXiv:2608.03403](https://arxiv.org/abs/2608.03403)
|
||||
> 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%">
|
||||
</p>
|
||||
|
||||
### Overview
|
||||
|
||||
This folder archives **ExpG**, a tool-use enhancement built on [Agentscope ReMe](https://github.com/agentscope-ai/ReMe). ExpG mines, distills, and reuses experience from historical tool calls to provide **capability boundaries** and **best-practice guidance**, which helps agents:
|
||||
|
||||
- Select and invoke tools more robustly under dynamic or noisy environments;
|
||||
- Let smaller models with guidance outperform larger, memoryless baselines;
|
||||
- Improve consistently across tool selection, tool calling, and response generation.
|
||||
|
||||
**How ReMe is used:** Start the Tool Memory service; historical tool calls are written and evaluated via `add_tool_call_result`, distilled into tool-level guidance via `summary_tool_memory`, then retrieved and injected into later reasoning via `retrieve_tool_memory`. ReMe provides the vector store and service APIs; the acquisition / distillation / reuse strategy is implemented by ExpG. Full implementation and experiments are in [WangCan1178/ExpG](https://github.com/WangCan1178/ExpG).
|
||||
|
||||
---
|
||||
|
||||
### ExpG Mechanism
|
||||
|
||||
ExpG treats tool invocations as learnable experience and runs a three-stage pipeline:
|
||||
|
||||
1. **Experience Acquisition**
|
||||
- Analyze invocation quality from historical trajectories (success/failure, cost, latency, etc.);
|
||||
- Build structured experience units per tool, recording context, parameter patterns, and outcomes.
|
||||
|
||||
2. **Experience Distillation**
|
||||
- Filter noisy or unhelpful experiences and keep representative patterns;
|
||||
- Aggregate by equivalence classes to cover common and rare failure modes;
|
||||
- Summarize with an LLM into generalizable textual guidance.
|
||||
|
||||
3. **Experience Reuse**
|
||||
- Retrieve relevant experience / guidance for future tasks;
|
||||
- Inject guidance into tool selection, argument generation, and response synthesis;
|
||||
- Improve stability under dynamic environments and imperfect feedback.
|
||||
|
||||
---
|
||||
|
||||
### Main Results
|
||||
|
||||
Performance comparison (%) across MetaTool, API-Bank, and BFCL-V3. **Bold** indicates the best results within each model.
|
||||
|
||||
| Model | Method | MetaTool Pass@1 | MetaTool Avg@3 | MetaTool Pass@3 | API-Bank Pass@1 | API-Bank Avg@3 | API-Bank Pass@3 | BFCL-V3 Pass@1 | BFCL-V3 Avg@3 | BFCL-V3 Pass@3 | Total Pass@1 | Total Avg@3 | Total Pass@3 |
|
||||
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| GPT-5 nano | No Method | 72.62 | 72.76 | 78.49 | 82.96 | 83.46 | 86.97 | 53.80 | 53.00 | 60.95 | 70.82 | 70.62 | 76.63 |
|
||||
| GPT-5 nano | Few-shot | 74.12 | 75.11 | 82.32 | 83.71 | 83.96 | **87.22** | 56.18 | 55.24 | 61.39 | 72.36 | 72.65 | 79.28 |
|
||||
| GPT-5 nano | DRAFT | 73.94 | 73.04 | 78.97 | 84.21 | 83.46 | **87.22** | 57.27 | 57.27 | 62.26 | 72.52 | 71.58 | 77.23 |
|
||||
| GPT-5 nano | Mem0 | 74.96 | 76.13 | 82.92 | 84.96 | 85.21 | **87.22** | 60.95 | 61.61 | 65.08 | 73.98 | 74.67 | 80.35 |
|
||||
| GPT-5 nano | **ExpG** | **81.67** | **82.07** | **84.60** | **86.72** | **86.55** | **87.22** | **64.43** | **63.99** | **66.38** | **79.32** | **79.22** | **81.69** |
|
||||
| DeepSeek-V3 | No Method | 83.10 | 82.94 | 84.66 | 84.71 | 84.38 | 85.46 | 58.79 | 59.65 | 65.94 | 78.92 | 78.66 | 81.37 |
|
||||
| DeepSeek-V3 | Few-shot | 82.74 | 83.90 | 86.28 | 85.21 | 84.63 | 86.22 | 60.52 | 60.30 | 67.90 | 79.08 | 79.45 | 82.92 |
|
||||
| DeepSeek-V3 | DRAFT | 80.23 | 80.79 | 82.44 | 84.96 | 85.63 | 86.47 | 62.26 | 61.61 | 68.55 | 77.70 | 77.80 | 80.54 |
|
||||
| DeepSeek-V3 | Mem0 | 83.88 | 84.56 | 86.40 | 85.46 | 85.55 | 86.47 | 65.08 | 65.15 | 68.33 | 80.70 | 80.91 | 83.12 |
|
||||
| DeepSeek-V3 | **ExpG** | **85.26** | **85.38** | **86.52** | **87.72** | **87.39** | **87.97** | **69.41** | **69.92** | **72.02** | **82.76** | **82.61** | **84.11** |
|
||||
| Qwen3-8B | No Method | 76.51 | 76.97 | 77.71 | 83.96 | 83.88 | 84.21 | 58.79 | 58.28 | 60.30 | 74.46 | 74.41 | 75.56 |
|
||||
| Qwen3-8B | Few-shot | 79.93 | 79.83 | 82.92 | 83.71 | 82.62 | 84.96 | 60.09 | 59.29 | 61.39 | 76.91 | 76.27 | 79.32 |
|
||||
| Qwen3-8B | DRAFT | 78.19 | 77.33 | 77.89 | 85.71 | 84.96 | 85.46 | 60.74 | 60.30 | 62.91 | 76.20 | 75.18 | 76.35 |
|
||||
| Qwen3-8B | Mem0 | 75.07 | 75.47 | 82.38 | 86.22 | 86.05 | 86.47 | 63.34 | 64.93 | 66.16 | 74.69 | 74.98 | 80.07 |
|
||||
| Qwen3-8B | **ExpG** | **83.52** | **84.88** | **85.08** | **86.47** | **87.89** | **87.97** | **67.46** | **66.96** | **68.33** | **81.06** | **81.82** | **82.48** |
|
||||
| Qwen3-32B | No Method | 80.05 | 79.43 | 80.17 | 84.71 | 84.88 | 85.21 | 65.15 | 65.08 | 66.16 | 78.05 | 77.55 | 78.41 |
|
||||
| Qwen3-32B | **ExpG** | **84.68** | **85.02** | **86.28** | **86.97** | **87.30** | **87.72** | **70.72** | **71.01** | **73.32** | **82.48** | **82.56** | **84.14** |
|
||||
| Qwen3-235B | No Method | 78.25 | 79.23 | 80.29 | 85.46 | 85.46 | 85.71 | 71.37 | 71.15 | 73.54 | 78.13 | 78.49 | 79.91 |
|
||||
| Qwen3-235B | **ExpG** | **86.34** | **86.70** | **86.94** | **87.47** | **86.97** | **88.22** | **79.61** | **78.52** | **80.04** | **85.29** | **84.98** | **85.69** |
|
||||
|
||||
---
|
||||
|
||||
### Reference Code
|
||||
|
||||
| Path | Role |
|
||||
| --- | --- |
|
||||
| [`tool_memory.py`](./tool_memory.py) | HTTP client for official ReMe Tool Memory APIs (`add_tool_call_result` / `summary_tool_memory` / `retrieve_tool_memory`) |
|
||||
| [`parse_tool_call_result_prompt.yaml`](./parse_tool_call_result_prompt.yaml) | Prompt for multi-aspect evaluation of each tool call |
|
||||
| [`summary_tool_memory_prompt.yaml`](./summary_tool_memory_prompt.yaml) | Prompt for summarizing tool call history into guidance |
|
||||
| [`tool_memory_flows.yaml`](./tool_memory_flows.yaml) | Tool Memory flow / op config excerpt |
|
||||
|
||||
These are reference snippets. For the full runnable codebase, see [WangCan1178/ExpG](https://github.com/WangCan1178/ExpG).
|
||||
|
||||
---
|
||||
|
||||
### Citation
|
||||
|
||||
```bibtex
|
||||
@misc{wang2026expg,
|
||||
title = {Towards Robust Tool Use in Agents via Experience-Driven Adaptive Guidance},
|
||||
author = {Can Wang and Haoran Chen and Li Yu and Ding Hao and Bohai Zhao and Zhaoyang Liu and Zhiying Tu},
|
||||
year = {2026},
|
||||
eprint = {2608.03403},
|
||||
archivePrefix = {arXiv},
|
||||
primaryClass = {cs.AI},
|
||||
url = {https://arxiv.org/abs/2608.03403},
|
||||
howpublished = {\url{https://github.com/WangCan1178/ExpG}}
|
||||
}
|
||||
```
|
||||
98
benchmark/toolmemory/README_ZH.md
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
## Towards Robust Tool Use in Agents via Experience-Driven Adaptive Guidance
|
||||
|
||||
**语言**:中文 / [English](./README.md)
|
||||
|
||||
> 论文:[arXiv:2608.03403](https://arxiv.org/abs/2608.03403)
|
||||
> 代码:[https://github.com/WangCan1178/ExpG](https://github.com/WangCan1178/ExpG)
|
||||
|
||||
<p align="center">
|
||||
<img src="gitcha.png" alt="ExpG 挑战与概览" width="85%">
|
||||
</p>
|
||||
|
||||
### 简介
|
||||
|
||||
本目录归档基于 [Agentscope ReMe](https://github.com/agentscope-ai/ReMe) 的工具使用增强工作 **ExpG**:在 ReMe 记忆框架之上,从历史工具调用中挖掘、提炼并复用经验,为智能体提供工具的 **能力边界** 与 **最佳实践指导**,从而:
|
||||
|
||||
- 在动态或有噪环境下更鲁棒地选择和调用工具;
|
||||
- 让较小模型在带有经验指导时超越更大、但无记忆的基线;
|
||||
- 在工具选择、工具调用和响应生成等多个阶段带来一致收益。
|
||||
|
||||
**如何使用 ReMe:** 启动 Tool Memory 服务后,历史工具调用经 `add_tool_call_result` 写入并评估,经 `summary_tool_memory` 蒸馏成工具级指导,再经 `retrieve_tool_memory` 取回并注入后续推理。向量存储与服务接口由 ReMe 提供,经验获取 / 蒸馏 / 复用策略由 ExpG 实现。完整实现与实验见 [WangCan1178/ExpG](https://github.com/WangCan1178/ExpG)。
|
||||
|
||||
---
|
||||
|
||||
### ExpG 机制概览
|
||||
|
||||
ExpG 将工具调用视为可学习经验,并通过三阶段流水线完成经验的获取、提炼与复用:
|
||||
|
||||
1. **经验获取(Experience Acquisition)**
|
||||
- 从历史工具调用轨迹中分析调用质量(成功/失败、代价、时间等);
|
||||
- 针对不同工具构建结构化的经验单元,记录调用上下文、参数模式和结果。
|
||||
|
||||
2. **经验蒸馏(Experience Distillation)**
|
||||
- 过滤无效 / 噪声经验,保留具有代表性的调用模式;
|
||||
- 基于“等价类”视角对经验进行聚合,覆盖常见模式与稀有失败模式;
|
||||
- 使用 LLM 对经验进行总结,形成可泛化的文本化指导(guidance)。
|
||||
|
||||
3. **经验复用(Experience Reuse)**
|
||||
- 在未来任务中,根据当前工具调用上下文检索相关经验 / 指导;
|
||||
- 将经验引导融入到工具选择、参数生成和响应整理等环节;
|
||||
- 使得代理在面对动态环境和不完美反馈时仍能保持稳定表现。
|
||||
|
||||
---
|
||||
|
||||
### 主实验结果
|
||||
|
||||
MetaTool、API-Bank、BFCL-V3 上的性能对比(%)。**加粗**为各模型组内最优。
|
||||
|
||||
| Model | Method | MetaTool Pass@1 | MetaTool Avg@3 | MetaTool Pass@3 | API-Bank Pass@1 | API-Bank Avg@3 | API-Bank Pass@3 | BFCL-V3 Pass@1 | BFCL-V3 Avg@3 | BFCL-V3 Pass@3 | Total Pass@1 | Total Avg@3 | Total Pass@3 |
|
||||
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| GPT-5 nano | No Method | 72.62 | 72.76 | 78.49 | 82.96 | 83.46 | 86.97 | 53.80 | 53.00 | 60.95 | 70.82 | 70.62 | 76.63 |
|
||||
| GPT-5 nano | Few-shot | 74.12 | 75.11 | 82.32 | 83.71 | 83.96 | **87.22** | 56.18 | 55.24 | 61.39 | 72.36 | 72.65 | 79.28 |
|
||||
| GPT-5 nano | DRAFT | 73.94 | 73.04 | 78.97 | 84.21 | 83.46 | **87.22** | 57.27 | 57.27 | 62.26 | 72.52 | 71.58 | 77.23 |
|
||||
| GPT-5 nano | Mem0 | 74.96 | 76.13 | 82.92 | 84.96 | 85.21 | **87.22** | 60.95 | 61.61 | 65.08 | 73.98 | 74.67 | 80.35 |
|
||||
| GPT-5 nano | **ExpG** | **81.67** | **82.07** | **84.60** | **86.72** | **86.55** | **87.22** | **64.43** | **63.99** | **66.38** | **79.32** | **79.22** | **81.69** |
|
||||
| DeepSeek-V3 | No Method | 83.10 | 82.94 | 84.66 | 84.71 | 84.38 | 85.46 | 58.79 | 59.65 | 65.94 | 78.92 | 78.66 | 81.37 |
|
||||
| DeepSeek-V3 | Few-shot | 82.74 | 83.90 | 86.28 | 85.21 | 84.63 | 86.22 | 60.52 | 60.30 | 67.90 | 79.08 | 79.45 | 82.92 |
|
||||
| DeepSeek-V3 | DRAFT | 80.23 | 80.79 | 82.44 | 84.96 | 85.63 | 86.47 | 62.26 | 61.61 | 68.55 | 77.70 | 77.80 | 80.54 |
|
||||
| DeepSeek-V3 | Mem0 | 83.88 | 84.56 | 86.40 | 85.46 | 85.55 | 86.47 | 65.08 | 65.15 | 68.33 | 80.70 | 80.91 | 83.12 |
|
||||
| DeepSeek-V3 | **ExpG** | **85.26** | **85.38** | **86.52** | **87.72** | **87.39** | **87.97** | **69.41** | **69.92** | **72.02** | **82.76** | **82.61** | **84.11** |
|
||||
| Qwen3-8B | No Method | 76.51 | 76.97 | 77.71 | 83.96 | 83.88 | 84.21 | 58.79 | 58.28 | 60.30 | 74.46 | 74.41 | 75.56 |
|
||||
| Qwen3-8B | Few-shot | 79.93 | 79.83 | 82.92 | 83.71 | 82.62 | 84.96 | 60.09 | 59.29 | 61.39 | 76.91 | 76.27 | 79.32 |
|
||||
| Qwen3-8B | DRAFT | 78.19 | 77.33 | 77.89 | 85.71 | 84.96 | 85.46 | 60.74 | 60.30 | 62.91 | 76.20 | 75.18 | 76.35 |
|
||||
| Qwen3-8B | Mem0 | 75.07 | 75.47 | 82.38 | 86.22 | 86.05 | 86.47 | 63.34 | 64.93 | 66.16 | 74.69 | 74.98 | 80.07 |
|
||||
| Qwen3-8B | **ExpG** | **83.52** | **84.88** | **85.08** | **86.47** | **87.89** | **87.97** | **67.46** | **66.96** | **68.33** | **81.06** | **81.82** | **82.48** |
|
||||
| Qwen3-32B | No Method | 80.05 | 79.43 | 80.17 | 84.71 | 84.88 | 85.21 | 65.15 | 65.08 | 66.16 | 78.05 | 77.55 | 78.41 |
|
||||
| Qwen3-32B | **ExpG** | **84.68** | **85.02** | **86.28** | **86.97** | **87.30** | **87.72** | **70.72** | **71.01** | **73.32** | **82.48** | **82.56** | **84.14** |
|
||||
| Qwen3-235B | No Method | 78.25 | 79.23 | 80.29 | 85.46 | 85.46 | 85.71 | 71.37 | 71.15 | 73.54 | 78.13 | 78.49 | 79.91 |
|
||||
| Qwen3-235B | **ExpG** | **86.34** | **86.70** | **86.94** | **87.47** | **86.97** | **88.22** | **79.61** | **78.52** | **80.04** | **85.29** | **84.98** | **85.69** |
|
||||
|
||||
---
|
||||
|
||||
### 参考代码
|
||||
|
||||
| 路径 | 作用 |
|
||||
| --- | --- |
|
||||
| [`tool_memory.py`](./tool_memory.py) | 官方风格 ReMe Tool Memory HTTP 客户端(`add_tool_call_result` / `summary_tool_memory` / `retrieve_tool_memory`) |
|
||||
| [`parse_tool_call_result_prompt.yaml`](./parse_tool_call_result_prompt.yaml) | 单次工具调用多维评估用的 prompt |
|
||||
| [`summary_tool_memory_prompt.yaml`](./summary_tool_memory_prompt.yaml) | 将工具调用历史总结为 guidance 的 prompt |
|
||||
| [`tool_memory_flows.yaml`](./tool_memory_flows.yaml) | Tool Memory 相关的 flow / op 配置摘录 |
|
||||
|
||||
以上为参考片段。完整可运行代码见 [WangCan1178/ExpG](https://github.com/WangCan1178/ExpG)。
|
||||
|
||||
---
|
||||
|
||||
### 引用
|
||||
|
||||
```bibtex
|
||||
@misc{wang2026expg,
|
||||
title = {Towards Robust Tool Use in Agents via Experience-Driven Adaptive Guidance},
|
||||
author = {Can Wang and Haoran Chen and Li Yu and Ding Hao and Bohai Zhao and Zhaoyang Liu and Zhiying Tu},
|
||||
year = {2026},
|
||||
eprint = {2608.03403},
|
||||
archivePrefix = {arXiv},
|
||||
primaryClass = {cs.AI},
|
||||
url = {https://arxiv.org/abs/2608.03403},
|
||||
howpublished = {\url{https://github.com/WangCan1178/ExpG}}
|
||||
}
|
||||
```
|
||||
BIN
benchmark/toolmemory/gitcha.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
49
benchmark/toolmemory/parse_tool_call_result_prompt.yaml
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
prompt: |
|
||||
You are an expert in evaluating tool invocation process. The tool is invoked by an AI agent.
|
||||
|
||||
Tool invocation Information:
|
||||
- Tool Name: {tool_name}
|
||||
- Success Flag: {success_flag}
|
||||
- Time Cost: {time_cost}s
|
||||
- Token Cost: {token_cost} tokens
|
||||
- Agent Context: {context}
|
||||
- Input Parameters: {input_params}
|
||||
- Tool Response: {response}
|
||||
- Tool Schema: {schema}
|
||||
|
||||
Evaluation Method:
|
||||
Start from a default score list of scores = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0].
|
||||
For each item below that is satisfied, assign 1 point to the corresponding index.
|
||||
The final scores should be a list of 10 integers, each being either 0 or 1.
|
||||
|
||||
1. Use Quality (total 2 points. If context is provided, use it as an aid when evaluating):
|
||||
- Index 1: Should the tool be invoked now? Consider whether all necessary information for the tool's invocation is ready, and whether the tool execution environment is correct. If it is a multi-round conversation, also consider the dependency relationships of the tool chain.
|
||||
- Index 2: If should, is the chosen tool appropriate?
|
||||
|
||||
2. Input Quality (total 4 points. When evaluating, consider both the context and the tool schema):
|
||||
- Index 3: Are all required parameters provided?
|
||||
- Index 4: Are the input parameters valid and supported by the tool?
|
||||
- Index 5: Are the input parameters in the correct format for their respective fields?
|
||||
- Index 6: Does the value (content) of input parameter correctly reflect and match the given context?
|
||||
|
||||
3. Response Quality (total 4 points):
|
||||
- Index 7: Does the response provide meaningful and useful information? Or are there any error messages or information that can be used as guidance for agent invoking tool better?
|
||||
- Index 8: Does the response match the tool's intended purpose/function?
|
||||
- Index 9: Does the response value correct (content appropriate) given the input parameters?
|
||||
- Index 10: Does the response help accomplish the task within the given context?
|
||||
|
||||
Important:
|
||||
1. Sometimes there is not enough information in the context or schema to make a complete evaluation. In such cases, make your best judgment based on the available information.
|
||||
2. Some tools (commonly system tools such as mkdir, touch, echo, etc.) modify the external environment. Since these results cannot be obtained, they return "None" as the response. At this point, all the scores in the quality of the response should be obtained and should not be seen as a problem for the tool.
|
||||
3. Evaluation independently from the success flag. The success_flag indicates whether the tool executed without technical errors. The evaluation should evaluate the quality of the tool invocation. A tool can execute successfully (Success Flag=1) but still produce low-quality or irrelevant responses, leading to a low evaluation score.
|
||||
4. Sometimes an agent will execute multiple steps and invoke multiple tools to complete a task, but you only need to evaluate the use of one tool for one of the steps, not whether the final task is completed or not.
|
||||
|
||||
Answer Format:
|
||||
Please provide your answer in the following JSON format:
|
||||
|
||||
```json
|
||||
{
|
||||
"scores": [0,0,0,0,0,0,0,0,0,0],
|
||||
"explanation": "A brief evaluation (2-3 sentences) explaining the quality of the tool invocation, based on your evaluation. Low-quality aspects need to be reified, especially the causes of tool invocation errors."
|
||||
}
|
||||
```
|
||||
32
benchmark/toolmemory/summary_tool_memory_prompt.yaml
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
prompt: |
|
||||
You are an expert in analyzing tool usage patterns and generating practical usage guidance for agents.
|
||||
|
||||
Tool Information:
|
||||
- Tool Name: {tool_name}
|
||||
- Tool Schema: {tool_schema}
|
||||
|
||||
Recent Tool Invocation Experiences:
|
||||
{experiences}
|
||||
|
||||
Important:
|
||||
1. Assume the tool (tool schema) can't be changed, your task is to guide agent to use it better.
|
||||
2. Your answer must be based on the information given, don't make it up. If not enough data, state "Not enough data to determine Core Function/Success Patterns/Common Issues/Best Practices."
|
||||
3. Your answer will be used to guide the use of the tool in the future, so do not include content related to recent tool invocation experience such as "case #3" or "Call #2", but some values can be used as examples.
|
||||
4. Pay attention to information not mentioned in the tool schema, such as the response upon successful tool invocation. It's also welcome to uncover insights, such as how tools can be used more effectively, and possible dependencies between tools. But if they aren't, don't make them up.
|
||||
5. Finally, to avoid deriving incorrect guidance from individual invocation, check whether, if the agent follows the proposed guidance, it can perform better on all recent invocation histories. If not, revise the guidance until it can. Specifically:
|
||||
- Don't write guidance in an absolute tone without a very deterministic message (meaning that all invocation histories are satisfied, otherwise it will result in failure).
|
||||
- Sometimes there may be inconsistencies. Consider whether this is due to the context in which the tool is being used.
|
||||
|
||||
Your Task:
|
||||
Based on the tool invocation history, generate a concise and logical tool usage guidance following this structure:
|
||||
1. Core Function: What this tool does and when to use it.
|
||||
2. Success Patterns: Parameter patterns and usage scenarios that work well.
|
||||
3. Common Issues: Main pitfalls to avoid and why they fail.
|
||||
4. Best Practices: 2-3 actionable recommendations.
|
||||
|
||||
Answer Format:
|
||||
Provide a structured, concise guidance (max 200 words). Focus on actionable insights derived from actual usage data. Avoid generic advice and think step by step.
|
||||
|
||||
```txt
|
||||
Your concise, data-driven tool usage guidance
|
||||
```
|
||||
234
benchmark/toolmemory/tool_memory.py
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
"""Official-style ReMe Tool Memory HTTP helpers.
|
||||
|
||||
Aligned with ReMe Tool Memory HTTP APIs (see ReMe cookbook
|
||||
``use_tool_memory_demo.py`` and docs under ``docs/tool_memory/``):
|
||||
|
||||
- ``add_tool_call_result``
|
||||
- ``summary_tool_memory``
|
||||
- ``retrieve_tool_memory``
|
||||
|
||||
Response memories are read from ``metadata.memory_list[].content``.
|
||||
This module does not use ExpG-only fields such as ``no_persist``,
|
||||
``source_task``, or ``add_to``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import httpx
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_BASE_URL = "http://localhost:8002"
|
||||
|
||||
|
||||
class ToolMemoryFetcher:
|
||||
"""HTTP client for ReMe Tool Memory endpoints."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
workspace_id: str,
|
||||
base_url: str = DEFAULT_BASE_URL,
|
||||
timeout: float = 60.0,
|
||||
) -> None:
|
||||
self.workspace_id = workspace_id
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.timeout = timeout
|
||||
|
||||
def _url(self, endpoint: str) -> str:
|
||||
return f"{self.base_url}/{endpoint.lstrip('/')}"
|
||||
|
||||
@staticmethod
|
||||
def _join_tool_names(tool_names: List[str] | str) -> str:
|
||||
if isinstance(tool_names, str):
|
||||
return tool_names
|
||||
return ",".join(tool_names)
|
||||
|
||||
@staticmethod
|
||||
def _memory_list(payload: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
metadata = payload.get("metadata") or {}
|
||||
if not isinstance(metadata, dict):
|
||||
return []
|
||||
memory_list = metadata.get("memory_list") or []
|
||||
return memory_list if isinstance(memory_list, list) else []
|
||||
|
||||
@classmethod
|
||||
def _content_by_tool(cls, payload: Dict[str, Any]) -> Dict[str, str]:
|
||||
result: Dict[str, str] = {}
|
||||
for memory in cls._memory_list(payload):
|
||||
if not isinstance(memory, dict):
|
||||
continue
|
||||
tool_name = str(memory.get("when_to_use") or "").strip()
|
||||
content = memory.get("content") or ""
|
||||
if tool_name:
|
||||
result[tool_name] = str(content)
|
||||
return result
|
||||
|
||||
async def add_tool_call_result_async(
|
||||
self,
|
||||
tool_call_results: List[Dict[str, Any]],
|
||||
) -> Dict[str, Any]:
|
||||
"""Call ``add_tool_call_result``."""
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.post(
|
||||
self._url("add_tool_call_result"),
|
||||
json={
|
||||
"workspace_id": self.workspace_id,
|
||||
"tool_call_results": tool_call_results,
|
||||
},
|
||||
timeout=self.timeout,
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def summary_tool_memory_async(
|
||||
self,
|
||||
tool_names: List[str] | str,
|
||||
) -> Dict[str, Any]:
|
||||
"""Call ``summary_tool_memory``."""
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.post(
|
||||
self._url("summary_tool_memory"),
|
||||
json={
|
||||
"workspace_id": self.workspace_id,
|
||||
"tool_names": self._join_tool_names(tool_names),
|
||||
},
|
||||
timeout=self.timeout,
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def retrieve_tool_memory_async(
|
||||
self,
|
||||
tool_names: List[str] | str,
|
||||
) -> Dict[str, Any]:
|
||||
"""Call ``retrieve_tool_memory``."""
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.post(
|
||||
self._url("retrieve_tool_memory"),
|
||||
json={
|
||||
"workspace_id": self.workspace_id,
|
||||
"tool_names": self._join_tool_names(tool_names),
|
||||
},
|
||||
timeout=self.timeout,
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def collect_memory_async(
|
||||
self,
|
||||
tool_names: List[str],
|
||||
) -> Dict[str, str]:
|
||||
"""Summarize then retrieve guidance for tools.
|
||||
|
||||
Returns:
|
||||
Mapping from tool name to memory ``content`` string.
|
||||
"""
|
||||
if not tool_names:
|
||||
return {}
|
||||
|
||||
names = self._join_tool_names(tool_names)
|
||||
try:
|
||||
summary = await self.summary_tool_memory_async(names)
|
||||
if not summary.get("success"):
|
||||
logger.warning("summary_tool_memory failed for %s", names)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.warning("summary_tool_memory error for %s: %s", names, exc)
|
||||
|
||||
try:
|
||||
retrieved = await self.retrieve_tool_memory_async(names)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.warning("retrieve_tool_memory error for %s: %s", names, exc)
|
||||
return {}
|
||||
|
||||
if not retrieved.get("success"):
|
||||
logger.warning("retrieve_tool_memory failed for %s", names)
|
||||
return {}
|
||||
|
||||
return self._content_by_tool(retrieved)
|
||||
|
||||
def add_tool_call_result(
|
||||
self,
|
||||
tool_call_results: List[Dict[str, Any]],
|
||||
) -> Dict[str, Any]:
|
||||
"""Sync wrapper for ``add_tool_call_result``."""
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
self._url("add_tool_call_result"),
|
||||
json={
|
||||
"workspace_id": self.workspace_id,
|
||||
"tool_call_results": tool_call_results,
|
||||
},
|
||||
timeout=self.timeout,
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def summary_tool_memory(self, tool_names: List[str] | str) -> Dict[str, Any]:
|
||||
"""Sync wrapper for ``summary_tool_memory``."""
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
self._url("summary_tool_memory"),
|
||||
json={
|
||||
"workspace_id": self.workspace_id,
|
||||
"tool_names": self._join_tool_names(tool_names),
|
||||
},
|
||||
timeout=self.timeout,
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def retrieve_tool_memory(self, tool_names: List[str] | str) -> Dict[str, Any]:
|
||||
"""Sync wrapper for ``retrieve_tool_memory``."""
|
||||
with httpx.Client() as client:
|
||||
response = client.post(
|
||||
self._url("retrieve_tool_memory"),
|
||||
json={
|
||||
"workspace_id": self.workspace_id,
|
||||
"tool_names": self._join_tool_names(tool_names),
|
||||
},
|
||||
timeout=self.timeout,
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def collect_memory(self, tool_names: List[str]) -> Dict[str, str]:
|
||||
"""Sync wrapper for summarize + retrieve.
|
||||
|
||||
Prefer ``collect_memory_async`` inside an existing event loop.
|
||||
"""
|
||||
if not tool_names:
|
||||
return {}
|
||||
|
||||
names = self._join_tool_names(tool_names)
|
||||
try:
|
||||
summary = self.summary_tool_memory(names)
|
||||
if not summary.get("success"):
|
||||
logger.warning("summary_tool_memory failed for %s", names)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.warning("summary_tool_memory error for %s: %s", names, exc)
|
||||
|
||||
try:
|
||||
retrieved = self.retrieve_tool_memory(names)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.warning("retrieve_tool_memory error for %s: %s", names, exc)
|
||||
return {}
|
||||
|
||||
if not retrieved.get("success"):
|
||||
logger.warning("retrieve_tool_memory failed for %s", names)
|
||||
return {}
|
||||
|
||||
return self._content_by_tool(retrieved)
|
||||
|
||||
def get_memory_content(
|
||||
self,
|
||||
tool_names: List[str] | str,
|
||||
) -> Optional[str]:
|
||||
"""Retrieve and join memory contents for the given tools."""
|
||||
payload = self.retrieve_tool_memory(tool_names)
|
||||
if not payload.get("success"):
|
||||
return None
|
||||
contents = [content for content in self._content_by_tool(payload).values() if content]
|
||||
return "\n\n".join(contents) if contents else None
|
||||
45
benchmark/toolmemory/tool_memory_flows.yaml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# Tool Memory flow / op config excerpt used by ExpG.
|
||||
# Full runnable code: https://github.com/WangCan1178/ExpG
|
||||
|
||||
flow:
|
||||
retrieve_tool_memory:
|
||||
flow_content: retrieve_tool_memory_op
|
||||
description: "Retrieves tool memories from the vector database based on tool names to provide tool usage patterns and best practices"
|
||||
input_schema:
|
||||
tool_names:
|
||||
type: string
|
||||
description: "Comma-separated tool names (e.g., 'tool_name1,tool_name2')"
|
||||
required: true
|
||||
|
||||
add_tool_call_result:
|
||||
flow_content: parse_tool_call_result_op >> update_vector_store_op
|
||||
description: "Evaluates and adds tool call results to the tool memory database, creating new memory or updating existing memory for the specified tool"
|
||||
input_schema:
|
||||
tool_call_results:
|
||||
type: array
|
||||
description: "List of tool call result objects, each containing: tool_name, input, output, success, time_cost, token_cost, create_time"
|
||||
required: true
|
||||
|
||||
summary_tool_memory:
|
||||
flow_content: summary_tool_memory_op >> update_vector_store_op
|
||||
description: "Analyzes tool call history and generates comprehensive usage patterns, best practices, and recommendations for the specified tools"
|
||||
input_schema:
|
||||
tool_names:
|
||||
type: string
|
||||
description: "Comma-separated tool names to summarize (e.g., 'tool_name1,tool_name2')"
|
||||
required: true
|
||||
|
||||
op:
|
||||
parse_tool_call_result_op:
|
||||
backend: parse_tool_call_result_op
|
||||
llm: default
|
||||
params:
|
||||
max_history_tool_call_cnt: 100
|
||||
evaluation_sleep_interval: 1.0
|
||||
|
||||
summary_tool_memory_op:
|
||||
backend: summary_tool_memory_op
|
||||
llm: default
|
||||
params:
|
||||
data_from: '2025-09-10 10:56:58'
|
||||
summary_sleep_interval: 1.0
|
||||
238
docs/en/auto_dream.md
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
# Auto Dream
|
||||
|
||||
`auto_dream` is ReMe's long-term memory distillation flow from daily to digest. By default it scans the target date and
|
||||
the previous day, processes only files changed since the previous dream, extracts a small set of high-value memory units
|
||||
across that window, integrates them into `digest/`, and writes the target day's `interests.yaml` for proactive use.
|
||||
|
||||
<p align="center">
|
||||
<img src="../figure/auto-dream-and-proactive.svg" alt="ReMe Auto Dream and Proactive flow from daily to digest to proactive" width="92%">
|
||||
</p>
|
||||
|
||||
Its daily inputs usually come from [Auto Memory](./auto_memory.md) and [Auto Resource](./auto_resource.md). For the file
|
||||
semantics of `digest/`, Sources sections, and wikilinks, see [Memory as File](./memory_as_file.md). For the linking
|
||||
strategy used during Integrate, see [Auto Link](./auto_link.md). To read `interests.yaml`,
|
||||
use [Proactive](./proactive.md).
|
||||
|
||||
## Configuration
|
||||
|
||||
The default configuration is in `reme/config/default.yaml`:
|
||||
|
||||
```yaml
|
||||
auto_dream:
|
||||
backend: base
|
||||
parameters:
|
||||
date:
|
||||
type: string
|
||||
default: ""
|
||||
hint:
|
||||
type: string
|
||||
default: ""
|
||||
scan_days:
|
||||
type: integer
|
||||
default: 2
|
||||
max_units:
|
||||
type: integer
|
||||
default: 5
|
||||
topic_count:
|
||||
type: integer
|
||||
default: 3
|
||||
topic_diversity_days:
|
||||
type: integer
|
||||
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
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
| Parameter | Purpose |
|
||||
|------------------------|---------------------------------------------------------------------------------------------------------|
|
||||
| `date` | Date to process in `YYYY-MM-DD` format. When empty, use today in the application's timezone. |
|
||||
| `hint` | Additional guidance from the caller for the Extract and Integrate stages. |
|
||||
| `scan_days` | Recent-date window ending at `date`; defaults to 2 and has a minimum of 1. |
|
||||
| `max_units` | Maximum reusable units extracted in one run; defaults to 5. |
|
||||
| `topic_count` | Maximum number of topics written to `interests.yaml`. Defaults to 3. |
|
||||
| `topic_diversity_days` | Number of past days of `interests.yaml` files considered when avoiding duplicate topics. Defaults to 7. |
|
||||
|
||||
## Inputs and Outputs
|
||||
|
||||
Inputs are daily Markdown files from the most recent `scan_days` ending at the specified date. For example,
|
||||
`date=2026-06-20` with `scan_days=2` scans:
|
||||
|
||||
```text
|
||||
daily/2026-06-19.md
|
||||
daily/2026-06-19/**/*.md
|
||||
daily/2026-06-20.md
|
||||
daily/2026-06-20/**/*.md
|
||||
```
|
||||
|
||||
Every `daily/<date>/interests.yaml` in the scan window is excluded from extraction so previous proactive output cannot
|
||||
feed back into the next run. Final topics are written only for the target date.
|
||||
|
||||
The main outputs are:
|
||||
|
||||
| Output | Description |
|
||||
|--------------------------------|------------------------------------------------------------------------------|
|
||||
| `digest/procedure/*.md` | Methods, workflows, runbooks, and executable experience. |
|
||||
| `digest/personal/*.md` | User-, team-, and project-related preferences, facts, and long-term context. |
|
||||
| `digest/wiki/*.md` | General knowledge, concepts, observations, and decision precedents. |
|
||||
| `daily/<date>/interests.yaml` | Topics worth proactive attention from the host agent that day. |
|
||||
| `metadata/file_catalog/dream*` | Dream-specific catalog used to detect changes in daily inputs. |
|
||||
|
||||
## Four Stages
|
||||
|
||||
### 1. Extract
|
||||
|
||||
`dream_extract_step` performs three tasks:
|
||||
|
||||
1. Refresh each `daily/<date>.md` in the scan window.
|
||||
2. Scan those day indexes and `daily/<date>/**/*.md`, comparing mtimes with `file_catalog: dream`.
|
||||
3. Send all changed files together to the LLM and globally extract two structured result types: `units` and `topics`.
|
||||
|
||||
`units` are long-term memory units ready to be distilled into digest. Each has `name`, `bucket`, `summary`, and `paths`.
|
||||
A run returns at most `max_units`; extraction merges cross-file evidence for the same abstraction and drops passing
|
||||
mentions, per-file summaries, and weak candidates without reusable value. `bucket` may only be `procedure`, `personal`,
|
||||
or `wiki`; unknown values are routed to `wiki`.
|
||||
|
||||
`topics` are proactive-interest candidates for the day. They contain `title`, `reason`, `evidence`, `keywords`, and
|
||||
`paths` and are filtered again in the Topics stage.
|
||||
|
||||
If there are no changed files, Extract succeeds with no units; Integrate then has no unit work, Topics preserves any
|
||||
existing target-day topics, and Finish still performs its normal catalog summary. If files changed but no LLM is
|
||||
configured, Extract fails because extraction requires an LLM.
|
||||
|
||||
### 2. Integrate
|
||||
|
||||
`dream_integrate_step` invokes an agent independently for each unit and integrates that unit into one digest node. It
|
||||
exposes these tools to the agent:
|
||||
|
||||
```text
|
||||
node_search, read, frontmatter_read, write, edit, frontmatter_update
|
||||
```
|
||||
|
||||
This stage carries the core responsibility of `auto_link`. It first uses `node_search` to recall similar or related
|
||||
nodes at digest-node granularity, decides whether to create or update a node, and finally writes sources and related
|
||||
digest nodes as wikilinks. See [Auto Link](./auto_link.md) for the recall, deduplication, and edge-writing rules.
|
||||
|
||||
Extract is the gate for deciding whether material is worth remembering, so Integrate has no `SKIP` action: each admitted
|
||||
unit must land in exactly one digest node. Creates and updates must retain provenance and weave related digest links
|
||||
into contextual sentences; bare wikilinks and standalone relationship fields are not valid output.
|
||||
|
||||
There are four integration actions:
|
||||
|
||||
| Action | Meaning |
|
||||
|---------------|--------------------------------------------------------------------------------|
|
||||
| `CREATE` | No equivalent abstraction exists; create a new digest node. |
|
||||
| `CORROBORATE` | The same memory appeared again; append a source or strengthen the description. |
|
||||
| `REFINE` | New material adds boundaries, steps, prerequisites, applicability, or detail. |
|
||||
| `CORRECT` | New material corrects errors, omissions, or conflicts in the existing node. |
|
||||
|
||||
Successfully integrated units are recorded in `integrate_results`. Failed units enter `failed_units`, and their source
|
||||
paths enter `failed_paths`. The Finish stage does not checkpoint failed paths, ensuring that they can be retried later.
|
||||
|
||||
### 3. Topics
|
||||
|
||||
`dream_topics_step` turns topic candidates from Extract into the final `daily/<date>/interests.yaml` for the day.
|
||||
|
||||
It reads:
|
||||
|
||||
```text
|
||||
daily/<date>/interests.yaml
|
||||
daily/<each of the previous topic_diversity_days dates>/interests.yaml
|
||||
```
|
||||
|
||||
Existing topics from the same day are preserved, while similar topics from the previous `topic_diversity_days` days are
|
||||
deduplicated. At most three topics are written by default. With an LLM configured, the LLM selects topics that are more
|
||||
specific, actionable, and non-repetitive. Without an LLM, the step falls back to local normalization and deduplication.
|
||||
|
||||
Example output format. See [Proactive](./proactive.md) for the interface that reads this file:
|
||||
|
||||
```yaml
|
||||
date: 2026-06-20
|
||||
topic_count: 3
|
||||
diversity_days: 7
|
||||
topics:
|
||||
- title: Quality regression in the memory retrieval pipeline
|
||||
reason: The user has recently made repeated changes to search, node_search, and dream integration.
|
||||
evidence: daily/2026-06-20/session.md
|
||||
keywords:
|
||||
- memory search
|
||||
- auto dream
|
||||
paths:
|
||||
- daily/2026-06-20/session.md
|
||||
```
|
||||
|
||||
### 4. Finish
|
||||
|
||||
`dream_finish_step` completes the run:
|
||||
|
||||
1. Write successfully processed changed paths to `file_catalog: dream`.
|
||||
2. Also write the target `daily/<date>/interests.yaml` and every refreshed day-index page in the scan window to the
|
||||
catalog.
|
||||
3. Persist the dream catalog if there were upserts or deletions.
|
||||
4. Return a summary containing counts for scanned, changed, integrated, topics, checkpoints, and related values.
|
||||
|
||||
Failed paths are not checkpointed. The next `auto_dream` run therefore continues to treat them as changed inputs until
|
||||
integration succeeds.
|
||||
|
||||
## Running Auto Dream
|
||||
|
||||
CLI:
|
||||
|
||||
```bash
|
||||
reme auto_dream date=2026-06-20
|
||||
```
|
||||
|
||||
With caller guidance:
|
||||
|
||||
```bash
|
||||
reme auto_dream date=2026-06-20 hint="Prioritize engineering decisions and long-term preferences"
|
||||
```
|
||||
|
||||
Override the default scan window and unit cap:
|
||||
|
||||
```bash
|
||||
reme auto_dream date=2026-06-20 scan_days=3 max_units=8
|
||||
```
|
||||
|
||||
The same set of steps can also be placed in a `cron` Job, for example to run every morning:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
daily_auto_dream:
|
||||
backend: cron
|
||||
cron: "30 3 * * *"
|
||||
steps:
|
||||
- backend: dream_extract_step
|
||||
file_catalog: dream
|
||||
- backend: dream_integrate_step
|
||||
- backend: dream_topics_step
|
||||
- backend: dream_finish_step
|
||||
file_catalog: dream
|
||||
```
|
||||
|
||||
## Important Boundaries
|
||||
|
||||
`auto_dream` consumes only daily inputs and does not rewrite daily bodies. Daily preserves facts and the original
|
||||
situation; digest is the abstracted long-term memory layer.
|
||||
|
||||
`digest` is not a copy of the source text. Its body should preserve reusable abstractions, while a Sources section
|
||||
points back with contextual sentences such as `The decision was recorded in [[daily/<date>/decision.md]].` Links follow
|
||||
the workspace-relative wikilink semantics described in
|
||||
[Memory as File](./memory_as_file.md).
|
||||
|
||||
`auto_dream` does not invent an overview from nothing. Only content that actually appears in daily input and is
|
||||
extracted as a unit or topic can enter digest or `interests.yaml`.
|
||||
|
||||
The complete flow depends on an LLM for Extract and Integrate. Topics can perform local deduplication without an LLM,
|
||||
but that does not mean the full dream flow can run offline.
|
||||
146
docs/en/auto_link.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# Auto Link
|
||||
|
||||
In the current implementation, `auto_link` is not a separately registered Job. It is a capability of the Integrate stage
|
||||
in
|
||||
`auto_dream`: when `dream_integrate_step` writes a memory unit to `digest/`, it also recalls digest nodes, makes a
|
||||
deduplication decision, links sources, and weaves wikilinks to related nodes into the result.
|
||||
|
||||
For the complete dream flow, see [Auto Dream](./auto_dream.md). For general wikilink, frontmatter, and
|
||||
workspace-relative path semantics, see [Memory as File](./memory_as_file.md). For question-answering retrieval, see
|
||||
[Memory Search](./memory_search.md).
|
||||
|
||||
## Where It Runs
|
||||
|
||||
The default `auto_dream` flow is:
|
||||
|
||||
```yaml
|
||||
auto_dream:
|
||||
steps:
|
||||
- dream_extract_step
|
||||
- dream_integrate_step # where auto_link actually happens
|
||||
- dream_topics_step
|
||||
- dream_finish_step
|
||||
```
|
||||
|
||||
The Integrate stage processes each unit independently. A unit is written to exactly one target digest node, but that
|
||||
node may link to multiple sources and multiple related digest nodes.
|
||||
|
||||
## Goals
|
||||
|
||||
`auto_link` addresses graph quality at write time:
|
||||
|
||||
| Problem | Handling |
|
||||
|------------------------------------------------|----------------------------------------------------------------------|
|
||||
| The same memory already exists | Recall and update the existing node instead of creating a duplicate. |
|
||||
| New and existing material are related | Write workspace-relative wikilinks into the body. |
|
||||
| A digest node is disconnected from its sources | Add daily/resource links under a `## Sources` section. |
|
||||
| A node contains only isolated prose | Add links to related digest nodes on both CREATE and UPDATE. |
|
||||
|
||||
## Toolchain
|
||||
|
||||
`dream_integrate_step` exposes these tools to the agent:
|
||||
|
||||
```text
|
||||
node_search
|
||||
read
|
||||
frontmatter_read
|
||||
write
|
||||
edit
|
||||
frontmatter_update
|
||||
```
|
||||
|
||||
`node_search` is digest-only node retrieval designed for dream integration. It returns node-level signals such as the
|
||||
digest node's `path` and the `name` and `description` from frontmatter. It does not expand the body and does not perform
|
||||
the link expansion used by ordinary search.
|
||||
|
||||
`read` and `frontmatter_read` are used only for candidates that may be relevant, avoiding expansion of every recalled
|
||||
result into a large context.
|
||||
|
||||
## Linking Flow
|
||||
|
||||
### 1. Recall candidate nodes
|
||||
|
||||
The agent first calls `node_search` with the unit's triggers, verbs, nouns, synonyms, and possible failure modes. Broad
|
||||
recall, for example `limit=20-30`, is recommended by default because this step serves both deduplication and link
|
||||
discovery.
|
||||
|
||||
Recalled results are internally classified into three groups:
|
||||
|
||||
| Classification | Meaning | Next action |
|
||||
|--------------------|---------------------------------------------------------------------------------------------------------|---------------------------|
|
||||
| `same_abstraction` | The trigger or underlying abstraction is the same, with substantial content overlap. | Use as the UPDATE target. |
|
||||
| `related` | An adjacent process, prerequisite, failure mode, concept, preference, or upstream/downstream knowledge. | Write a body wikilink. |
|
||||
| `unrelated` | Only superficially similar or unrelated. | Ignore. |
|
||||
|
||||
### 2. Choose a write action
|
||||
|
||||
Every unit must select one action:
|
||||
|
||||
| Action | Linking semantics |
|
||||
|---------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `CREATE` | Write a new `digest/<bucket>/<slug>.md` and add source and related-node links to its body. |
|
||||
| `CORROBORATE` | The same abstraction appeared again; append its source link and strengthen the description when needed. |
|
||||
| `REFINE` | New material extends the existing node; insert the additional content in the appropriate section and preserve existing links. |
|
||||
| `CORRECT` | New material corrects the existing node; use source links to identify the basis for the correction. |
|
||||
|
||||
An UPDATE should be additive whenever possible: do not delete existing wikilinks or source entries. This prevents later
|
||||
graph indexing and retrieval from losing edges.
|
||||
|
||||
### 3. Write source edges
|
||||
|
||||
Source edges are ordinary wikilinks grouped under a Markdown heading:
|
||||
|
||||
```markdown
|
||||
## Sources
|
||||
|
||||
The decision was recorded in [[daily/2026-06-20/session.md]], while the supporting technical evidence comes from
|
||||
[[resource/2026-06-20/paper.md]].
|
||||
```
|
||||
|
||||
These edges represent the evidence behind a digest node. Plain-text descriptions do not count as source edges because
|
||||
only wikilinks can be parsed reliably by the file graph. The surrounding sentence must explain what each source
|
||||
supports; a bare wikilink line is not valid Integrate output. For the complete parsing rules, see
|
||||
[Memory as File](./memory_as_file.md#wikilink).
|
||||
|
||||
### 4. Write relationships between digest nodes
|
||||
|
||||
Relationships between digest nodes use complete workspace-relative paths woven into natural prose:
|
||||
|
||||
```markdown
|
||||
This design extends [[digest/wiki/hybrid-search.md]] and uses
|
||||
[[digest/procedure/rebuild-index.md]]. Follow
|
||||
[[digest/personal/team-review-preference.md]] during review.
|
||||
```
|
||||
|
||||
## Bucket Differences
|
||||
|
||||
`auto_link` adjusts the shape of its output according to the unit bucket:
|
||||
|
||||
| Bucket | Writing focus |
|
||||
|-------------|-----------------------------------------------------------------------------------------------------------------------------|
|
||||
| `procedure` | Write a runbook with triggers, steps, inputs, and failure modes. Link prerequisites, substeps, and related preferences. |
|
||||
| `personal` | Write user-, team-, or project-specific facts and preferences. Link related projects, habits, and decision context. |
|
||||
| `wiki` | Write general knowledge, principles, observations, and decision precedents. Link concepts, methods, and adjacent knowledge. |
|
||||
|
||||
Regardless of bucket, preserve source edges and weave recalled related digest nodes into the body whenever possible.
|
||||
|
||||
## Relationship to Search
|
||||
|
||||
`auto_link` uses `node_search`, not the question-answering `search`.
|
||||
|
||||
| Capability | Purpose |
|
||||
|---------------|-----------------------------------------------------------------------------------------------------------|
|
||||
| `search` | External question answering; returns chunks and can expand upstream/downstream link context. |
|
||||
| `node_search` | Dream integration; recalls only digest node-level summaries for deduplication and related-link decisions. |
|
||||
|
||||
This boundary matters. The Integrate stage needs to decide whether the same abstraction already exists and which nodes
|
||||
should be linked; it should not load large numbers of body chunks into context. [Memory Search](./memory_search.md)
|
||||
handles question-oriented chunk retrieval, RRF fusion, and link expansion.
|
||||
|
||||
## Failure and Retry
|
||||
|
||||
If integration of a unit fails, `dream_integrate_step` records `failed_units` and `failed_paths`.
|
||||
`dream_finish_step` does not checkpoint those source paths, so the next `auto_dream` run processes them again.
|
||||
|
||||
This makes auto_link writes retryable: a failure does not mark the input as complete or silently discard digest edges
|
||||
that should have been created.
|
||||
114
docs/en/auto_memory.md
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
# Auto Memory
|
||||
|
||||
Auto Memory is ReMe's entry point for conversational memory. Within a target date, it uses `session_id` to find or update at
|
||||
most one daily memory card, whose filename is a concise topic or event name chosen by the Agent. The day's `YYYY-MM-DD.md`
|
||||
page indexes those cards. It turns "we talked about it" into "it was remembered" while retaining a source conversation record
|
||||
as evidence.
|
||||
|
||||
<p align="center">
|
||||
<img src="../figure/auto-memory-resource.svg" alt="ReMe Auto Memory and Auto Resource writing daily memory cards" width="92%">
|
||||
</p>
|
||||
|
||||
For the general file semantics of `daily/`, `session/`, frontmatter, and wikilinks, see
|
||||
[Memory as File](./memory_as_file.md).
|
||||
|
||||
```text
|
||||
Conversation
|
||||
├─ step 1: daily/YYYY-MM-DD/<generated_name>.md # one topic-named card per session
|
||||
├─ step 2: daily/YYYY-MM-DD.md # daily index linking the cards
|
||||
└─ source: session/dialog/<session_id>.jsonl # source conversation record
|
||||
```
|
||||
|
||||
## What It Records
|
||||
|
||||
Auto Memory does not preserve a chat transcript as a running summary. It records information that may remain useful later:
|
||||
|
||||
- User preferences: preferred style, collaboration habits, and long-term requirements.
|
||||
- Key facts: project background, important numbers, explicit conclusions, and constraints.
|
||||
- Process decisions: what happened, why a choice was made, and which alternatives were rejected.
|
||||
- Current state: what has been completed, what is blocked, and what comes next.
|
||||
- Reusable experience: commands, workflows, diagnostic methods, and solutions.
|
||||
|
||||
## Write Location
|
||||
|
||||
Auto Memory writes distilled memories to `daily/`. Conversations from the same day first become individual cards:
|
||||
|
||||
Example directory:
|
||||
|
||||
```text
|
||||
workspace/
|
||||
daily/
|
||||
2026-06-20.md
|
||||
2026-06-20/
|
||||
login-refactor-decision.md
|
||||
retrieval-regression.md
|
||||
```
|
||||
|
||||
The two files under the date directory are topic-named cards distilled from different conversations.
|
||||
`daily/2026-06-20.md` is the index page for that day. Resource files enter the same daily memory layer; see
|
||||
[Auto Resource](./auto_resource.md).
|
||||
|
||||
When a call includes `session_id`, Auto Memory uses it to find the corresponding card through frontmatter, while the Agent
|
||||
chooses a readable filename through `name`:
|
||||
|
||||
```yaml
|
||||
name: login-refactor-decision
|
||||
session_id: session-a
|
||||
source_conversation: "[[session/dialog/session-a.jsonl]]"
|
||||
```
|
||||
|
||||
This keeps different conversations separate without forcing opaque IDs into filenames. An update locates the existing note by
|
||||
`session_id` or `source_conversation`; if the Agent supplies a better frontmatter `name`, the system can rename the note and
|
||||
retarget inbound wikilinks. To see what happened on a day, start with `YYYY-MM-DD.md`.
|
||||
|
||||
## Preserving the Original Information
|
||||
|
||||
The distilled daily note is optimized for readability; a filtered source conversation record is retained for trust and
|
||||
verification.
|
||||
|
||||
While generating memory cards, Auto Memory also saves the source messages:
|
||||
|
||||
```text
|
||||
session/
|
||||
dialog/
|
||||
session-a.jsonl
|
||||
session-b.jsonl
|
||||
```
|
||||
|
||||
Each daily note points to its corresponding conversation record. Saved messages omit tool-result blocks and base64 data
|
||||
blocks, preventing recalled memory and binary payloads from being mistaken for user-provided evidence later.
|
||||
|
||||
## Message Timestamps
|
||||
|
||||
Auto Memory preserves each retained message's `created_at` in both the prompt and the source conversation JSONL. When importing historical
|
||||
conversations or benchmark data, provide the actual occurrence time for every message so the model does not confuse event
|
||||
time with execution time:
|
||||
|
||||
```bash
|
||||
reme auto_memory \
|
||||
session_id=locomo-session \
|
||||
messages='[
|
||||
{"role":"user","content":"Jon lost his job today.","created_at":"2023-01-19T08:00:00"},
|
||||
{"role":"assistant","content":"I am sorry to hear that.","created_at":"2023-01-19T08:01:00"}
|
||||
]'
|
||||
```
|
||||
|
||||
For compatibility with common dataset schemas, `auto_memory` also checks `time_created`, `timestamp`, `createdAt`,
|
||||
`timeCreated`, and `created_time` when `created_at` is absent. These fields may appear either at the top level of a message
|
||||
or inside `metadata`.
|
||||
|
||||
When a call does not explicitly provide `date`, Auto Memory uses the latest valid `created_at` date in the messages. If no
|
||||
message contains a valid timestamp, it falls back to the current date. Historical imports may also specify the
|
||||
target date directly:
|
||||
|
||||
```bash
|
||||
reme auto_memory \
|
||||
session_id=locomo-session \
|
||||
date=2023-01-19 \
|
||||
messages='[{"role":"user","content":"Jon lost his job today."}]'
|
||||
```
|
||||
|
||||
## What Happens Next
|
||||
|
||||
Auto Memory only creates memory in the daily layer. To distill this material further into long-term `digest/` nodes, use
|
||||
[Auto Dream](./auto_dream.md). To search daily and digest content, use [Memory Search](./memory_search.md).
|
||||
105
docs/en/auto_resource.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# Auto Resource `Beta`
|
||||
|
||||
Auto Resource is ReMe's entry point for interpreting resources and is currently in **Beta**. Resource files first enter
|
||||
`resource/`, preferably under a date directory, and are then interpreted into daily resource cards. Each card's filename
|
||||
comes from the LLM-generated frontmatter `name`, and `source_resource` links the card back to its original file.
|
||||
|
||||
<p align="center">
|
||||
<img src="../figure/auto-memory-resource.svg" alt="ReMe Auto Memory and Auto Resource writing daily memory cards" width="92%">
|
||||
</p>
|
||||
|
||||
For the general file semantics of workspace layers, `resource/`, and `daily/`, see
|
||||
[Memory as File](./memory_as_file.md). For the flow that writes conversations to daily, see
|
||||
[Auto Memory](./auto_memory.md).
|
||||
|
||||
```text
|
||||
resource/[YYYY-MM-DD/]<resource_file>
|
||||
├─ step 1: daily/YYYY-MM-DD/<generated_name>.md # interpreted resource card
|
||||
├─ step 2: source_resource points to the original resource
|
||||
└─ step 3: daily/YYYY-MM-DD.md # daily index linking the cards
|
||||
```
|
||||
|
||||
## What It Records
|
||||
|
||||
Auto Resource does more than copy file content. It extracts information that will make the resource easier to retrieve
|
||||
and understand later:
|
||||
|
||||
- Core content: what the resource is mainly about.
|
||||
- Structure: its sections, tables, fields, and data organization.
|
||||
- Key details: important numbers, names, dates, and conclusions.
|
||||
- Context and purpose: why the resource exists and how it relates to current work.
|
||||
- Actionable items: tasks, deadlines, and follow-up work.
|
||||
|
||||
In short, it turns "a file was archived" into "the resource is usable."
|
||||
|
||||
## Original Resource Entry Point
|
||||
|
||||
Auto Resource uses `resource/` as the entry point for source material. Date directories are recommended, and their date
|
||||
determines which daily memory layer receives the interpreted card. A file directly under `resource/` is also supported
|
||||
and uses today in the application timezone.
|
||||
|
||||
Example directory:
|
||||
|
||||
```text
|
||||
workspace/
|
||||
resource/
|
||||
quick-note.txt # enters today's daily layer
|
||||
2026-06-20/
|
||||
market-report.md
|
||||
meeting-notes.csv
|
||||
```
|
||||
|
||||
The current Beta version is best suited to text-based resources such as `md`, `txt`, `json`, `jsonl`, `csv`, `yaml`, and
|
||||
`html`.
|
||||
|
||||
## Resource Cards
|
||||
|
||||
Each resource file produces one daily resource card. The system initially uses the resource file's stem as a temporary
|
||||
path. After the agent writes the card, the file is renamed according to its frontmatter `name`:
|
||||
|
||||
```text
|
||||
resource/2026-06-20/market-report.md
|
||||
↓
|
||||
daily/2026-06-20/market-report-highlights.md
|
||||
```
|
||||
|
||||
The resource card links to the original file through frontmatter:
|
||||
|
||||
```yaml
|
||||
source_resource: "[[resource/2026-06-20/market-report.md]]"
|
||||
```
|
||||
|
||||
When a resource changes, Auto Resource finds and updates the corresponding card through `source_resource`. When a
|
||||
resource is deleted, its daily note is also removed. The older `daily/YYYY-MM-DD/<resource_stem>.md` naming convention
|
||||
remains supported as a fallback.
|
||||
|
||||
## Daily Index
|
||||
|
||||
Resource cards enter the same daily memory layer as Auto Memory cards. The day's `YYYY-MM-DD.md` page acts as an index
|
||||
and organizes those resource cards:
|
||||
|
||||
```text
|
||||
daily/
|
||||
2026-06-20.md
|
||||
2026-06-20/
|
||||
market-report-highlights.md
|
||||
meeting-notes-summary.md
|
||||
```
|
||||
|
||||
To review which resources were processed on a day, start with `YYYY-MM-DD.md`. To inspect what was distilled from one
|
||||
resource, open its corresponding resource card.
|
||||
|
||||
## Preserving the Original Resource
|
||||
|
||||
The interpreted daily note is optimized for readability; the original resource is retained for trust and verification.
|
||||
|
||||
Auto Resource does not move the original file. It remains at its original path under `resource/`. Text resources can
|
||||
therefore enter the daily memory flow while their source files stay in their original location.
|
||||
|
||||
## What Happens Next
|
||||
|
||||
Auto Resource only creates resource interpretations in the daily layer. To distill long-term knowledge from resources
|
||||
into
|
||||
`digest/`, use [Auto Dream](./auto_dream.md). The default live index covers daily cards and digest nodes. Run
|
||||
`reme reindex`
|
||||
when original resource files must also be directly searchable; see [Memory Search](./memory_search.md).
|
||||
230
docs/en/contributing.md
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
# Open Source and Contributing
|
||||
|
||||
ReMe is open source and hosted on GitHub:
|
||||
|
||||
**https://github.com/agentscope-ai/ReMe**
|
||||
|
||||
---
|
||||
|
||||
## How to Contribute
|
||||
|
||||
Thank you for your interest in ReMe. ReMe is a file-first, self-evolving memory system for agents. Contributions are
|
||||
welcome through issue reports, documentation improvements, additional tests, bug fixes, and new capabilities.
|
||||
|
||||
If this is your first time running ReMe locally, start with [Quick Start](./quick_start.md). If your change affects
|
||||
runtime layers, Jobs, Steps, or components, read [ReMe Framework](./framework.md). If it affects workspace directories,
|
||||
frontmatter, wikilinks, or chunking, read [Memory as File](./memory_as_file.md).
|
||||
|
||||
### 1. Before You Begin
|
||||
|
||||
Before investing in an implementation:
|
||||
|
||||
- Check [Open Issues](https://github.com/agentscope-ai/ReMe/issues) for an existing issue or discussion.
|
||||
- If a related issue is still open, comment that you would like to work on it to avoid duplicate effort.
|
||||
- If no issue exists, create one describing the context, expected behavior, possible implementation, and scope of
|
||||
impact.
|
||||
- For larger feature changes, align with maintainers on interfaces, configuration, compatibility, and test strategy
|
||||
before submitting an implementation.
|
||||
|
||||
### 2. Local Development Environment
|
||||
|
||||
The core ReMe code is located in:
|
||||
|
||||
- `reme/`: Python package source, including configuration, components, services, Jobs, Steps, schemas, and utilities.
|
||||
- `pyproject.toml`: project metadata, dependencies, optional dependencies, command entry points, and test configuration.
|
||||
- `tests/`: unit and integration tests.
|
||||
|
||||
The project requires Python 3.11 or later. A virtual environment is recommended:
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -e reme_studio -e ".[dev,full]"
|
||||
cd reme_studio
|
||||
npm ci
|
||||
npm run build:static
|
||||
cd ..
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
### 3. Development Model
|
||||
|
||||
Before developing ReMe code, read [ReMe Framework](./framework.md). New or modified core capabilities should follow the
|
||||
layers and call chain described there:
|
||||
|
||||
```text
|
||||
CLI / Client -> Service -> Application -> Job -> Step -> Component / Workspace
|
||||
```
|
||||
|
||||
In practice:
|
||||
|
||||
- Capabilities exposed to users or external systems should normally be orchestrated by a Job, then exposed by a Service
|
||||
as a CLI-, HTTP-, or MCP-callable interface.
|
||||
- Reusable infrastructure belongs in `reme/components/`, with dependencies declared through `BaseComponent.bind()`.
|
||||
- Atomic business operations belong in `reme/steps/` and access the file store, agent wrapper, catalog, LLM, and other
|
||||
components through `BaseStep.Ref`.
|
||||
- Request, response, and persistent data structures belong in `reme/schema/` or `reme/enumeration/`. Do not scatter
|
||||
implicit structures through Step implementations.
|
||||
- Configuration-driven defaults belong in `reme/config/default.yaml`, and the default configuration must remain runnable
|
||||
and testable.
|
||||
|
||||
When adding a Step or Job, pay particular attention to these conventions:
|
||||
|
||||
- Register implementations with `@R.register("<backend_name>")`. Registration names should be stable, clear, and match
|
||||
the configured `backend`.
|
||||
- After adding a Step file, make sure its package `__init__.py` imports the module; otherwise, the registry will not
|
||||
load it.
|
||||
- A Step should perform one atomic business operation. Cross-step flows belong in Job configuration or a dedicated
|
||||
orchestration Step.
|
||||
- A Job composes Steps and selects normal, streaming, background, or scheduled execution. `enable_serve` controls
|
||||
whether it is externally exposed.
|
||||
- When a Step needs components, prefer `BaseStep.Ref`. Do not reconstruct global components inside a Step or bypass
|
||||
`ApplicationContext`.
|
||||
- File, index, graph, frontmatter, and wikilink behavior must preserve consistent workspace-relative path semantics.
|
||||
- Add fast tests under `tests/unit/` for new capabilities. Put cross-component, LLM, embedding, or service behavior
|
||||
under
|
||||
`tests/integration/` when appropriate.
|
||||
|
||||
### 4. Code and Documentation Changes
|
||||
|
||||
Choose the appropriate entry point for the type of change:
|
||||
|
||||
| Change type | Primary location | Guidance |
|
||||
|-----------------------------------|-------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Configuration or startup behavior | `reme/config/`, `reme/application.py`, `reme/reme.py` | Keep the default configuration runnable and avoid breaking existing CLI, HTTP, and MCP entry points. |
|
||||
| Component capability | `reme/components/` | Reuse `BaseComponent`, the registry, and context objects. |
|
||||
| Job or Step | `reme/components/job/`, `reme/steps/` | Follow the Job -> Step model in [ReMe Framework](./framework.md), keep request and response schemas clear, and add corresponding tests. |
|
||||
| Data structure | `reme/schema/`, `reme/enumeration/` | Preserve serialization compatibility and existing frontmatter and wikilink semantics. |
|
||||
| Utility | `reme/utils/` | Keep function boundaries small and cover edge cases with unit tests. |
|
||||
| User documentation | `docs/en/`, `README.md` | Update documentation when user-visible behavior changes. |
|
||||
|
||||
If a change involves an LLM, embeddings, an external service, file watching, or a background task, also describe its
|
||||
dependencies, failure behavior, and local validation method.
|
||||
|
||||
### 5. Commit Message Format
|
||||
|
||||
Use [Conventional Commits](https://www.conventionalcommits.org/) to keep history clear.
|
||||
|
||||
Format:
|
||||
|
||||
```text
|
||||
<type>(<scope>): <subject>
|
||||
```
|
||||
|
||||
Common types:
|
||||
|
||||
- `feat`: new feature
|
||||
- `fix`: bug fix
|
||||
- `docs`: documentation only
|
||||
- `style`: code-style change with no behavior change
|
||||
- `refactor`: refactoring that neither fixes a bug nor adds a feature
|
||||
- `perf`: performance improvement
|
||||
- `test`: add or update tests
|
||||
- `chore`: build, tooling, or maintenance work
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
feat(search): add link expansion option
|
||||
fix(file-graph): handle pending wikilinks after move
|
||||
docs(memory): update auto memory guide
|
||||
test(config): cover default yaml parsing
|
||||
chore(pre-commit): update lint hooks
|
||||
```
|
||||
|
||||
### 6. Pull Request Titles
|
||||
|
||||
PR titles should use the same format:
|
||||
|
||||
```text
|
||||
<type>(<scope>): <description>
|
||||
```
|
||||
|
||||
Requirements:
|
||||
|
||||
- Use `feat`, `fix`, `docs`, `test`, `refactor`, `chore`, `perf`, `style`, `build`, or `revert` as the type.
|
||||
- Use lowercase letters, numbers, hyphens, or underscores for the scope.
|
||||
- Keep the description short and state the actual effect of the PR.
|
||||
|
||||
Examples:
|
||||
|
||||
```text
|
||||
feat(auto-memory): persist source conversation metadata
|
||||
fix(markdown): keep wikilink aliases during edit
|
||||
docs(en): add contribution guide
|
||||
```
|
||||
|
||||
### 7. Pre-submit Checks
|
||||
|
||||
Before committing or opening a PR, run at least:
|
||||
|
||||
```bash
|
||||
pre-commit run --all-files
|
||||
pytest
|
||||
```
|
||||
|
||||
For a localized code change, start with a narrower test set:
|
||||
|
||||
```bash
|
||||
pytest tests/unit/test_search_step.py
|
||||
pytest tests/unit/test_reme_cli.py
|
||||
```
|
||||
|
||||
If `pre-commit` modifies files automatically, commit those changes and rerun the checks until everything passes.
|
||||
|
||||
The current pre-commit configuration includes YAML/TOML/JSON validation, private-key detection, trailing-whitespace
|
||||
checks,
|
||||
`black`, `flake8`, `pylint`, and `pyroma`. The main formatting rules are:
|
||||
|
||||
- `black --line-length=120`
|
||||
- `flake8 --max-line-length=120`
|
||||
- `pylint --max-line-length=120`
|
||||
|
||||
Some integration tests may require an LLM, embeddings, or external service configuration. If you cannot run them
|
||||
locally, state why they were skipped and what alternative validation you completed in the PR description.
|
||||
|
||||
### 8. Testing Requirements
|
||||
|
||||
Add tests according to the risk of the change:
|
||||
|
||||
- For a bug fix, first add a regression test that reproduces the issue.
|
||||
- For a new Step, Job, or component, cover at least the main path and a failure path.
|
||||
- For changes to shared logic such as indexes, graphs, wikilinks, frontmatter, or file operations, add edge cases.
|
||||
- For changes to the CLI, services, or configuration parsing, cover the user-visible entry point.
|
||||
- Documentation-only changes usually do not require new tests, but running `pre-commit run --all-files` is still
|
||||
recommended.
|
||||
|
||||
Place tests according to the existing structure:
|
||||
|
||||
- `tests/unit/`: fast tests that require no real external service.
|
||||
- `tests/integration/`: integration tests spanning components or requiring external configuration.
|
||||
|
||||
### 9. Documentation Contributions
|
||||
|
||||
When a change affects how users install, configure, invoke, or understand ReMe, update the documentation as well.
|
||||
|
||||
Documentation lives under:
|
||||
|
||||
```text
|
||||
docs/
|
||||
```
|
||||
|
||||
Documentation should:
|
||||
|
||||
- Use clear titles that directly identify a capability or flow.
|
||||
- Provide commands that can be copied and run.
|
||||
- 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.
|
||||
|
||||
---
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Bugs and feature requests: [GitHub Issues](https://github.com/agentscope-ai/ReMe/issues)
|
||||
- Project home: [GitHub Repository](https://github.com/agentscope-ai/ReMe)
|
||||
- Documentation site: [https://reme.agentscope.io](https://reme.agentscope.io)
|
||||
|
||||
---
|
||||
|
||||
Thank you for contributing to ReMe. Your improvements help make long-term memory for agents more readable, controllable,
|
||||
and maintainable.
|
||||
840
docs/en/framework.md
Normal file
|
|
@ -0,0 +1,840 @@
|
|||
# ReMe Framework
|
||||
|
||||
## 1. Overview
|
||||
|
||||
The ReMe runtime can be understood as follows: **a configuration-driven Application assembles components and Jobs; the
|
||||
Service exposes service-enabled Jobs to the CLI, HTTP, or MCP; and each Job executes its Steps in sequence**.
|
||||
|
||||
<p align="center">
|
||||
<img src="../figure/framework-structure.svg" alt="ReMe framework structure: CLI, Service, Application, Job, Step, and Component" width="92%">
|
||||
</p>
|
||||
|
||||
To run and use ReMe first, see [Quick Start](./quick_start.md). For workspace file semantics, see
|
||||
[Memory as File](./memory_as_file.md). User-facing guides for retrieval, automatic memory, and proactive reading are
|
||||
[Memory Search](./memory_search.md), [Auto Memory](./auto_memory.md), [Auto Resource](./auto_resource.md),
|
||||
[Auto Dream](./auto_dream.md), and [Proactive](./proactive.md).
|
||||
|
||||
### Capability Boundary
|
||||
|
||||
ReMe v4 focuses on long-term memory: it distills conversations and resources into `daily/`, organizes them into
|
||||
`digest/`, and exposes write, retrieval, and proactive-read capabilities through the CLI, HTTP, and MCP.
|
||||
|
||||
Single-session context-window management is outside the scope of ReMe v4. This includes compressing the current
|
||||
conversation, injecting summaries, trimming tool output, or providing an independent `/compact` interface. Those
|
||||
capabilities belong in the host agent framework. ReMe accepts conversations, resources, and file changes that have
|
||||
already occurred and persists the information with long-term value.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
CLI["reme CLI<br/>reme/reme.py"] --> Client["Client<br/>http / mcp"]
|
||||
Client --> Service["Service<br/>HTTP / MCP"]
|
||||
Service --> App["Application<br/>reme/application.py"]
|
||||
App --> Jobs["Jobs<br/>base / stream / background / cron"]
|
||||
Jobs --> Steps["Steps<br/>reme/steps/**"]
|
||||
Steps --> Ctx["RuntimeContext<br/>data + Response + stream queue"]
|
||||
Steps --> Components["Components<br/>store / graph / index / llm / agent / catalog"]
|
||||
Components --> Workspace["Workspace<br/>daily / digest / resource / metadata"]
|
||||
```
|
||||
|
||||
Core layers:
|
||||
|
||||
| Layer | Main location | Responsibility |
|
||||
|-------------|----------------------------|---------------------------------------------------------------------------------------------------|
|
||||
| CLI | `reme/reme.py` | Parse commands; `start` launches the service; other actions call the service through a client. |
|
||||
| Service | `reme/components/service/` | Register Jobs as HTTP endpoints or MCP tools. |
|
||||
| Application | `reme/application.py` | Assemble configured objects, start them in dependency order, close them, and invoke Jobs. |
|
||||
| Job | `reme/components/job/` | Orchestrate Steps and select normal, streaming, background, or scheduled execution. |
|
||||
| Step | `reme/steps/` | Atomic business operations such as file I/O, retrieval, indexing, and self-evolution. |
|
||||
| Component | `reme/components/` | Reusable infrastructure such as file_store, file_graph, keyword_index, and agent_wrapper. |
|
||||
| Schema | `reme/schema/` | Data structures such as `Request`, `Response`, `FileChunk`, `FileNode`, and configuration models. |
|
||||
| Config | `reme/config/` | Default YAML configuration and command-line override parsing. |
|
||||
|
||||
## 2. Directory Structure
|
||||
|
||||
```text
|
||||
reme/
|
||||
reme.py # CLI entry point
|
||||
application.py # Application assembly and lifecycle
|
||||
plugin.py # installed plugin contract and entry-point loader
|
||||
config/
|
||||
default.yaml # default service / jobs / components
|
||||
config_parser.py # config=, dot notation, and env placeholder parsing
|
||||
components/
|
||||
component_registry.py # backend registry and application-local copies
|
||||
base_component.py # ComponentMixin / BaseComponent / bind dependency declarations
|
||||
runtime_context.py # context for one Job execution
|
||||
job/ # BaseJob / StreamJob / BackgroundJob / CronJob
|
||||
service/ # HTTP / MCP services
|
||||
client/ # HTTP / MCP clients
|
||||
file_store/ # file-index coordination layer
|
||||
file_graph/ # wikilink graph
|
||||
keyword_index/ # BM25 and other keyword indexes
|
||||
file_chunker/ # Markdown / JSON / JSONL / generic text chunking
|
||||
file_catalog/ # change checkpoints
|
||||
as_llm/, as_embedding/ # model wrappers
|
||||
agent_wrapper/ # AgentScope / Claude Code / Codex wrappers
|
||||
steps/
|
||||
base_step.py # BaseStep, Ref, dispatch_steps
|
||||
common/ # version, help, health_check, status, chat
|
||||
benchmark/ # LongMemEval / BEAM evaluation steps
|
||||
cookbook/ # built-in cookbook support steps
|
||||
file_io/ # read/write/edit/delete/move/frontmatter/daily
|
||||
index/ # watch/init/update/search/traverse
|
||||
evolve/ # auto_memory, auto_resource, auto_dream, proactive
|
||||
transfer/ # upload/download
|
||||
plugins/
|
||||
auto-fin/ # independent example plugin distribution
|
||||
daily_paper/ # independent paper-research plugin distribution
|
||||
integrations/
|
||||
claude_code/ # Claude Code adapter and marketplace
|
||||
hermes_agent/ # Hermes Agent memory-provider adapter
|
||||
```
|
||||
|
||||
The default workspace directories are defined by `ApplicationConfig`:
|
||||
|
||||
```text
|
||||
<workspace_dir>/
|
||||
metadata/ # persistent file_store, file_graph, keyword_index, file_catalog, and related state
|
||||
session/ # source conversations used by memory workflows
|
||||
mem_session/ # generated Agent wrapper sessions and configuration
|
||||
resource/ # external resources
|
||||
daily/ # lightly processed memory
|
||||
digest/ # long-term digest memory
|
||||
```
|
||||
|
||||
`Application.__init__()` first ensures that these directories exist, then initializes the service, components, and Jobs.
|
||||
|
||||
## 3. Startup and Call Chain
|
||||
|
||||
### 3.1 CLI
|
||||
|
||||
The entry point is `reme/reme.py::main()`:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["main()"] --> B["parse_args(*sys.argv[1:])"]
|
||||
B --> C{action}
|
||||
C -->|" start "| D["load_env()"]
|
||||
D --> E["resolve_app_config(**kwargs)"]
|
||||
E --> F["precheck_start(service)"]
|
||||
F --> G["ReMe(**config).run_app()"]
|
||||
C -->|" find_reme "| H["cli_find_reme()"]
|
||||
C -->|" other actions "| I["call_server(action, **kwargs)"]
|
||||
I --> J["R.get(ComponentEnum.CLIENT, backend)"]
|
||||
J --> K["client(action=action, **kwargs)"]
|
||||
```
|
||||
|
||||
Common commands:
|
||||
|
||||
```bash
|
||||
reme start
|
||||
reme start service.port=8181
|
||||
reme version
|
||||
reme search query="memory" limit=5
|
||||
reme search query="memory" backend=mcp
|
||||
```
|
||||
|
||||
Configuration parsing supports:
|
||||
|
||||
| Capability | Source | Description |
|
||||
|------------------------|-------------------------|--------------------------------------------------------------------------|
|
||||
| Default configuration | `resolve_app_config()` | Load `reme/config/default.yaml` when `config` is not specified. |
|
||||
| Explicit configuration | `config=<name-or-path>` | Accept a built-in configuration name or a YAML/JSON file path. |
|
||||
| Dot notation | `parse_dot_notation()` | For example, `service.port=8181`. |
|
||||
| Environment variables | `_expand_env_vars()` | Support `${VAR}` and `${VAR:-default}`. |
|
||||
| Value conversion | `_convert_value()` | Convert bool, int, float, JSON list/dict, and null values automatically. |
|
||||
|
||||
### 3.2 Service
|
||||
|
||||
`BaseService.run_app()` executes in this order:
|
||||
|
||||
Set the optional `service.jobs` list to restrict HTTP or MCP exposure to those job names. If omitted, all jobs with
|
||||
`enable_serve: true` remain eligible; an empty list exposes none. The whitelist does not override `enable_serve: false`.
|
||||
When the list is configured, a missing, disabled, unsupported, or invalid selected job fails service startup.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Service.build_service(app)"] --> B["read app.context.jobs"]
|
||||
B --> C{"enabled and selected by service.jobs?"}
|
||||
C -->|yes| D["Service.add_job(job)"]
|
||||
C -->|no| E["skip registration"]
|
||||
D --> F["Service.start_service(app)"]
|
||||
E --> F
|
||||
F --> G["app.start() during lifespan"]
|
||||
G --> H["Application starts jobs"]
|
||||
```
|
||||
|
||||
HTTP service behavior:
|
||||
|
||||
| Job type | HTTP exposure |
|
||||
|-------------------------------------------|---------------------------------------------------|
|
||||
| Non-`StreamJob` with `enable_serve: true` | `POST /<job.name>` returning `Response` JSON. |
|
||||
| `StreamJob` | `POST /<job.name>` returning `text/event-stream`. |
|
||||
| `enable_serve: false` | No endpoint is registered. |
|
||||
|
||||
After registering Job endpoints, the HTTP service can also mount the ReMe Studio single-page application. The default is
|
||||
`service.web_enabled=true`. Builds are resolved from `service.web_static_dir`, `REME_WEB_STATIC_DIR`, the optional
|
||||
`reme_studio` package installed by the `web` and `core` extras, and source-tree locations such as
|
||||
`reme_studio/dist-static`. If no `index.html` is found, only the frontend is skipped and the Job API remains available. The
|
||||
Studio `GET` fallback does not replace existing `POST /<job.name>` routes.
|
||||
|
||||
MCP service behavior:
|
||||
|
||||
| Job type | MCP exposure |
|
||||
|-------------------------------------------|-------------------------------------------------------------------|
|
||||
| Non-`StreamJob` with `enable_serve: true` | Registered as an MCP tool. |
|
||||
| `StreamJob` | Currently skipped and not registered. |
|
||||
| `BackgroundJob` | Forces `enable_serve=False` at construction and is never exposed. |
|
||||
|
||||
MCP services can inject server-owned arguments with `injected_job_kwargs`; callers cannot override those arguments. Set
|
||||
`tool_error_on_failure: true` to expose an unsuccessful ReMe `Response` as an MCP tool error.
|
||||
|
||||
## 4. Registry and Dependency Injection
|
||||
|
||||
### 4.1 Global Registry R
|
||||
|
||||
ReMe uses the process-wide singleton `R = ComponentRegistry()`. Every component, Job, and Step is registered with
|
||||
`@R.register("name")`.
|
||||
|
||||
```python
|
||||
from ...components import R
|
||||
|
||||
|
||||
@R.register("version_step")
|
||||
class VersionStep(BaseStep):
|
||||
...
|
||||
```
|
||||
|
||||
The registry key is:
|
||||
|
||||
```text
|
||||
(component_type, register_name) -> class
|
||||
```
|
||||
|
||||
`component_type` comes from a class attribute:
|
||||
|
||||
| Type | Class attribute |
|
||||
|-----------|-----------------------------------------------------------|
|
||||
| Step | `BaseStep.component_type = ComponentEnum.STEP` |
|
||||
| Job | `BaseJob.component_type = ComponentEnum.JOB` |
|
||||
| Service | `BaseService.component_type = ComponentEnum.SERVICE` |
|
||||
| FileStore | `BaseFileStore.component_type = ComponentEnum.FILE_STORE` |
|
||||
|
||||
The same backend name can therefore exist under different component types. For example, `http` can be both a service
|
||||
backend and a client backend.
|
||||
|
||||
`ComponentEnum` provides the built-in identifiers, but installed plugins may declare a new type with a namespaced
|
||||
string such as `example.reranker`. Custom identifiers use lowercase letters and numbers separated by `.`, `_`, or `-`.
|
||||
They are configured under `components` and participate in the same dependency ordering and lifecycle as built-ins.
|
||||
|
||||
### 4.2 Built-in and Plugin Registration
|
||||
|
||||
Built-in implementations populate the built-in registry through package imports. ReMe freezes that template after
|
||||
bootstrap, and each `Application` receives a mutable copy. Runtime code resolves backends through the application's
|
||||
registry rather than changing the process-wide template. ReMe then loads only the installed plugins explicitly named by
|
||||
`plugins` in the resolved configuration. A plugin exposes its package through the `reme.plugins` Python entry-point
|
||||
group. The package's `plugin.yaml` has two optional mappings: `backends` maps registration names to
|
||||
`module:Class` targets, and `application_defaults` contributes a low-priority `ApplicationConfig` fragment. The
|
||||
entry-point name is the plugin's identity.
|
||||
Plugins are enabled explicitly through the application config's `plugins` list or a `plugins=[...]` CLI override.
|
||||
Plugin registration therefore stays local to one application;
|
||||
duplicate `(component_type, backend)` providers fail during assembly instead of overwriting each other.
|
||||
|
||||
The legacy Python `Plugin` descriptor and `reme.configs` entry points remain accepted during migration. Configuration
|
||||
files can use `extends` to inherit another built-in, legacy plugin, or file-based configuration. See the independently
|
||||
packaged [Auto Fin](../../plugins/auto-fin/README.md) and [Daily Paper](../../plugins/daily_paper/README.md) plugins.
|
||||
|
||||
Plugin packages are managed locally and remain separate from per-application activation:
|
||||
|
||||
```bash
|
||||
reme plugins list
|
||||
reme plugins install reme-auto-fin
|
||||
reme plugins install reme-daily-paper
|
||||
reme plugins show daily-paper
|
||||
reme plugins validate daily-paper
|
||||
reme plugins uninstall daily-paper
|
||||
|
||||
reme start plugins='["auto-fin","daily-paper"]'
|
||||
```
|
||||
|
||||
These management commands use the current Python interpreter's pip and never run through an HTTP or MCP service.
|
||||
|
||||
### 4.3 Component.bind
|
||||
|
||||
Dependencies between components are declared with `BaseComponent.bind()`. At startup,
|
||||
`Application._topological_order()` reads every component's `dependencies` and starts them in topological order.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Component.__init__<br/>self.keyword_index = self.bind(...)"] --> B["Dependency placeholder"]
|
||||
B --> C["Application._topological_order()"]
|
||||
C --> D["component.start()"]
|
||||
D --> E["_resolve_bindings()"]
|
||||
E --> F["self.keyword_index = app_context.components[type][name]"]
|
||||
F --> G["component._start()"]
|
||||
```
|
||||
|
||||
Rules for `BaseComponent.bind(name, BaseClass, optional=True)`:
|
||||
|
||||
| Scenario | Behavior |
|
||||
|-----------------------------------------|------------------------------------------------------------|
|
||||
| `name` is empty | Return `None` and skip the dependency. |
|
||||
| `app_context` exists | Look up `app_context.components[ctype][name]`. |
|
||||
| Dependency missing and `optional=True` | Resolve to `None`. |
|
||||
| Dependency missing and `optional=False` | Fail at startup. |
|
||||
| Standalone mode | A private component can be created with `default_factory`. |
|
||||
|
||||
### 4.4 Step.Ref
|
||||
|
||||
Steps do not participate in component topological startup. They are created temporarily for each Job invocation. Steps
|
||||
access components primarily through `BaseStep.Ref`:
|
||||
|
||||
```python
|
||||
file_store: BaseFileStore = Ref(BaseFileStore, ComponentEnum.FILE_STORE)
|
||||
agent_wrapper: BaseAgentWrapper = Ref(BaseAgentWrapper, ComponentEnum.AGENT_WRAPPER, optional=True)
|
||||
```
|
||||
|
||||
Resolution priority:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["access self.file_store"] --> B{"same-named object in kwargs?"}
|
||||
B -->|yes| C["use kwargs object"]
|
||||
B -->|no| D{"same-named object in context.data?"}
|
||||
D -->|yes| E["use context object"]
|
||||
D -->|no| F["read name from kwargs['file_store']; default is default"]
|
||||
F --> G["app_context.components[FILE_STORE][name]"]
|
||||
```
|
||||
|
||||
A Step configuration can therefore specify:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- backend: update_catalog_step
|
||||
file_catalog: resource
|
||||
```
|
||||
|
||||
Here, `file_catalog: resource` means to resolve the `file_catalog` component named `resource`.
|
||||
|
||||
## 5. Application Lifecycle
|
||||
|
||||
The Application converts configuration into runtime objects and starts and closes them in order.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Application(**kwargs)"] --> B["ApplicationContext(**kwargs)<br/>parse ApplicationConfig"]
|
||||
B --> C["_setup_workspace_directories()"]
|
||||
C --> D["_init_service()"]
|
||||
D --> E["_init_components()"]
|
||||
E --> F["_init_jobs()"]
|
||||
F --> G["run_app()"]
|
||||
G --> H["service.run_app(app)"]
|
||||
```
|
||||
|
||||
Startup order in `Application._start()`:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["create optional thread_pool"] --> B["topologically sort components"]
|
||||
B --> C["start components"]
|
||||
C --> D["start BaseJob"]
|
||||
D --> E["start StreamJob"]
|
||||
E --> F["start BackgroundJob"]
|
||||
F --> G["start CronJob"]
|
||||
```
|
||||
|
||||
During shutdown, objects in `_started_components` are closed in reverse order so dependents close before their
|
||||
dependencies.
|
||||
|
||||
## 6. Job Model
|
||||
|
||||
A Job is the orchestration unit for an externally callable capability or background task. Jobs are configured under
|
||||
`jobs:`
|
||||
in `reme/config/default.yaml`.
|
||||
|
||||
### 6.1 BaseJob
|
||||
|
||||
`BaseJob` is the most common request-oriented Job:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Caller["Caller"] --> Job["BaseJob<br/>job(**kwargs)"]
|
||||
Job --> Ctx["RuntimeContext<br/>merged_kwargs"]
|
||||
Ctx --> S1["Step 1<br/>await step(context)"]
|
||||
S1 --> D1["read/write context.data / response"]
|
||||
D1 --> S2["Step 2<br/>await step(context)"]
|
||||
S2 --> D2["read/write context.data / response"]
|
||||
D2 --> Resp["context.response"]
|
||||
Resp --> Caller
|
||||
```
|
||||
|
||||
Important source behavior:
|
||||
|
||||
| Source | Behavior |
|
||||
|--------------------|----------------------------------------------------------------------------------|
|
||||
| `_start()` | Parse each Step config from YAML into `(step_cls, params)`. |
|
||||
| `_build_steps()` | Create new Step instances for every call, avoiding state shared across requests. |
|
||||
| `__call__()` | Create a `RuntimeContext` and execute Steps sequentially. |
|
||||
| Exception handling | Catch the exception, set `response.success=False`, and set `answer=str(e)`. |
|
||||
|
||||
### 6.2 StreamJob
|
||||
|
||||
`StreamJob` extends `BaseJob` but returns streaming chunks:
|
||||
|
||||
| Behavior | Description |
|
||||
|-------------|------------------------------------------------------------|
|
||||
| Context | Includes `stream_queue`. |
|
||||
| Step output | Call `context.add_stream_string(text, ChunkEnum.CONTENT)`. |
|
||||
| Exception | Write `ChunkEnum.ERROR`. |
|
||||
| Completion | Always send a `DONE` chunk. |
|
||||
|
||||
### 6.3 BackgroundJob
|
||||
|
||||
`BackgroundJob` runs long-lived loops such as file watchers. Its constructor forces `enable_serve=False`.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Application starts BackgroundJob"] --> B["_start() creates stop_event and task"]
|
||||
B --> C["_run_with_supervisor()"]
|
||||
C --> D["await self()"]
|
||||
D --> E{"exception?"}
|
||||
E -->|no, returned normally| F["finish"]
|
||||
E -->|yes and supervisor = True| G["exponential backoff + jitter"]
|
||||
G --> C
|
||||
E -->|yes and supervisor = False| H["raise exception"]
|
||||
I["close()"] --> J["stop_event.set()"]
|
||||
J --> K["wait close_timeout; cancel on timeout"]
|
||||
```
|
||||
|
||||
The default `BackgroundJob.__call__()` also executes configured Steps in sequence, but it does not swallow exceptions,
|
||||
which allows the supervisor to restart the task.
|
||||
|
||||
### 6.4 CronJob
|
||||
|
||||
`CronJob` extends `BackgroundJob` with a `cron` expression:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
nightly_dream:
|
||||
backend: cron
|
||||
cron: "0 3 * * *"
|
||||
steps:
|
||||
- backend: dream_extract_step
|
||||
- backend: dream_integrate_step
|
||||
- backend: dream_topics_step
|
||||
- backend: dream_finish_step
|
||||
```
|
||||
|
||||
The current implementation uses `croniter` to calculate the next trigger time. The timezone comes from
|
||||
`app_config.timezone`.
|
||||
|
||||
### 6.5 Default Job Types
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Jobs["default.yaml jobs"] --> BG["background<br/>index_update_loop<br/>resource_watch_loop<br/>digest_watch_loop"]
|
||||
Jobs --> Cron["cron<br/>dream_cron<br/>optimize_index_cron"]
|
||||
Jobs --> Stream["stream<br/>chat"]
|
||||
Jobs --> Base["base<br/>version / help / health_check / status / app_config<br/>search / node_search / traverse / graph_snapshot / reindex<br/>read / load / read_image / write / save / edit / delete / move / list / stat / frontmatter_*<br/>daily_list / daily_reindex / daily_write<br/>auto_memory / auto_memory_cc / auto_resource / auto_dream / proactive"]
|
||||
```
|
||||
|
||||
## 7. Step Model
|
||||
|
||||
A Step is a concrete business action. Every Step extends `BaseStep` and implements `execute()`.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Job._build_steps()"] --> B["Step.__init__()"]
|
||||
B --> C["load prompt<br/>class-named YAML + prompt_dict override"]
|
||||
C --> D["Step.__call__(context, **kwargs)"]
|
||||
D --> E["clear Ref cache"]
|
||||
E --> F["RuntimeContext.from_context()"]
|
||||
F --> G["input_mapping"]
|
||||
G --> H["execute()"]
|
||||
H --> I["output_mapping"]
|
||||
I --> J["return result"]
|
||||
```
|
||||
|
||||
### 7.1 RuntimeContext
|
||||
|
||||
`RuntimeContext` is shared by all Steps within one Job invocation:
|
||||
|
||||
| Field | Description |
|
||||
|----------------|----------------------------------------------------------------------------|
|
||||
| `response` | Final `Response(answer, success, metadata)`. |
|
||||
| `data` | Free-form dictionary containing input parameters and intermediate results. |
|
||||
| `stream_queue` | Output queue for streaming Jobs. |
|
||||
| `stop_event` | Stop signal for background Jobs. |
|
||||
|
||||
Common Step code:
|
||||
|
||||
```python
|
||||
assert self.context is not None
|
||||
query = self.context.get("query", "")
|
||||
self.context["processed_query"] = query.strip().lower()
|
||||
self.context.response.answer = "..."
|
||||
self.context.response.metadata["key"] = "value"
|
||||
return self.context.response
|
||||
```
|
||||
|
||||
### 7.2 input_mapping / output_mapping
|
||||
|
||||
`BaseStep.__call__()` invokes `RuntimeContext.apply_mapping()` before and after execution:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- backend: some_step
|
||||
input_mapping:
|
||||
user_query: query
|
||||
output_mapping:
|
||||
result: final_result
|
||||
```
|
||||
|
||||
The semantics are to copy `context.data[source]` to `context.data[target]`.
|
||||
|
||||
### 7.3 dispatch_steps
|
||||
|
||||
Some Steps produce batches of events and dispatch them to other Steps. `BaseStep.dispatch_steps()` resolves and executes
|
||||
child Steps according to configuration.
|
||||
|
||||
Example from the default configuration:
|
||||
|
||||
```yaml
|
||||
index_update_loop:
|
||||
backend: background
|
||||
watch_dirs: [ daily_dir, digest_dir ]
|
||||
watch_suffixes: [ md ]
|
||||
steps:
|
||||
- backend: init_changes_step
|
||||
monitor_type: file_store
|
||||
monitor_name: default
|
||||
dispatch_steps: [ update_index_step ]
|
||||
- backend: watch_changes_step
|
||||
dispatch_steps: [ update_index_step ]
|
||||
```
|
||||
|
||||
Flow:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Init["init_changes_step"] --> Batch["changes batch"]
|
||||
Watch["watch_changes_step"] --> Batch
|
||||
Batch --> Dispatch["dispatch_steps(...)"]
|
||||
Dispatch --> Update["update_index_step"]
|
||||
Update --> Store["file_store"]
|
||||
```
|
||||
|
||||
## 8. Components in the Default Configuration
|
||||
|
||||
Current default components in `reme/config/default.yaml`:
|
||||
|
||||
| ComponentEnum | Name | Backend | Description |
|
||||
|-------------------|---------------------------------|--------------------------------------------------|--------------------------------------------------------------------------------|
|
||||
| `service` | singleton | `http` | Default HTTP service. |
|
||||
| `tokenizer` | `default` | `regex` | BM25 tokenizer. |
|
||||
| `as_embedding` | `default` | Not configured by default; example uses `openai` | Provides the embedding model wrapper after uncommenting the example config. |
|
||||
| `embedding_store` | `default` | Not configured by default; example uses `local` | Depends on `as_embedding: default` after uncommenting the example config. |
|
||||
| `as_llm` | `default` | `${LLM_BACKEND:-openai}` | LLM model wrapper. |
|
||||
| `agent_wrapper` | `default` | `agentscope` | AgentScope wrapper. |
|
||||
| `agent_wrapper` | `claude_code` | `claude_code` | Claude Code wrapper. |
|
||||
| `agent_wrapper` | `codex/codex_oauth` | `codex` | Codex wrappers for API-key and OAuth authentication. |
|
||||
| `file_graph` | `default` | `local` | Wikilink graph. |
|
||||
| `file_catalog` | `default/resource/digest/dream` | `local` | File-change checkpoints. |
|
||||
| `file_chunker` | `markdown` | `markdown` | Markdown AST chunking. |
|
||||
| `file_chunker` | `json/jsonl/default` | `json/jsonl/default` | JSON, JSONL, and generic text chunkers; generic text supports `txt` and `log`. |
|
||||
| `keyword_index` | `default` | `bm25` | BM25 keyword index. |
|
||||
| `file_store` | `default` | `local` | Combines file_graph and keyword_index; defaults to `embedding_store: ""`. |
|
||||
|
||||
Note that the `search` Step configuration contains `vector_weight`, but `file_store.default.embedding_store` is empty by
|
||||
default. Vector retrieval is available only when the runtime configuration enables an embedding store.
|
||||
|
||||
## 9. Adding a Step
|
||||
|
||||
### 9.1 Minimal Step
|
||||
|
||||
Suppose you want to add a Step that converts input text to uppercase.
|
||||
|
||||
Create a file such as `reme/steps/common/uppercase.py`:
|
||||
|
||||
```python
|
||||
from ..base_step import BaseStep
|
||||
from ...components import R
|
||||
|
||||
|
||||
@R.register("uppercase_step")
|
||||
class UppercaseStep(BaseStep):
|
||||
async def execute(self):
|
||||
assert self.context is not None
|
||||
text = self.context.get("text", "")
|
||||
result = str(text).upper()
|
||||
|
||||
self.context["uppercase_text"] = result
|
||||
self.context.response.answer = result
|
||||
self.context.response.metadata["length"] = len(result)
|
||||
return self.context.response
|
||||
```
|
||||
|
||||
### 9.2 Registering the Step
|
||||
|
||||
Make sure `reme/steps/common/__init__.py` imports the new module. Add:
|
||||
|
||||
```python
|
||||
from . import uppercase
|
||||
```
|
||||
|
||||
The reason is that `@R.register("uppercase_step")` only executes after the module is imported.
|
||||
|
||||
### 9.3 Accessing Components
|
||||
|
||||
If a Step needs an existing component, prefer the Refs provided by `BaseStep`:
|
||||
|
||||
```python
|
||||
class MySearchStep(BaseStep):
|
||||
async def execute(self):
|
||||
assert self.context is not None
|
||||
results = await self.file_store.keyword_search(
|
||||
self.context.get("query", ""),
|
||||
limit=5,
|
||||
)
|
||||
...
|
||||
```
|
||||
|
||||
Common attributes available directly:
|
||||
|
||||
| Attribute | Component resolved by default |
|
||||
|----------------------|-------------------------------------|
|
||||
| `self.as_llm` | `.model` from `as_llm: default`. |
|
||||
| `self.agent_wrapper` | `agent_wrapper: default`; optional. |
|
||||
| `self.file_catalog` | `file_catalog: default`; optional. |
|
||||
| `self.file_store` | `file_store: default`. |
|
||||
|
||||
To select a non-default component from Job configuration:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- backend: my_step
|
||||
file_catalog: dream
|
||||
```
|
||||
|
||||
### 9.4 Step Design Guidance
|
||||
|
||||
| Guidance | Reason |
|
||||
|---------------------------------------------------------------------------------|-------------------------------------------------------------------------------|
|
||||
| Read input from `context` and write intermediate results to `context`. | A multi-Step Job passes data through the same context. |
|
||||
| Write the final result to `context.response`. | Services and clients consume the standard `Response`. |
|
||||
| Do not store request-scoped state on a Step instance. | A Step is rebuilt for every Job call, and stateless Steps are easier to test. |
|
||||
| A background loop that supports interruption should check `context.stop_event`. | `BackgroundJob.close()` relies on the stop event for graceful shutdown. |
|
||||
| Call `add_stream_string()` only from a StreamJob. | A normal Job has no stream queue. |
|
||||
|
||||
### 9.5 Unit Test Example
|
||||
|
||||
A Step can be instantiated directly and passed a `RuntimeContext`:
|
||||
|
||||
```python
|
||||
import pytest
|
||||
|
||||
from reme.components.runtime_context import RuntimeContext
|
||||
from reme.steps.common.uppercase import UppercaseStep
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_uppercase_step():
|
||||
ctx = RuntimeContext(text="hello")
|
||||
resp = await UppercaseStep()(ctx)
|
||||
assert resp.answer == "HELLO"
|
||||
assert ctx["uppercase_text"] == "HELLO"
|
||||
```
|
||||
|
||||
## 10. Adding a Job
|
||||
|
||||
A Job usually requires no new Python class; configure existing Steps instead. Add a new Job backend only when a new
|
||||
execution model is required.
|
||||
|
||||
### 10.1 Adding a Normal Request Job
|
||||
|
||||
Add the Job under `jobs:` in a YAML configuration:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
uppercase:
|
||||
backend: base
|
||||
description: "Convert text to uppercase."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
text:
|
||||
type: string
|
||||
description: "input text"
|
||||
required:
|
||||
- text
|
||||
steps:
|
||||
- backend: uppercase_step
|
||||
```
|
||||
|
||||
Start and call it:
|
||||
|
||||
```bash
|
||||
reme start
|
||||
reme uppercase text="hello"
|
||||
```
|
||||
|
||||
Call chain:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
CLI["CLI<br/>reme uppercase text=hello"] --> HTTP["HTTP Client"]
|
||||
HTTP --> Req["POST /uppercase"]
|
||||
Req --> S["HttpService"]
|
||||
S --> J["uppercase BaseJob<br/>job(text='hello')"]
|
||||
J --> Step["uppercase_step<br/>await step(context)"]
|
||||
Step --> Resp["context.response.answer = HELLO"]
|
||||
Resp --> JSON["Response JSON"]
|
||||
JSON --> CLIOut["CLI prints answer"]
|
||||
```
|
||||
|
||||
### 10.2 Adding a Multi-Step Job
|
||||
|
||||
A Job can chain multiple Steps:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
demo_echo:
|
||||
backend: base
|
||||
description: "Normalize query, then echo it."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
query:
|
||||
type: string
|
||||
default: ""
|
||||
min_score:
|
||||
type: number
|
||||
default: 0.5
|
||||
steps:
|
||||
- backend: demo_echo_step1
|
||||
- backend: demo_echo_step2
|
||||
```
|
||||
|
||||
The first Step writes:
|
||||
|
||||
```text
|
||||
context["processed_query"]
|
||||
context["adjusted_min_score"]
|
||||
```
|
||||
|
||||
The second Step reads those fields and writes the final `response`.
|
||||
|
||||
### 10.3 Adding a Stream Job
|
||||
|
||||
Use `backend: stream` in configuration:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
stream_uppercase:
|
||||
backend: stream
|
||||
description: "Stream uppercase text."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
text:
|
||||
type: string
|
||||
required:
|
||||
- text
|
||||
steps:
|
||||
- backend: uppercase_prepare_step
|
||||
- backend: uppercase_stream_step
|
||||
```
|
||||
|
||||
Example streaming Step:
|
||||
|
||||
```python
|
||||
from ..base_step import BaseStep
|
||||
from ...components import R
|
||||
from ...enumeration import ChunkEnum
|
||||
|
||||
|
||||
@R.register("uppercase_stream_step")
|
||||
class UppercaseStreamStep(BaseStep):
|
||||
async def execute(self):
|
||||
assert self.context is not None
|
||||
for ch in self.context.get("uppercase_text", ""):
|
||||
await self.context.add_stream_string(ch, ChunkEnum.CONTENT)
|
||||
return self.context.response
|
||||
```
|
||||
|
||||
### 10.4 Adding a Background Job
|
||||
|
||||
Use `backend: background` in configuration:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
my_watch_loop:
|
||||
backend: background
|
||||
watch_dirs: [ daily_dir ]
|
||||
watch_suffixes: [ md ]
|
||||
steps:
|
||||
- backend: init_changes_step
|
||||
monitor_type: file_store
|
||||
monitor_name: default
|
||||
dispatch_steps: [ update_index_step ]
|
||||
- backend: watch_changes_step
|
||||
dispatch_steps: [ update_index_step ]
|
||||
```
|
||||
|
||||
Characteristics of a background Job:
|
||||
|
||||
| Characteristic | Description |
|
||||
|---------------------------------|--------------------------------------------------------------------|
|
||||
| Not externally exposed | `BackgroundJob.__init__()` forces `enable_serve=False`. |
|
||||
| Has a supervisor | Restarts with exponential backoff after an exception by default. |
|
||||
| Has a stop event | Notifies the loop to exit during close. |
|
||||
| Suitable for watching/consuming | File watching, queue consumption, and periodic long-running loops. |
|
||||
|
||||
### 10.5 Adding a Cron Job
|
||||
|
||||
Use `backend: cron` in configuration:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
daily_auto_dream:
|
||||
backend: cron
|
||||
cron: "30 3 * * *"
|
||||
steps:
|
||||
- backend: dream_extract_step
|
||||
file_catalog: dream
|
||||
- backend: dream_integrate_step
|
||||
- backend: dream_topics_step
|
||||
- backend: dream_finish_step
|
||||
file_catalog: dream
|
||||
```
|
||||
|
||||
An invalid `cron` expression fails at startup.
|
||||
|
||||
### 10.6 When a New Job Backend Is Needed
|
||||
|
||||
Most use cases require only a new Step plus a YAML Job. Consider adding `reme/components/job/*.py` only in these cases:
|
||||
|
||||
| Requirement | New Job class? |
|
||||
|---------------------------------------------------------------------|--------------------------------|
|
||||
| Add a business command | No; use `backend: base`. |
|
||||
| Chain existing steps | No; use `steps:`. |
|
||||
| Need SSE/streaming output | No; use `backend: stream`. |
|
||||
| Need a background loop | No; use `backend: background`. |
|
||||
| Need cron scheduling | No; use `backend: cron`. |
|
||||
| Need entirely new scheduling, concurrency, or transaction semantics | Yes; add a Job backend. |
|
||||
|
||||
Minimal shape of a new Job backend:
|
||||
|
||||
```python
|
||||
from .base_job import BaseJob
|
||||
from ..component_registry import R
|
||||
|
||||
|
||||
@R.register("my_job_backend")
|
||||
class MyJob(BaseJob):
|
||||
async def __call__(self, **kwargs):
|
||||
# custom scheduling logic
|
||||
return await super().__call__(**kwargs)
|
||||
```
|
||||
|
||||
Also ensure the module is imported by `reme/components/job/__init__.py`.
|
||||
395
docs/en/memory_as_file.md
Normal file
|
|
@ -0,0 +1,395 @@
|
|||
# Memory as File
|
||||
|
||||
ReMe's core idea is **Memory as File, File as Memory**.
|
||||
|
||||
<p align="center">
|
||||
<img src="../figure/memory-as-file.svg" alt="ReMe Memory as File model" width="92%">
|
||||
</p>
|
||||
|
||||
**Memory as File**: long-term memory is not hidden in a black-box database. Its source material and readable memories
|
||||
live in user-owned files under the workspace. Users and agents can directly read, write, move, and delete those files;
|
||||
indexes and snapshots under `metadata/` are derived state that can be rebuilt.
|
||||
|
||||
**File as Memory**: each file is more than ordinary text. It is an indexable, linkable, and evolvable memory node. ReMe
|
||||
parses frontmatter, body chunks, and wikilink edges from files and organizes them into retrieval indexes and a graph.
|
||||
|
||||
In other words, files are both a human-readable interface and an operational interface for agents. Directory structure
|
||||
carries the memory layers, while Markdown syntax expresses content, metadata, and relationships.
|
||||
|
||||
## Design Goals
|
||||
|
||||
ReMe represents memory as files not merely for convenient storage, but to give long-term memory several essential
|
||||
properties:
|
||||
|
||||
| Goal | Meaning |
|
||||
|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Readable | Users can open the workspace directly and read daily notes, digest nodes, and source material like ordinary notes. |
|
||||
| Editable | Users and agents can correct, extend, move, or delete memory with file operations, without a specialized database client. |
|
||||
| Traceable | Long-term conclusions in digest can point back to daily, resource, or session files from a Sources section. |
|
||||
| Portable | The workspace is an ordinary directory. Markdown, JSONL, YAML, and resource files can be backed up, synchronized, versioned, or moved to other tools. |
|
||||
| Indexable | Although the files are plain text, ReMe parses frontmatter, chunks, and wikilinks to build a retrieval index and file graph. |
|
||||
| Collaborative | Humans judge and correct; agents organize, link, and retrieve. Both operate on the same files. |
|
||||
|
||||
ReMe memory is therefore neither a hidden database record nor a prompt fragment visible only to an LLM. It is first a
|
||||
file owned by the user and only then indexed by the system for retrieval.
|
||||
|
||||
## Memory Layers
|
||||
|
||||
A ReMe workspace divides memory into four layers:
|
||||
|
||||
```text
|
||||
source records -> session/ + resource/
|
||||
working memory -> daily/
|
||||
long memory -> digest/
|
||||
system state -> metadata/
|
||||
```
|
||||
|
||||
Each layer solves a different problem.
|
||||
|
||||
`session/` and `resource/` preserve source records. Files under `resource/` remain unchanged at their original path.
|
||||
Standard Auto Memory records retain conversation messages while intentionally omitting tool-result and base64 data
|
||||
blocks; this keeps recalled output and binary payloads from masquerading as user-provided evidence. Generated Agent
|
||||
runtime state instead lives under `mem_session/`.
|
||||
|
||||
`daily/` is the lightly processed layer. It organizes the day's conversations and resources into more readable daily
|
||||
notes:
|
||||
what happened, which conclusions were reached, which follow-up tasks remain, and where the source material lives. Daily
|
||||
does not aim for final abstraction; it is closer to a workbench for the day.
|
||||
|
||||
`digest/` is the deeply processed layer. It stores memory nodes that can be reused over time, such as user preferences,
|
||||
project background, procedural experience, conceptual knowledge, and decision precedents. Digest should not merely copy
|
||||
daily. It should merge recurring facts, methods, and relationships into more stable descriptions.
|
||||
|
||||
`metadata/` is the system index layer. It stores runtime state such as the file catalog, chunk index, and graph
|
||||
snapshots. Users normally do not edit this content manually. The actual editing surface is `daily/`, `digest/`, and,
|
||||
when necessary,
|
||||
`resource/`.
|
||||
|
||||
These layers let ReMe preserve both the original situation and its abstraction: daily reconstructs what happened, while
|
||||
digest answers what remains reusable later.
|
||||
|
||||
## Directory Layout
|
||||
|
||||
ReMe uses directories to express memory organization and layers. Source material first enters `resource/` or `session/`,
|
||||
then flows into `daily/`, and is finally integrated into `digest/` by `auto_dream`.
|
||||
|
||||
The corresponding automatic flows are [Auto Memory](./auto_memory.md), [Auto Resource](./auto_resource.md), and
|
||||
[Auto Dream](./auto_dream.md). Use [Memory Search](./memory_search.md) to retrieve these files.
|
||||
|
||||
```text
|
||||
<workspace_dir>/
|
||||
├── metadata/ # system index layer; persistent indexes, graph, catalogs; not a manual editing surface
|
||||
├── session/ # source-record layer; source conversations
|
||||
│ ├── dialog/
|
||||
│ │ └── <session_id>.jsonl # source messages saved by auto_memory
|
||||
│ └── claude_code/
|
||||
│ └── <session_id>.jsonl # ReMe copy used by auto_memory_cc
|
||||
├── mem_session/ # generated Agent wrapper sessions/config, not user memory
|
||||
│ ├── agentscope/
|
||||
│ ├── claude_config/
|
||||
│ └── codex/
|
||||
├── resource/ # source-record layer; original external material
|
||||
│ ├── <resource>.<ext> # root-level input uses today's date
|
||||
│ └── YYYY-MM-DD/
|
||||
│ └── <resource>.<ext> # dated input uses the directory date
|
||||
├── daily/ # lightly processed layer; facts, conversation summaries, and resource interpretations by date
|
||||
│ ├── YYYY-MM-DD.md # index page for the day
|
||||
│ └── YYYY-MM-DD/
|
||||
│ ├── <generated_name>.md # topic-named conversation or resource card
|
||||
│ └── interests.yaml # proactive interest topics generated by auto_dream
|
||||
└── digest/ # deeply processed layer; reusable personal facts, procedures, and knowledge nodes
|
||||
├── personal/
|
||||
│ └── <memory>.md # user profile, preferences, and durable personal facts
|
||||
├── procedure/
|
||||
│ └── <memory>.md # procedures, methods, and operational experience
|
||||
└── wiki/
|
||||
└── <memory>.md # general knowledge, concepts, and decision precedents
|
||||
```
|
||||
|
||||
Typical flows:
|
||||
|
||||
```text
|
||||
conversation
|
||||
-> session/dialog/<session_id>.jsonl
|
||||
-> daily/YYYY-MM-DD/<generated_name>.md
|
||||
-> digest/personal | digest/procedure | digest/wiki
|
||||
|
||||
external resource
|
||||
-> resource/[YYYY-MM-DD/]<resource>.<ext>
|
||||
-> daily/YYYY-MM-DD/<generated_name>.md
|
||||
-> digest/wiki | digest/procedure
|
||||
```
|
||||
|
||||
The first two steps focus on recording and organizing; the final step focuses on long-term distillation. `auto_memory`
|
||||
and
|
||||
`auto_resource` generate daily notes from source input, and `auto_dream` extracts and integrates digest nodes from
|
||||
daily. The generated daily filename comes from validated frontmatter `name`; `session_id`, `source_conversation`, and
|
||||
`source_resource`
|
||||
provide stable provenance and lookup identity instead of determining the filename.
|
||||
|
||||
## Markdown Format
|
||||
|
||||
ReMe favors Markdown for memory because it works well for human reading, agent editing, and programmatic parsing.
|
||||
|
||||
A typical memory file:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: Solar Supply Chain Research
|
||||
description: An end-to-end view from polysilicon to modules
|
||||
tags: [new energy, solar]
|
||||
---
|
||||
|
||||
# Conclusions
|
||||
|
||||
The solar supply chain consists of [[digest/wiki/polysilicon.md]], wafers, cells, and modules.
|
||||
One major producer is [[digest/wiki/longi.md|LONGi]].
|
||||
```
|
||||
|
||||
### Frontmatter
|
||||
|
||||
Frontmatter is a YAML block at the beginning of a file, enclosed by `---`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: Document name
|
||||
description: Document description
|
||||
source_conversation: [[session/dialog/abc.jsonl]]
|
||||
---
|
||||
```
|
||||
|
||||
The current code recognizes `name` and `description` explicitly. Other fields are preserved as additional metadata. The
|
||||
write interface merges `name`, `description`, and `metadata` into frontmatter.
|
||||
|
||||
Treat frontmatter as a node-level summary and the body as evidence, explanation, and relationships. For example:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: "User preference: documentation style"
|
||||
description: The user prefers direct, engineering-oriented technical explanations with context but without unnecessary length.
|
||||
kind: preference
|
||||
confidence: observed
|
||||
---
|
||||
|
||||
The user repeatedly asks documentation to explain motivation, boundaries, and examples while avoiding marketing language.
|
||||
|
||||
Apply this preference when following [[digest/procedure/technical-documentation.md]].
|
||||
|
||||
## Sources
|
||||
|
||||
This preference was recorded in [[daily/2026-06-20/documentation-style.md]], which captures the user's repeated guidance.
|
||||
```
|
||||
|
||||
This has three benefits:
|
||||
|
||||
1. `name` and `description` serve as lightweight summaries in lists, recall results, and agent decisions.
|
||||
2. The body can carry fuller facts, conditions, counterexamples, and sources.
|
||||
3. Ordinary wikilinks can be parsed by the graph and maintained when files move.
|
||||
|
||||
Frontmatter is best for stable, short, structured fields; the body is best for explanations meant for people. Do not put
|
||||
long body text into YAML fields.
|
||||
|
||||
### Wikilink
|
||||
|
||||
Wikilinks express relationships between files with `[[...]]`:
|
||||
|
||||
```text
|
||||
[[daily/2026-06-20/session.md]]
|
||||
[[notes/example.md#L9]]
|
||||
[[notes/example.md#L9-L10]]
|
||||
[[notes/example.md#L9-L10,L15-L20]]
|
||||
```
|
||||
|
||||
ReMe wikilinks use **literal path semantics**:
|
||||
|
||||
```text
|
||||
[[X]] -> target_path = "X"
|
||||
```
|
||||
|
||||
ReMe does not append `.md` automatically, search by filename, or automatically resolve folder notes. Use complete
|
||||
workspace-relative paths with their extensions.
|
||||
|
||||
Ordinary Markdown links such as `[label](../wiki/example.md)` do not create `FileLink` edges and are not rewritten by
|
||||
move or retarget operations.
|
||||
|
||||
Anchors such as `#L9`, `#L9-L10`, and `#L9-L10,L15-L20` remain ordinary `target_anchor` strings in the graph. The graph
|
||||
parser does not validate line-anchor syntax, so values such as `#L0`, `#L10-L9`, and `#L9,` are also stored. The `read`
|
||||
job does not interpret an anchor appended to `path`; use the separate 1-based, inclusive `start_line` and `end_line`
|
||||
arguments to read a range, for example `read(path="digest/wiki/solar.md", start_line=9, end_line=10)`.
|
||||
|
||||
Wikilinks support these behaviors:
|
||||
|
||||
```text
|
||||
body link -> create a FileLink
|
||||
move a file -> rewrite [[old path]] in inbound edges by default
|
||||
delete a file -> return remaining inbound edges so references can be cleaned up
|
||||
search match -> expand inbound and outbound links to provide context
|
||||
```
|
||||
|
||||
Parsed result:
|
||||
|
||||
```text
|
||||
FileLink
|
||||
source_path = current file
|
||||
target_path = notes/example.md
|
||||
target_anchor = L9-L10,L15-L20
|
||||
```
|
||||
|
||||
Older documents containing wrappers such as `related:: [[path]]`,
|
||||
`- related:: [[path]]`, or `[related:: [[path]]]` remain readable. ReMe ignores the surrounding text and indexes the
|
||||
inner `[[path]]` as an ordinary link. After upgrading from a version that stored typed links, run `reme reindex`
|
||||
once to rebuild the derived graph without the removed relationship field.
|
||||
|
||||
### Sources and Relationships
|
||||
|
||||
The two most important link types in ReMe are source links and conceptual relationship links.
|
||||
|
||||
A Sources section records where a long-term memory came from:
|
||||
|
||||
```markdown
|
||||
## Sources
|
||||
|
||||
The preference was observed in [[daily/2026-06-20/documentation-style.md]], and the supporting report evidence is retained in
|
||||
[[resource/2026-06-20/report.pdf]].
|
||||
```
|
||||
|
||||
A conceptual relationship link explains which other long-term memories relate to the node. Weave it into natural prose:
|
||||
|
||||
```markdown
|
||||
This analysis extends [[digest/wiki/solar-supply-chain.md]], follows
|
||||
[[digest/procedure/research-report-analysis.md]], and contrasts with
|
||||
[[digest/wiki/central-inverter.md]].
|
||||
```
|
||||
|
||||
## Human and Agent Editing
|
||||
|
||||
Because memory is stored as files, users can edit the workspace directly, while agents can read and write the same files
|
||||
through ReMe's file tools. Both follow the same conventions:
|
||||
|
||||
| Operation | Guidance |
|
||||
|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Add memory | Write to the appropriate directory, use frontmatter for Markdown, and prefer complete workspace-relative wikilinks. |
|
||||
| Edit a body | Preserve existing sources and important wikilinks. When correcting an old conclusion, explain how the new material changes the previous judgment. |
|
||||
| Move a file | ReMe's move tool rewrites old paths in inbound edges by default. After a manual move, inspect inbound links again. |
|
||||
| Delete a file | Check inbound links first. ReMe's delete tool returns source files that still point to the target, making dangling references easier to clean up. |
|
||||
| Edit metadata | Use frontmatter for short fields. When the body changes substantially, update `description` as well. |
|
||||
|
||||
A practical rule is: **an agent may rewrite the wording, but it must not lose evidence edges**. In particular, Sources
|
||||
entries and existing digest-to-digest wikilinks are the basis for traceable and extensible long-term memory.
|
||||
|
||||
## Path Semantics
|
||||
|
||||
All file tools and wikilinks use workspace-relative paths as their basic unit:
|
||||
|
||||
```text
|
||||
digest/wiki/solar.md
|
||||
daily/2026-06-20/documentation-style.md
|
||||
resource/2026-06-20/report.pdf
|
||||
```
|
||||
|
||||
This creates a clear boundary: ReMe does not treat `[[solar]]` as a repository-wide title search and does not assume
|
||||
Obsidian-style same-name resolution. `[[digest/wiki/solar.md]]` points to that exact path.
|
||||
|
||||
Recommended practices:
|
||||
|
||||
1. Include `.md` when linking a Markdown file.
|
||||
2. Use the complete source path when linking from digest to daily or resource.
|
||||
3. Rename or move files through ReMe's move tool whenever possible to avoid stale paths.
|
||||
4. Put external source material under `resource/YYYY-MM-DD/...` and long-term abstractions under `digest/...`. Do not
|
||||
put raw source material directly into digest.
|
||||
|
||||
Explicit path semantics sacrifice a little convenience when writing by hand, but provide predictability, portability,
|
||||
and automatic maintainability.
|
||||
|
||||
## Memory Chunking
|
||||
|
||||
Memory chunking divides a file into retrievable fragments. ReMe does not split Markdown at fixed lengths by default; it
|
||||
tries to preserve semantic structure.
|
||||
|
||||
This section explains how files become retrieval chunks. For index updates, BM25, vector recall, and link expansion, see
|
||||
[Memory Search](./memory_search.md).
|
||||
|
||||
Traditional RAG often uses fixed-window splitting:
|
||||
|
||||
```text
|
||||
Document
|
||||
|
|
||||
| every N tokens + overlap
|
||||
v
|
||||
chunk 1 | chunk 2 | chunk 3 | ...
|
||||
```
|
||||
|
||||
This is simple, but it can cut headings, tables, code blocks, lists, and `[[wikilinks]]` in the middle. After a match,
|
||||
the agent often sees only an isolated fragment without knowing its section or relationship to other memory nodes.
|
||||
|
||||
ReMe chunking is closer to splitting memory by file structure:
|
||||
|
||||
```text
|
||||
Markdown file
|
||||
|
|
||||
| frontmatter + headings + blocks + wikilinks
|
||||
v
|
||||
semantic chunks with document skeleton
|
||||
```
|
||||
|
||||
Comparison:
|
||||
|
||||
```text
|
||||
traditional RAG chunk
|
||||
= fixed-length text fragment + overlap
|
||||
|
||||
ReMe memory chunk
|
||||
= section structure + body fragment + line range + wikilink relationship context
|
||||
```
|
||||
|
||||
Markdown files use `MarkdownFileChunker`:
|
||||
|
||||
```text
|
||||
Markdown
|
||||
|
|
||||
| mistletoe AST
|
||||
v
|
||||
Document
|
||||
└─ H1 section
|
||||
├─ paragraph / list / table / code
|
||||
└─ H2 section
|
||||
└─ ...
|
||||
|
|
||||
v
|
||||
FileChunk[]
|
||||
```
|
||||
|
||||
Chunking rules:
|
||||
|
||||
```text
|
||||
1. Parse frontmatter first; send the body to the chunker separately.
|
||||
2. Build a section tree from heading levels.
|
||||
3. Prefer one complete section per chunk.
|
||||
4. When a section is too long, recursively split its subsections and body blocks.
|
||||
5. Repeat table headers when splitting tables.
|
||||
6. Repeat the fence when splitting code blocks.
|
||||
7. Pack lists by item.
|
||||
8. Only then split greedily by line and add [Part X/N].
|
||||
```
|
||||
|
||||
By default, every chunk includes its heading skeleton:
|
||||
|
||||
```text
|
||||
# Top-level heading
|
||||
|
||||
## Current section
|
||||
|
||||
Matched body fragment
|
||||
|
||||
## Following section heading
|
||||
```
|
||||
|
||||
This lets the agent see not only an isolated paragraph but also its structural position in the source file.
|
||||
|
||||
Non-Markdown files use `DefaultFileChunker` by default. It splits by byte size and preserves a small overlap. For
|
||||
Markdown, the chunker also avoids cutting `[[wikilinks]]` in the middle.
|
||||
|
||||
`DefaultFileChunker` and `MarkdownFileChunker` decode files with their configured `encoding` and normalize platform
|
||||
newlines to LF before indexing. Their default `invalid_encoding_policy: replace` keeps decodable content searchable
|
||||
when a source contains invalid bytes, without modifying the source file. Set `invalid_encoding_policy: strict` on a
|
||||
chunker component to reject such files instead.
|
||||
249
docs/en/memory_search.md
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
# Memory Search
|
||||
|
||||
Memory Search is ReMe's memory retrieval entry point. The default background loop continuously builds Markdown under
|
||||
`daily/` and `digest/` into a searchable chunk index and wikilink graph. At query time, it first recalls the most
|
||||
relevant fragments and then expands context along the bidirectional links of the files containing those fragments.
|
||||
`reme reindex` has a broader rebuild scope that also scans `resource/` and JSONL; it is intentionally different from the
|
||||
live watcher.
|
||||
|
||||
<p align="center">
|
||||
<img src="../figure/auto-index-and-memory-search.svg" alt="ReMe Auto Index and Memory Search indexing, recall, fusion, and link expansion" width="92%">
|
||||
</p>
|
||||
|
||||
For the general semantics of file layers, frontmatter, wikilinks, and chunking, see
|
||||
[Memory as File](./memory_as_file.md). This page focuses on index maintenance and query execution.
|
||||
|
||||
```text
|
||||
workspace files
|
||||
├─ index_update_loop: detect added / modified / deleted
|
||||
├─ update_index_step: file -> FileNode + FileChunk[]
|
||||
├─ file_store: store chunks, BM25, optional embeddings, and the wikilink graph
|
||||
└─ search_step: BM25 / vector recall -> RRF fusion -> link expansion
|
||||
```
|
||||
|
||||
## What It Searches
|
||||
|
||||
The default `index_update_loop` watches two memory directories:
|
||||
|
||||
- `daily_dir`: daily working memory and session memory cards generated by Auto Memory.
|
||||
- `digest_dir`: long-term distilled digest nodes.
|
||||
|
||||
The live watcher handles only the `md` suffix. A separate `resource_watch_loop` watches `resource_dir`, and Auto
|
||||
Resource turns those inputs into daily cards that enter the live index. When `reme reindex` is run manually, its
|
||||
configuration scans
|
||||
`daily_dir`, `digest_dir`, and `resource_dir` for `md` and `jsonl`; Markdown uses the `markdown` chunker and JSONL uses
|
||||
the
|
||||
`jsonl` chunker.
|
||||
|
||||
## How the Index Is Built
|
||||
|
||||
### Index Update
|
||||
|
||||
The background Job `index_update_loop` maintains the index using configuration from `reme/config/default.yaml`:
|
||||
|
||||
```yaml
|
||||
index_update_loop:
|
||||
backend: background
|
||||
watch_dirs: [daily_dir, digest_dir]
|
||||
watch_suffixes: [md]
|
||||
steps:
|
||||
- backend: init_changes_step
|
||||
monitor_type: file_store
|
||||
monitor_name: default
|
||||
dispatch_steps: [ update_index_step ]
|
||||
- backend: watch_changes_step
|
||||
dispatch_steps: [ update_index_step ]
|
||||
```
|
||||
|
||||
`init_changes_step` runs at startup. It scans the watched directories, compares file mtimes on disk with
|
||||
`FileNode.st_mtime` values already stored in `file_store`, calculates added, modified, and deleted changes, and passes
|
||||
`context["changes"]` to `update_index_step`.
|
||||
|
||||
While the service is running, `watch_changes_step` takes over. It uses `watchfiles.awatch()` to watch the same
|
||||
directories, groups file events within a quiet window, and uses `coalesce_changes()` to collapse repeated events for the
|
||||
same path into one stable batch of changes.
|
||||
|
||||
`update_index_step` performs the actual index writes:
|
||||
|
||||
1. Select a file chunker by suffix.
|
||||
2. Parse the file into one `FileNode` and multiple `FileChunk` objects.
|
||||
3. For an added or modified file, delete its old chunks before upserting the new chunks.
|
||||
4. For a deleted file, remove its records from `file_store`, `keyword_index`, and `file_graph`.
|
||||
5. When changes exist, dump state to `metadata/` so it can be restored on the next startup.
|
||||
|
||||
The Markdown chunker parses YAML frontmatter, heading structure, and wikilinks into `FileNode`, `FileChunk`, and
|
||||
`FileLink`
|
||||
objects. For detailed chunking rules, see [Memory as File](./memory_as_file.md#memory-chunking).
|
||||
|
||||
### Index Optimization
|
||||
|
||||
Both BM25 and the FAISS HNSW vector index use tombstone markers instead of physical removal when deleting nodes; too
|
||||
many tombstones degrade search performance. An idle-time optimization mechanism is built in—the `optimize_index_cron`
|
||||
scheduled job compacts tombstones and rebuilds indexes during off-peak hours:
|
||||
|
||||
```yaml
|
||||
optimize_index_cron:
|
||||
backend: cron
|
||||
cron: "0 2 * * *"
|
||||
steps:
|
||||
- backend: optimize_index_step
|
||||
```
|
||||
|
||||
By default it runs at 2:00 AM daily; adjust the cron expression to customize the schedule.
|
||||
|
||||
## What file_store Contains
|
||||
|
||||
The default `file_store.default` backend is `local`:
|
||||
|
||||
```yaml
|
||||
file_store:
|
||||
default:
|
||||
backend: local
|
||||
embedding_store: ""
|
||||
keyword_index: default
|
||||
file_graph: default
|
||||
```
|
||||
|
||||
It combines three kinds of capability:
|
||||
|
||||
| Part | Default state | Purpose |
|
||||
|-------------------------|---------------|-------------------------------------------------------------------------|
|
||||
| `file_chunks` | Enabled | Store `FileChunk` text, line numbers, scores, and optional embeddings. |
|
||||
| `keyword_index.default` | Enabled | BM25 inverted index where chunk ID is the document ID. |
|
||||
| `file_graph.default` | Enabled | Store `FileNode` objects and wikilink edges. |
|
||||
| `embedding_store` | Disabled | When enabled, generate embeddings for chunks and support vector recall. |
|
||||
|
||||
Out of the box, search therefore uses primarily BM25 plus link expansion. After setting `embedding_store: default`,
|
||||
`SearchStep` runs vector and keyword recall together. Additionally, switching the `file_store` `backend` from `local` to
|
||||
`faiss` upgrades vector retrieval from a linear scan to a FAISS HNSW index, offering faster recall at scale.
|
||||
|
||||
The embedding store accepts `health_check_timeout` for its startup probe. A temporary failure skips the current vector
|
||||
backfill while keeping BM25 available; a later successful provider request resumes the missing-vector backfill
|
||||
automatically.
|
||||
|
||||
Embedded integrations that have already verified a provider can call `resume_embedding(verified=True)`. When changing
|
||||
the embedding vector space, pass `rebuild=True`; persisted vectors are invalidated before a serial background rebuild,
|
||||
and vector search remains unavailable until the rebuilt vectors are safely persisted.
|
||||
|
||||
## How to Search
|
||||
|
||||
The `search` Job is also configured in `default.yaml`:
|
||||
|
||||
```yaml
|
||||
search:
|
||||
backend: base
|
||||
description: "Hybrid workspace search (vector + BM25, RRF-fused)."
|
||||
parameters:
|
||||
query: string
|
||||
limit: integer
|
||||
min_score: number
|
||||
start_date: string
|
||||
end_date: string
|
||||
steps:
|
||||
- backend: search_step
|
||||
vector_weight: 0.7
|
||||
candidate_multiplier: 5.0
|
||||
expand_links: true
|
||||
max_links_per_direction: 10
|
||||
```
|
||||
|
||||
Call it with:
|
||||
|
||||
```bash
|
||||
reme search query="recent discussions about indexing" limit=5
|
||||
```
|
||||
|
||||
Use `start_date` and `end_date` for inclusive `YYYY-MM-DD` filtering:
|
||||
|
||||
```bash
|
||||
reme search query="index regression" start_date=2026-06-01 end_date=2026-06-20 limit=10
|
||||
```
|
||||
|
||||
`search_step` executes in this order:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["query + limit"] --> B["candidates = min(200, limit * candidate_multiplier)"]
|
||||
B --> C["file_store.vector_search(...)"]
|
||||
B --> D["file_store.keyword_search(...)"]
|
||||
C --> E["RRF fusion"]
|
||||
D --> E
|
||||
E --> F["min_score filter"]
|
||||
F --> G["truncate to limit"]
|
||||
G --> H["expand_links(...)"]
|
||||
H --> I["Response.answer + metadata"]
|
||||
```
|
||||
|
||||
If only BM25 has results, the BM25 ranking is returned directly. If only vector search has results, the vector ranking
|
||||
is returned directly. When both have results, they are fused with RRF. RRF does not compare BM25 and cosine scores
|
||||
directly; it compares ranks in the two result lists:
|
||||
|
||||
```text
|
||||
fused_score = vector_weight / (60 + vector_rank)
|
||||
+ keyword_weight / (60 + keyword_rank)
|
||||
```
|
||||
|
||||
The default `vector_weight=0.7` gives semantic recall more weight when embeddings are enabled, while keyword search can
|
||||
still promote chunks with exact term matches.
|
||||
|
||||
## How BM25 Works
|
||||
|
||||
`keyword_search()` calls `keyword_index.retrieve(query, limit)`. Each chunk is a document in the BM25 index:
|
||||
|
||||
- `doc_id` is `FileChunk.id`.
|
||||
- `content` is `FileChunk.text`.
|
||||
- The tokenizer splits text into tokens.
|
||||
- The inverted index records which chunks contain each token and its term frequency within each chunk.
|
||||
- A query scores only the posting lists matching its tokens and returns the highest-scoring chunk IDs.
|
||||
|
||||
When a file changes, `LocalFileStore.upsert()` first removes the BM25 documents corresponding to the file's old
|
||||
`chunk_ids`
|
||||
and then adds the new chunk text. Deletion is lazy; the index can later be compacted with optimize.
|
||||
|
||||
## Progressive Expansion
|
||||
|
||||
"Progressive" in Memory Search does not mean putting the entire repository into one result. Retrieval expands in three
|
||||
layers:
|
||||
|
||||
1. Chunk recall: return only the `limit` most relevant text fragments.
|
||||
2. File location: each result includes `path:start_line-end_line`. Pass the path and line bounds separately as `path`,
|
||||
`start_line`, and `end_line` when calling `read`; the range is not part of the `path` value.
|
||||
3. Link neighbors: call `expand_links()` for each matched file and expand at most `max_links_per_direction` outlinks and
|
||||
inlinks.
|
||||
|
||||
Expansion data comes from `file_graph` rather than rescanning files:
|
||||
|
||||
```text
|
||||
matched chunk
|
||||
-> chunk.path
|
||||
-> file_store.get_outlinks(path)
|
||||
-> file_store.get_inlinks(path)
|
||||
-> file_store.get_nodes(neighbor_paths)
|
||||
-> render neighbor path, name, description, and anchor
|
||||
```
|
||||
|
||||
This keeps search results short while still showing which long-term nodes, resources, or other daily notes a memory
|
||||
connects to. If a result is worth pursuing, use `read path=...` to open the source or
|
||||
`traverse path=... depth=2` to continue along the wikilink graph.
|
||||
|
||||
## Return Format
|
||||
|
||||
`SearchStep` writes results in two places:
|
||||
|
||||
- `response.answer`: human-readable text. Each matched block contains its path, line numbers, score, and chunk content,
|
||||
followed by outlinks and inlinks.
|
||||
- `response.metadata`: structured programmatic results containing `results`, `link_expansion`, and `counts`.
|
||||
|
||||
Typical text structure:
|
||||
|
||||
```text
|
||||
========== daily/2026-06-20/retrieval-regression.md:12-28 [score=0.0317 keyword=4.8120] ==========
|
||||
...matched memory fragment...
|
||||
outlinks (2):
|
||||
-> digest/indexing.md name="Indexing" description="..."
|
||||
inlinks (1):
|
||||
<- daily/2026-06-19.md name="..."
|
||||
```
|
||||
|
||||
`counts` reports how many vector and keyword candidates were recalled and how many results were ultimately returned.
|
||||
With embeddings disabled by default, `vector` is usually `0` and `hybrid` is `false`.
|
||||
225
docs/en/plugin_management.md
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
# Plugin Management
|
||||
|
||||
ReMe plugins are ordinary Python distributions discovered through the `reme.plugins` entry-point group. Installing a
|
||||
plugin makes it available to the current Python environment; it does not enable the plugin in every ReMe application.
|
||||
|
||||
Keep these two operations separate:
|
||||
|
||||
```text
|
||||
reme plugins install ... install a package into the current Python environment
|
||||
plugins: [auto-fin] enable an installed plugin for one Application
|
||||
```
|
||||
|
||||
Plugin package management is local-only. It does not run through a ReMe HTTP or MCP service and never edits application
|
||||
configuration files automatically.
|
||||
|
||||
A typical plugin workflow has three stages:
|
||||
|
||||
1. Install ReMe and the plugin distribution.
|
||||
2. Configure the plugin's runtime environment as described in the
|
||||
[ReMe model-configuration guide](../../README.md#optional-model-configuration).
|
||||
3. Start an Application with the plugin explicitly enabled, for example
|
||||
`reme start plugins='["auto-fin"]'`.
|
||||
|
||||
## List installed plugins
|
||||
|
||||
```bash
|
||||
reme plugins list
|
||||
```
|
||||
|
||||
The table shows the plugin entry-point name, Python distribution, version, and plugin contract:
|
||||
|
||||
```text
|
||||
PLUGIN DISTRIBUTION VERSION FORMAT
|
||||
-------- ------------- ------- --------
|
||||
auto-fin reme-auto-fin X.Y.Z manifest
|
||||
```
|
||||
|
||||
`manifest` plugins use the current package-level `plugin.yaml` contract. `legacy` plugins use the compatible Python
|
||||
descriptor contract.
|
||||
|
||||
A manifest separates backend registration from application configuration:
|
||||
|
||||
```yaml
|
||||
backends:
|
||||
example_step: example_plugin.steps:ExampleStep
|
||||
|
||||
application_defaults:
|
||||
jobs:
|
||||
example:
|
||||
backend: base
|
||||
steps:
|
||||
- backend: example_step
|
||||
```
|
||||
|
||||
`application_defaults` is a partial `ApplicationConfig`. It is kept below the manifest's `backends` namespace because
|
||||
backend import declarations are part of plugin discovery and are not application configuration.
|
||||
|
||||
Use JSON when another local tool needs structured output:
|
||||
|
||||
```bash
|
||||
reme plugins list --json
|
||||
```
|
||||
|
||||
To compare installed plugins with one application config:
|
||||
|
||||
```bash
|
||||
reme plugins list --config default
|
||||
```
|
||||
|
||||
The optional `ENABLED` column reflects only the `plugins` list resolved from that config. A command-line override used
|
||||
by another running process is not a global enable state.
|
||||
|
||||
## Install a plugin package
|
||||
|
||||
Install a published distribution:
|
||||
|
||||
```bash
|
||||
reme plugins install reme-auto-fin
|
||||
```
|
||||
|
||||
Install or upgrade a pinned version:
|
||||
|
||||
```bash
|
||||
reme plugins install 'reme-auto-fin==X.Y.Z'
|
||||
reme plugins install reme-auto-fin --upgrade
|
||||
```
|
||||
|
||||
Install a local plugin project:
|
||||
|
||||
```bash
|
||||
reme plugins install ./plugins/auto-fin
|
||||
```
|
||||
|
||||
Use editable mode while developing it:
|
||||
|
||||
```bash
|
||||
reme plugins install ./plugins/auto-fin --editable
|
||||
```
|
||||
|
||||
ReMe invokes pip through the same Python interpreter that runs the `reme` command. Pip remains responsible for package
|
||||
resolution, downloads, dependency changes, and build execution. Install only packages and local projects you trust.
|
||||
|
||||
After installation, confirm the discovered plugin name:
|
||||
|
||||
```bash
|
||||
reme plugins list
|
||||
reme plugins validate auto-fin
|
||||
```
|
||||
|
||||
## Inspect a plugin
|
||||
|
||||
```bash
|
||||
reme plugins show auto-fin
|
||||
```
|
||||
|
||||
For a manifest plugin, the result includes its registered backend names and default Job names. JSON output is also
|
||||
available:
|
||||
|
||||
```bash
|
||||
reme plugins show auto-fin --json
|
||||
```
|
||||
|
||||
`show` identifies the package contract without constructing a ReMe Application.
|
||||
|
||||
## Validate a plugin
|
||||
|
||||
Validate an installed plugin:
|
||||
|
||||
```bash
|
||||
reme plugins validate auto-fin
|
||||
```
|
||||
|
||||
Validate a local project before installation:
|
||||
|
||||
```bash
|
||||
reme plugins validate ./plugins/auto-fin
|
||||
```
|
||||
|
||||
Validation checks the entry point, `plugin.yaml`, backend imports and component types, registry collisions, merged
|
||||
`application_defaults`, and the resulting `ApplicationConfig`. Validation imports plugin backend modules, so run it
|
||||
only for trusted code.
|
||||
|
||||
## Enable a plugin in a service
|
||||
|
||||
Installation alone does not load plugin code into an Application. Enable plugins explicitly in configuration:
|
||||
|
||||
```yaml
|
||||
plugins:
|
||||
- auto-fin
|
||||
```
|
||||
|
||||
Or add them for one service launch:
|
||||
|
||||
```bash
|
||||
reme start plugins='["auto-fin"]'
|
||||
```
|
||||
|
||||
When `config` is omitted, ReMe loads `default.yaml`. The plugin's `application_defaults` are merged below that config,
|
||||
so explicit config values and CLI overrides win. This mapping is an `ApplicationConfig` fragment, not a separate
|
||||
configuration schema. The plugin backends are registered only in that Application's local registry.
|
||||
|
||||
After the default HTTP service starts, access plugin Jobs through ReMe's CLI client or HTTP:
|
||||
|
||||
```bash
|
||||
reme auto_fin topics="黄金,AI,存储芯片"
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:2333/auto_fin \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"topics":"黄金,AI,存储芯片"}'
|
||||
```
|
||||
|
||||
When the application uses an MCP service, service-enabled plugin Jobs appear as MCP tools instead.
|
||||
|
||||
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.
|
||||
|
||||
## Uninstall a plugin
|
||||
|
||||
Use the plugin entry-point name, not necessarily the distribution name:
|
||||
|
||||
```bash
|
||||
reme plugins uninstall auto-fin
|
||||
```
|
||||
|
||||
Skip pip's confirmation prompt when needed:
|
||||
|
||||
```bash
|
||||
reme plugins uninstall auto-fin --yes
|
||||
```
|
||||
|
||||
ReMe resolves `auto-fin` to the distribution that provides it, such as `reme-auto-fin`. If one distribution provides
|
||||
multiple plugin entry points, the command lists the other plugins that will also be removed.
|
||||
|
||||
Uninstallation does not rewrite user configuration. Remove the plugin from relevant `plugins` lists yourself;
|
||||
otherwise the next Application startup fails explicitly because the configured plugin is no longer installed. Restart
|
||||
already-running ReMe processes after installing, upgrading, or uninstalling packages.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Plugin is installed but unavailable
|
||||
|
||||
Check that the `reme` command and pip package share one Python interpreter:
|
||||
|
||||
```bash
|
||||
reme plugins list
|
||||
python -c 'import sys; print(sys.executable)'
|
||||
```
|
||||
|
||||
Using `reme plugins install` avoids the most common interpreter mismatch because it runs `python -m pip` with ReMe's
|
||||
own interpreter.
|
||||
|
||||
### Plugin is installed but not loaded
|
||||
|
||||
Add its entry-point name to the Application's `plugins` list. ReMe intentionally has no global enable/disable state.
|
||||
|
||||
### Startup reports that the plugin is not installed
|
||||
|
||||
The active config still enables a missing plugin. Reinstall it or remove the corresponding name from `plugins`.
|
||||
|
||||
### Changes are not visible in a running service
|
||||
|
||||
Plugin discovery and backend registration happen during Application construction. Restart the service after changing
|
||||
installed packages.
|
||||
155
docs/en/proactive.md
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
# Proactive
|
||||
|
||||
`proactive` is ReMe's interface for reading proactive memory. It does not reanalyze daily notes or call an LLM. It only
|
||||
reads the current day's interest topics written by `auto_dream`:
|
||||
|
||||
```text
|
||||
daily/<date>/interests.yaml
|
||||
```
|
||||
|
||||
A host agent can use it to learn "what is worth proactive attention today," then decide whether to remind the user, ask
|
||||
a follow-up question, recommend a next step, or produce a proactive insight.
|
||||
|
||||
`interests.yaml` is generated by the Topics stage of [Auto Dream](./auto_dream.md). `proactive` only reads and exposes
|
||||
the result.
|
||||
|
||||
## Configuration
|
||||
|
||||
The default configuration is in `reme/config/default.yaml`:
|
||||
|
||||
```yaml
|
||||
proactive:
|
||||
backend: base
|
||||
description: "Proactive: read daily/<date>/interests.yaml and expose the latest user-interest topics."
|
||||
parameters:
|
||||
date:
|
||||
type: string
|
||||
default: ""
|
||||
include_content:
|
||||
type: boolean
|
||||
default: true
|
||||
steps:
|
||||
- backend: proactive_step
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
| Parameter | Purpose |
|
||||
|-------------------|-------------------------------------------------------------------------------------------|
|
||||
| `date` | Date to read in `YYYY-MM-DD` format. When empty, use today in the application's timezone. |
|
||||
| `include_content` | Whether to return the raw YAML in the answer and metadata. Defaults to `true`. |
|
||||
|
||||
## Input Contract
|
||||
|
||||
A typical file looks like this:
|
||||
|
||||
```yaml
|
||||
date: 2026-06-20
|
||||
topic_count: 3
|
||||
diversity_days: 7
|
||||
topics:
|
||||
- title: Quality regression in the memory retrieval pipeline
|
||||
reason: The user has recently made repeated changes to search, node_search, and dream integration.
|
||||
evidence: daily/2026-06-20/session.md
|
||||
keywords:
|
||||
- memory search
|
||||
- auto dream
|
||||
paths:
|
||||
- daily/2026-06-20/session.md
|
||||
```
|
||||
|
||||
Only the `topics` list is parsed into structured results. Every topic requires at least `title` and `reason`;
|
||||
`evidence`, `keywords`, and `paths` are supporting fields.
|
||||
|
||||
## Return Value
|
||||
|
||||
When the file is read successfully, `proactive_step` returns `summary` and `topics` in the primary answer. When
|
||||
`include_content=true`, the answer also contains `content`. The same result fields remain available in standard response
|
||||
metadata:
|
||||
|
||||
| Field | Description |
|
||||
|-----------|------------------------------------------------------|
|
||||
| `date` | The date actually read. |
|
||||
| `path` | `daily/<date>/interests.yaml`. |
|
||||
| `topics` | Parsed topic list. |
|
||||
| `content` | Raw YAML; returned only when `include_content=true`. |
|
||||
| `skipped` | `true` when the file does not exist. |
|
||||
| `error` | Read or parse error. |
|
||||
| `summary` | Short summary. |
|
||||
|
||||
When the file exists and parses successfully, the answer is structured data. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"summary": "Read 1 proactive topic(s) from daily/2026-06-20/interests.yaml",
|
||||
"topics": [
|
||||
{
|
||||
"title": "Quality regression in the memory retrieval pipeline",
|
||||
"reason": "The user has recently made repeated changes to search, node_search, and dream integration.",
|
||||
"evidence": "daily/2026-06-20/session.md",
|
||||
"keywords": ["memory search", "auto dream"],
|
||||
"paths": ["daily/2026-06-20/session.md"]
|
||||
}
|
||||
],
|
||||
"content": "date: 2026-06-20\n..."
|
||||
}
|
||||
```
|
||||
|
||||
With `include_content=false`, the `content` field is omitted from the answer. Missing-file and read-error answers remain
|
||||
explicit `Skipped: ...` and `Error: ...` messages, respectively.
|
||||
|
||||
A missing file is not an error. The call succeeds with a skipped result:
|
||||
|
||||
```text
|
||||
Skipped: interests file not found at daily/2026-06-20/interests.yaml
|
||||
```
|
||||
|
||||
This lets a host agent treat "there is no dream result for today yet" as a normal empty state.
|
||||
|
||||
## Running Proactive
|
||||
|
||||
CLI:
|
||||
|
||||
```bash
|
||||
reme proactive date=2026-06-20
|
||||
```
|
||||
|
||||
Omit the raw YAML content:
|
||||
|
||||
```bash
|
||||
reme proactive date=2026-06-20 include_content=false
|
||||
```
|
||||
|
||||
## Relationship to auto_dream
|
||||
|
||||
`proactive` is the downstream read step for `auto_dream`:
|
||||
|
||||
```text
|
||||
daily notes
|
||||
-> auto_dream
|
||||
-> daily/<date>/interests.yaml
|
||||
-> proactive
|
||||
-> host agent
|
||||
```
|
||||
|
||||
The responsibilities are divided as follows. For the complete Extract, Integrate, Topics, and Finish flow, see
|
||||
[Auto Dream](./auto_dream.md):
|
||||
|
||||
| Module | Responsibility |
|
||||
|----------------------|--------------------------------------------------------|
|
||||
| `dream_extract_step` | Extract topic candidates from changed daily inputs. |
|
||||
| `dream_topics_step` | Deduplicate, select, and write `interests.yaml`. |
|
||||
| `proactive_step` | Read `interests.yaml` and expose it to the host agent. |
|
||||
|
||||
`proactive` does not modify files, update a catalog, or decide whether the user should be interrupted. It only provides
|
||||
the day's topic material. The caller's product policy determines whether, when, and in what tone to push it to the user.
|
||||
|
||||
## Failure Modes
|
||||
|
||||
| Scenario | Behavior |
|
||||
|-------------------------------------|--------------------------------------------------------|
|
||||
| `interests.yaml` does not exist | `success=true`, `skipped=true`, `topics=[]`. |
|
||||
| YAML cannot be read or parsed | `success=false`; the answer contains an error summary. |
|
||||
| YAML exists but has no valid topics | `success=true`, `topics=[]`. |
|
||||
|
||||
Callers should therefore check `success` first, then `skipped`, and finally whether `topics` is empty.
|
||||
236
docs/en/quick_start.md
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
# Quick Start
|
||||
|
||||
## Installation
|
||||
|
||||
ReMe requires Python 3.11+.
|
||||
|
||||
Install from pip:
|
||||
|
||||
```bash
|
||||
pip install "reme-ai[core]"
|
||||
```
|
||||
|
||||
Install from source:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/agentscope-ai/ReMe.git
|
||||
cd ReMe
|
||||
pip install -e reme_studio -e ".[core]"
|
||||
cd reme_studio
|
||||
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.
|
||||
|
||||
To use agent workflows such as `auto_memory`, `auto_resource`, and `auto_dream`, configure an LLM:
|
||||
|
||||
```bash
|
||||
cat > .env <<'EOF'
|
||||
LLM_BACKEND=openai
|
||||
LLM_MODEL_NAME=qwen3.7-plus
|
||||
LLM_API_KEY=your_api_key
|
||||
LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
EOF
|
||||
```
|
||||
|
||||
You can initially omit the LLM configuration if you only need basic file operations and BM25 retrieval.
|
||||
|
||||
---
|
||||
|
||||
## Start the Service
|
||||
|
||||
```bash
|
||||
reme start
|
||||
```
|
||||
|
||||
The default service address is `127.0.0.1:2333`. If the port is already in use:
|
||||
|
||||
```bash
|
||||
reme start service.port=8181
|
||||
```
|
||||
|
||||
```bash
|
||||
reme version
|
||||
reme health_check
|
||||
reme help
|
||||
```
|
||||
|
||||
`reme help` lists server actions. Ordinary commands invoke server Jobs over HTTP.
|
||||
|
||||
The base `reme-ai` package does not include frontend assets. Install `reme-ai[web]` or `reme-ai[core]`, then open
|
||||
<http://127.0.0.1:2333/> for ReMe Studio. It uses the same service to
|
||||
browse, edit, and search the workspace and inspect the digest wikilink graph. Disable it with
|
||||
`service.web_enabled=false`, or provide a custom build with `service.web_static_dir` / `REME_WEB_STATIC_DIR`. The Job
|
||||
API still starts if no web build is found.
|
||||
|
||||
---
|
||||
|
||||
## Workspace Layout
|
||||
|
||||
The default workspace is `.reme/` under the current directory. It is created automatically at startup:
|
||||
|
||||
```text
|
||||
.reme/
|
||||
├── metadata/ # persistent indexes, graph, catalogs, and related state
|
||||
├── session/ # source conversation records
|
||||
├── mem_session/ # generated Agent wrapper sessions/config
|
||||
├── resource/ # external resources
|
||||
├── daily/ # daily notes
|
||||
└── digest/ # long-term memory
|
||||
```
|
||||
|
||||
For directory layers, Markdown frontmatter, and wikilink semantics, see
|
||||
[Memory as File](./memory_as_file.md).
|
||||
|
||||
You can also specify the workspace at startup:
|
||||
|
||||
```bash
|
||||
reme start workspace_dir=/tmp/reme-demo service.port=8181
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Write, Index, and Search
|
||||
|
||||
```bash
|
||||
reme write \
|
||||
path=digest/wiki/quick-start-demo \
|
||||
name="Quick Start Demo" \
|
||||
description="Example memory for the quick start" \
|
||||
content="# Quick Start Demo
|
||||
|
||||
The default live watcher indexes Markdown under the daily and digest directories.
|
||||
|
||||
Related link: [[digest/wiki/search-demo.md]]"
|
||||
```
|
||||
|
||||
`path` is relative to the workspace. A missing suffix is automatically completed with `.md`. For Markdown files, `name`
|
||||
and
|
||||
`description` are written to frontmatter.
|
||||
|
||||
The background watcher builds the index automatically. You can also rebuild it manually:
|
||||
|
||||
```bash
|
||||
reme reindex
|
||||
```
|
||||
|
||||
Search:
|
||||
|
||||
```bash
|
||||
reme search query="quick start example memory" limit=5
|
||||
```
|
||||
|
||||
Read:
|
||||
|
||||
```bash
|
||||
reme read path=digest/wiki/quick-start-demo start_line=1 end_line=20
|
||||
```
|
||||
|
||||
With the default configuration, retrieval is primarily BM25 plus wikilink graph expansion. Vector retrieval is supported
|
||||
by the code, but the embedding store is disabled by default. For the full retrieval flow, see
|
||||
[Memory Search](./memory_search.md).
|
||||
|
||||
---
|
||||
|
||||
## Files and Daily Notes
|
||||
|
||||
```bash
|
||||
reme stat path=digest/wiki/quick-start-demo
|
||||
reme edit path=digest/wiki/quick-start-demo old="indexes" new="continuously indexes"
|
||||
reme frontmatter_read path=digest/wiki/quick-start-demo
|
||||
reme frontmatter_update path=digest/wiki/quick-start-demo metadata='{"tags":["demo"]}'
|
||||
```
|
||||
|
||||
The file-listing Job can be called directly from the CLI:
|
||||
|
||||
```bash
|
||||
reme list path=digest recursive=true limit=50
|
||||
```
|
||||
|
||||
The equivalent HTTP call is:
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:2333/list \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"path":"digest","recursive":true,"limit":50}'
|
||||
```
|
||||
|
||||
Daily notes:
|
||||
|
||||
```bash
|
||||
reme write path=daily/2026-06-20/demo-session.md name=demo-session description="Demo session" content="Recorded content"
|
||||
reme daily_list
|
||||
reme daily_reindex
|
||||
```
|
||||
|
||||
`write` can create a daily note directly. Run `daily_reindex` when the day's index needs to be refreshed.
|
||||
|
||||
---
|
||||
|
||||
## Automatic Memory
|
||||
|
||||
```bash
|
||||
reme auto_memory \
|
||||
session_id=chat-demo \
|
||||
messages='[{"role":"user","content":"I prefer to preserve project experience as Markdown."},{"role":"assistant","content":"Recorded."}]' \
|
||||
memory_hint="Record the user's preference"
|
||||
```
|
||||
|
||||
After placing external material under `resource/YYYY-MM-DD/` or directly under `resource/`, the default background task
|
||||
watches
|
||||
`md/txt/json/jsonl/csv/yaml/html`. You can also trigger processing manually:
|
||||
|
||||
```bash
|
||||
reme auto_resource changes='[{"path":"resource/2026-06-20/report.md","change":"added"}]'
|
||||
```
|
||||
|
||||
Distill daily notes into long-term digest memory:
|
||||
|
||||
```bash
|
||||
reme auto_dream date=2026-06-20
|
||||
reme proactive date=2026-06-20
|
||||
```
|
||||
|
||||
These flows require a working LLM. Without an LLM configuration, start with basic capabilities such as `write`, `read`,
|
||||
and
|
||||
`search`.
|
||||
|
||||
For more detail, see [Auto Memory](./auto_memory.md), [Auto Resource](./auto_resource.md),
|
||||
[Auto Dream](./auto_dream.md), and [Proactive](./proactive.md).
|
||||
|
||||
---
|
||||
|
||||
## HTTP and Configuration
|
||||
|
||||
Every service-enabled Job is exposed as `POST /<job>`:
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:2333/version \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{}'
|
||||
|
||||
curl -s http://127.0.0.1:2333/search \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"query":"quick start","limit":5}'
|
||||
```
|
||||
|
||||
The default configuration comes from `reme/config/default.yaml`. Override it at startup with dot notation:
|
||||
|
||||
```bash
|
||||
reme start \
|
||||
workspace_dir=/tmp/reme-demo \
|
||||
service.host=127.0.0.1 \
|
||||
service.port=8181 \
|
||||
enable_logo=false
|
||||
```
|
||||
|
||||
You can also specify a YAML or JSON configuration file:
|
||||
|
||||
```bash
|
||||
reme start config=/path/to/custom.yaml
|
||||
```
|
||||
339
docs/en/reme-blog.md
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
# ReMe: A Personal Knowledge Base That Keeps Growing After Every Conversation
|
||||
|
||||
Every day, we talk with AI.
|
||||
|
||||
It helps us analyze projects, read papers, and troubleshoot problems. We also tell it about our preferences, plans, and ideas we have not fully worked out yet.
|
||||
|
||||
But most of the time, when a conversation ends, its value is locked away in the chat history. The next time we open a new window, the AI may remember a conclusion but not where it came from. It may find an old conversation but fail to connect it with materials we read or decisions we made later.
|
||||
|
||||
Useful long-term memory should do more than preserve what once happened. It should keep organizing information, building connections, and bringing past knowledge back into future reasoning when needed.
|
||||
|
||||
That is exactly what ReMe sets out to do.
|
||||
|
||||
> **ReMe is a local-first, self-evolving personal knowledge base for AI agents. It continuously turns conversations and resources into readable, editable, searchable, and interconnected Markdown memories, while surfacing threads worth following.**
|
||||
|
||||
GitHub: [https://github.com/agentscope-ai/ReMe](https://github.com/agentscope-ai/ReMe)
|
||||
|
||||
Documentation: [https://reme.agentscope.io](https://reme.agentscope.io)
|
||||
|
||||
<p align="center">
|
||||
<img src="../figure/reme-blog/reme-blog-cover-benchmark.png" alt="ReMe self-evolving personal knowledge base and public benchmark results" width="100%">
|
||||
</p>
|
||||
|
||||
## A Memory Loop That Keeps Growing
|
||||
|
||||
<p align="center">
|
||||
<img src="../figure/reme-blog/reme-blog-architecture.svg" alt="ReMe self-evolving memory loop" width="100%">
|
||||
</p>
|
||||
|
||||
ReMe is not another chatbot, nor does it try to replace the agents you already use. It is a local memory layer that agents such as QwenPaw, OpenClaw, Hermes, and Claude Code can share.
|
||||
|
||||
Built around a set of ordinary files, it does four things:
|
||||
|
||||
- Auto Memory extracts information worth keeping from conversations;
|
||||
- Auto Resource turns external materials into traceable memories;
|
||||
- Auto Dream consolidates daily memories into long-term knowledge;
|
||||
- Index, Search, and Proactive bring old memories back into new tasks.
|
||||
|
||||
Together, they form a `capture → index → consolidate → recall` loop:
|
||||
|
||||
- Conversations and external resources are preserved first;
|
||||
- Valuable information is organized into daily memories;
|
||||
- Scattered events are consolidated into long-term knowledge nodes;
|
||||
- Search, knowledge links, and interest discovery bring old memories back into future reasoning.
|
||||
|
||||
Most importantly, this loop is centered not on an opaque database, but on files owned by the user. Indexes, graphs, and caches are merely derived state that can always be rebuilt.
|
||||
|
||||
## Memory as File: Your Memories Are Your Files
|
||||
|
||||
<p align="center">
|
||||
<img src="../figure/reme-blog/reme-blog-memory-as-file.svg" alt="ReMe Memory as File" width="100%">
|
||||
</p>
|
||||
|
||||
ReMe's core design is called **Memory as File, File as Memory.**
|
||||
|
||||
“Memory as File” means long-term memories are not hidden inside a product. They live in Markdown, JSONL, YAML, and original resource files within your workspace. You can open them directly in VS Code, Typora, or Obsidian, and back them up or move them with Git, cloud storage, or your own synchronization setup.
|
||||
|
||||
“File as Memory” means each file is more than plain text. With YAML frontmatter, section structure, line ranges, and Wikilinks, it becomes a memory node that can be indexed, connected, and continuously evolved.
|
||||
|
||||
For example, a long-term memory about writing preferences might look like this:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: "User preference: technical writing style"
|
||||
description: Prefers stating the problem and outcome first, followed by technical details and examples.
|
||||
kind: preference
|
||||
---
|
||||
|
||||
The user wants technical articles to have a clear narrative and avoid unnecessary jargon.
|
||||
|
||||
When writing an article, refer to [[digest/procedure/Technical content writing process.md]].
|
||||
|
||||
## Sources
|
||||
|
||||
This preference was observed in [[daily/2026-08-07/content-discussion.md]], which records the user's writing guidance.
|
||||
```
|
||||
|
||||
Months later, even if you have forgotten the conversation, the agent can still read the preference, find the related process, and follow `Sources` back to the original context.
|
||||
|
||||
This is also the key difference between ReMe and “black-box memory”: agents can organize memories, but users always retain the right to inspect, correct, move, and delete them.
|
||||
|
||||
## Auto Memory: Turning Conversations into a Daily Journal
|
||||
|
||||
<p align="center">
|
||||
<img src="../figure/reme-blog/reme-blog-auto-memory.svg" alt="ReMe Auto Memory turns conversations into daily memories" width="100%">
|
||||
</p>
|
||||
|
||||
A great deal of valuable information does not begin with “please remember this.”
|
||||
|
||||
For example, you might say in a conversation:
|
||||
|
||||
> “Let's not refactor the login module this week. We can do it after the customer demo. Upgrading dependencies directly caused compatibility issues last time, so let's add regression tests first.”
|
||||
|
||||
This short passage contains project status, a time constraint, a lesson from a previous failure, and a next action. Auto Memory extracts these details from the conversation stream and writes them into a daily memory card, while retaining a source conversation record in `session/dialog/`.
|
||||
|
||||
```text
|
||||
session/dialog/project-a.jsonl Source conversation record
|
||||
daily/2026-08-07/login-refactor-decision.md Content-named memory card
|
||||
daily/2026-08-07.md Daily index, providing an overview
|
||||
```
|
||||
|
||||
`session_id` remains in the card's frontmatter for stable lookup and provenance; the filename comes from the Agent-generated
|
||||
topic/event `name`, so it does not have to match the session ID.
|
||||
|
||||
The next time the login module comes up, the agent does not need to search through the entire chat history. It can immediately see why the refactor was postponed, what went wrong before, and what should happen next.
|
||||
|
||||
It is like having a recorder who is always present—not one that mechanically transcribes every word, but one that organizes what will still matter later.
|
||||
|
||||
## Auto Resource: Bringing External Materials into the Same Memory System
|
||||
|
||||
<p align="center">
|
||||
<img src="../figure/reme-blog/reme-blog-auto-resource.svg" alt="ReMe Auto Resource turns external materials into traceable personal memories" width="100%">
|
||||
</p>
|
||||
|
||||
Not all valuable information comes from conversations. Research materials, project documents, meeting notes, archived web pages, and structured data may all become part of a personal knowledge base.
|
||||
|
||||
Auto Resource provides a general entry point for external materials. After a resource enters `resource/`, ReMe preserves the original and organizes its topics, key facts, and actionable information into daily cards with `source_resource` links. It currently supports text-based resources including Markdown, plain text, JSON, JSONL, CSV, YAML, and HTML.
|
||||
|
||||
In other words, Auto Memory builds personal knowledge from conversations, while Auto Resource builds it from non-conversational materials. Both streams flow into the same daily memory layer, where ReMe indexes, consolidates, and retrieves them together.
|
||||
|
||||
### Daily Paper: An Example External-Resource Workflow
|
||||
|
||||
Daily Paper is an optional plugin built on this file-based memory system. It collects papers from the weekly and monthly Hugging Face Papers rankings, removes items recommended recently, ranks the remaining papers, selects three, saves their PDFs, and generates Chinese paper notes and a briefing that takes about five minutes to read.
|
||||
|
||||
Imagine that you regularly follow research on agent memory. Each morning, instead of receiving only three links, you get three detailed notes already saved locally. The briefing points to the original notes through Wikilinks, and each note links back to its PDF. A month later, when you ask, “What recent methods compress long-term memory?”, those materials are already in the same retrieval system. There is no need to search through browser history again.
|
||||
|
||||
Daily Paper demonstrates how Auto Resource can be composed into a concrete workflow, but the external-resource pipeline is not limited to papers.
|
||||
|
||||
## Auto Dream: Growing Daily Notes into Connected Long-Term Knowledge
|
||||
|
||||
<p align="center">
|
||||
<img src="../figure/reme-blog/reme-blog-auto-dream.svg" alt="ReMe Auto Dream extracts, classifies, and consolidates long-term knowledge from daily memories while adding Wikilinks" width="100%">
|
||||
</p>
|
||||
|
||||
As daily notes accumulate, a new problem emerges: the information is all there, but it remains scattered across different dates.
|
||||
|
||||
Suppose conversations and external materials give you three pieces of information about the same problem:
|
||||
|
||||
- The first time a build hung, clearing the cache did not help;
|
||||
- A project document later confirmed that insufficient Node.js memory was the root cause;
|
||||
- A third note added that the issue occurs more often in large TypeScript projects.
|
||||
|
||||
By default, Auto Dream looks at the two most recent days ending at the target date and sends only daily files changed since
|
||||
the previous run to extraction. It merges cross-file evidence for the same abstraction and keeps only the strongest reusable
|
||||
memories within a default cap of five units, then writes them into three categories of long-term memory:
|
||||
|
||||
- `Personal`: preferences, conventions, and constraints specific to a user, team, or project;
|
||||
- `Procedure`: repeatable processes, methods, and troubleshooting guides;
|
||||
- `Wiki`: general definitions, principles, observations, and knowledge.
|
||||
|
||||
For example, the information above would become `digest/procedure/Troubleshooting frozen frontend builds.md`, which records the triggering conditions, diagnostic sequence, failed attempts, solution, and scope of applicability—instead of simply concatenating several daily notes.
|
||||
|
||||
When consolidating each memory unit, Auto Dream first searches existing nodes across `personal`, `procedure`, and `wiki`, distinguishing between the “same abstraction” and “related knowledge.” The same abstraction determines how the target node evolves:
|
||||
|
||||
- `CREATE`: no equivalent memory exists, so create a new node;
|
||||
- `CORROBORATE`: the same conclusion appears again, so add its source and strengthen confidence;
|
||||
- `REFINE`: new material adds conditions, steps, or details;
|
||||
- `CORRECT`: new information corrects an earlier conclusion.
|
||||
|
||||
Related knowledge is written into the body as Wikilinks during the same consolidation process. This is Auto Link. For example, “Troubleshooting frozen frontend builds” can connect general knowledge, team preferences, and original evidence at once:
|
||||
|
||||
```markdown
|
||||
This issue often occurs in [[digest/wiki/Large TypeScript projects.md]]. When resolving it,
|
||||
follow the “add regression tests first” convention in [[digest/personal/Team change preferences.md]].
|
||||
|
||||
## Sources
|
||||
|
||||
The root cause and applicable scenarios were documented in
|
||||
[[daily/2026-08-07/build-debug.md|Build troubleshooting record]].
|
||||
```
|
||||
|
||||
Knowledge evolves and links are created in the same workflow. Relationships are not invisible edges hidden in a graph database; they are readable, editable content in the files themselves. The files can rebuild the graph—the graph never takes control of the files.
|
||||
|
||||
## Memory Index: Turning Ordinary Files into a Searchable Memory Network
|
||||
|
||||
<p align="center">
|
||||
<img src="../figure/reme-blog/reme-blog-memory-index.svg" alt="ReMe Memory Index build process" width="100%">
|
||||
</p>
|
||||
|
||||
Markdown is easy for people to read, but if files are merely piled into directories, agents still struggle to find them
|
||||
quickly. The default live index watches Markdown under `daily/` and `digest/`. A separate resource workflow watches
|
||||
`resource/` and turns those files into daily cards that enter the same index. For a full rebuild from existing files,
|
||||
`reme reindex` also scans `resource/` and JSONL.
|
||||
|
||||
A Markdown file is parsed into:
|
||||
|
||||
- One file node containing file-level information such as its path and frontmatter;
|
||||
- Multiple semantic chunks split, wherever possible, along the boundaries of headings, paragraphs, lists, and code blocks, while retaining section structure and line numbers;
|
||||
- Multiple Wikilink edges recording what the file points to and what points back to it.
|
||||
|
||||
For retrieval, ReMe can combine three types of signals:
|
||||
|
||||
| Retrieval signal | Problem it solves | Example |
|
||||
|------------------|-------------------|---------|
|
||||
| BM25 keywords | Exact names, terms, and identifiers must not be missed | “CATL”, “issue #184” |
|
||||
| Embedding vectors | Semantically similar wording should still match | “build frozen” and “packaging stage not responding” |
|
||||
| Wikilink graph | Reveal upstream and downstream relationships after finding a node | From “cobalt” to “ternary cathodes” and related research notes |
|
||||
|
||||
The default configuration enables BM25 and Wikilink expansion out of the box. Embeddings are optional and participate in vector retrieval only when enabled. Indexes, graphs, and caches are stored in `metadata/`; even if deleted, they can be rebuilt from the user's source files.
|
||||
|
||||
## Memory Search: Find the Answer First, Then Expand Relationships Progressively
|
||||
|
||||
<p align="center">
|
||||
<img src="../figure/reme-blog/reme-blog-memory-search.svg" alt="ReMe hybrid search and progressive expansion" width="100%">
|
||||
</p>
|
||||
|
||||
Many RAG systems put all Top-K passages into the context at once. This is simple, but it creates two problems: isolated chunks lack context, while expanding every neighbor's full text quickly consumes tokens.
|
||||
|
||||
ReMe's hybrid search lets BM25 and optional vector retrieval produce their own candidates, then fuses the rankings with RRF. Instead of directly comparing BM25 scores with cosine similarities—two different scales—RRF combines where each result appears in the two ranked lists.
|
||||
|
||||
After retrieval, information expands progressively in three layers:
|
||||
|
||||
1. **Start with the matching passage**: return the most relevant chunk, file path, and line numbers;
|
||||
2. **Then inspect the relationship directory**: show the file's outgoing and incoming links, including only each neighbor's path, name, description, and anchor rather than loading all of its content immediately;
|
||||
3. **Finally, go deeper as needed**: the agent decides which relationship is genuinely relevant, then reads the original file or continues traversing the graph.
|
||||
|
||||
For example, you ask: “What was the name of the book about attention that Alice recommended last time?”
|
||||
|
||||
The first step may find a dinner note that says only, “The title contains the word ‘deep.’” The result also shows that the note links to Alice's personal node and is backlinked by reading notes for *Deep Work*.
|
||||
|
||||
The agent does not need to load Alice's entire profile, every reading note, and a whole month of journal entries into its context. It only needs to follow the most relevant link and read once more before answering:
|
||||
|
||||
> It was *Deep Work*. Alice recommended it at that dinner, and you later read Chapter 3 and left notes.
|
||||
|
||||
This resembles human association: first recall a fragment, then follow the trail to recover the full context.
|
||||
|
||||
## Proactive: Discovering Needs You Have Not Yet Put into Words
|
||||
|
||||
<p align="center">
|
||||
<img src="../figure/reme-blog/reme-blog-proactive.svg" alt="ReMe Proactive's two-way memory loop" width="100%">
|
||||
</p>
|
||||
|
||||
At this point, ReMe has two input streams that continuously enrich the knowledge base:
|
||||
|
||||
- Auto Memory distills personal context from ongoing conversations;
|
||||
- Auto Resource adds new knowledge from external materials.
|
||||
|
||||
Proactive reverses the direction. From accumulated conversations and materials, it discovers topics you have not yet resolved or may want to pursue, along with information you have not noticed but that closely relates to your recent work. These discoveries can then guide what external knowledge enters the system next.
|
||||
|
||||
For example, over the past week you separately mentioned that:
|
||||
|
||||
- Search results lack sources;
|
||||
- Long documents lose section context after chunking;
|
||||
- You want to compare several agent-memory evaluation methods.
|
||||
|
||||
Even though you never explicitly said, “Help me systematically study the explainability of memory retrieval,” Auto Dream can distill an interest topic from these daily memories:
|
||||
|
||||
```yaml
|
||||
title: Evaluating the explainability of memory retrieval
|
||||
reason: The user has recently focused on source tracing, structure-aware chunking, and memory evaluation.
|
||||
evidence: daily/2026-08-07/search-discussion.md
|
||||
keywords:
|
||||
- memory search
|
||||
- source attribution
|
||||
- benchmark
|
||||
```
|
||||
|
||||
In a future beta release, after reading this topic through Proactive, a host agent could ask at an appropriate moment, “Would you like me to turn the retrieval issues we discussed recently into an evaluation plan?” It could also use the topic to initiate a user-authorized research workflow. Users would not need to identify and explicitly specify their interests and scope in advance; external resources related to needs implicit in their conversations could continue flowing into the knowledge base.
|
||||
|
||||
There is an important boundary: **ReMe's Proactive feature only reads and exposes interest topics. It does not independently access the internet, send notifications, or rewrite the knowledge base.**
|
||||
It does not guess your interests from nowhere. It surfaces clues that already appeared in your behavior and conversations but have not yet been explicitly stated.
|
||||
|
||||
## Performance: Can It Retrieve Information from Very Long Histories?
|
||||
|
||||
ReMe uses LongMemEval and BEAM to evaluate memory across multiple sessions and extremely long conversations. During evaluation, the agent can use ReAct to search and read over multiple rounds, generate an answer, and then receive an LLM-as-judge score.
|
||||
|
||||
| Benchmark | Setting | Sample size | Agentic score | Primary capabilities tested |
|
||||
|-----------|---------|------------:|---------------:|-----------------------------|
|
||||
| **LongMemEval cleaned-s** | **Overall** | **500 questions** | **89.4%** | Cross-session retrieval, knowledge updates, and temporal reasoning |
|
||||
| BEAM | 100K context | 20 cases / 400 questions | 66.1% | Ten types of long-context memory tasks |
|
||||
| BEAM | 1M context | 35 cases / 700 questions | 65.0% | Larger-scale, ultra-long conversation settings |
|
||||
|
||||
LongMemEval cleaned-s includes single-session facts, preferences, multi-session reasoning, knowledge updates, temporal reasoning, and other question types. ReMe achieved an overall Agentic score of 89.4% across 500 questions. See the [LongMemEval evaluation guide](../../benchmark/longmemeval/README.md) for the complete workflow and breakdown.
|
||||
|
||||
BEAM covers ten categories of tasks, including contradiction resolution, event ordering, information extraction, knowledge updates, multi-session reasoning, preference following, summarization, and temporal reasoning. ReMe scored 66.1% on 20 cases / 400 questions with a 100K context and 65.0% on 35 cases / 700 questions with a 1M context. See the [BEAM evaluation guide](../../benchmark/beam/README.md) for the complete setup.
|
||||
|
||||
ReMe also uses $\pi$-Bench to evaluate the potential of multi-session reasoning to improve agent proactivity. The PROC score in $\pi$-Bench evaluates capabilities including directly fulfilling hidden intent, guiding targeted clarification, recovering cross-session preferences, reusing cross-session conventions, inferring cross-task dependencies, and advancing underspecified requests. Across five user personas, ReMe Agent achieved an average PROC score of 0.580, outperforming NanoBot by 2.4% under the same test-model configuration. See the [$\pi$-Bench paper](https://arxiv.org/abs/2605.14678) for details about the benchmark.
|
||||
|
||||
## Who Is ReMe For?
|
||||
|
||||
### People Who Use Agents Directly
|
||||
|
||||
If you want AI to understand you continuously throughout a long-term collaboration, ReMe lets your personal assistant stop starting from scratch. Your preferences, project context, important materials, and past decisions accumulate through ongoing conversations and can be found again when they are genuinely relevant.
|
||||
|
||||
Researchers, engineers, analysts, and other knowledge workers all fall into this category. Researchers can connect papers, discussions, and reading notes; engineers can preserve project decisions and cross-session troubleshooting experience; analysts can build an evolving record of events, perspectives, and sources. Their professions differ, but they share the same need: AI that can understand the past, accumulate experience, and recover supporting context for the next task.
|
||||
|
||||
### Developers Who Build Agents
|
||||
|
||||
If you are building an agent, harness, or AI product, ReMe provides an independent long-term memory layer. Through its CLI, HTTP API, MCP Server, or Python API, you can let multiple agents share the same file-based workspace without reimplementing memory extraction, knowledge organization, hybrid retrieval, and relationship expansion for every application.
|
||||
|
||||
Files remain the source of truth, while indexes and caches can be rebuilt at any time. This also makes it easier to determine whether an incorrect retrieval originated in the source material, memory consolidation, or the retrieval pipeline.
|
||||
|
||||
Ultimately, ReMe is for users and developers who want AI to do more than “answer this one request”: they want it to understand the past, accumulate experience, and know them better over the course of a long-term collaboration. We want agents to understand you better the more you use them—but that understanding should not live in a black box that you cannot inspect, correct, or take with you.
|
||||
|
||||
ReMe's answer is straightforward:
|
||||
|
||||
- Memories are files owned by the user;
|
||||
- Original information preserves what happened, while long-term knowledge preserves the abstraction;
|
||||
- New conversations and resources keep flowing in, while existing knowledge is continuously supplemented and corrected;
|
||||
- Every conclusion can be traced to relationships and sources through Wikilinks;
|
||||
- Indexes and caches serve the files rather than replace them;
|
||||
- Agents can remember, organize, search, and discover, but users always retain ultimate control.
|
||||
|
||||
When these mechanisms come together, a personal knowledge base is no longer a repository you must maintain by hand.
|
||||
|
||||
It remembers a little more after every conversation and understands a little more after every new resource. At night, it reorganizes scattered experiences. When a future question arises, it follows the connections between pieces of knowledge and brings back the memory you actually need.
|
||||
|
||||
That is what ReMe sets out to do: **make memory not only persistent, but continuously evolving.**
|
||||
|
||||
## Integrate ReMe with the Agents You Already Use
|
||||
|
||||
ReMe can run as a local memory service accessed through its CLI, HTTP API, or MCP Server, or it can be embedded in a host
|
||||
process through its Python API. The default HTTP service can also serve ReMe Studio at the same address for browsing,
|
||||
editing, and searching the workspace and inspecting the digest wikilink graph. Different agents can choose the integration
|
||||
that best fits their runtime environment and share the same local memory workspace when needed.
|
||||
|
||||
| Agent | Recommended integration | Capabilities after integration |
|
||||
|-------|-------------------------|--------------------------------|
|
||||
| **DeepSeek Harness** | Install [`@agentscope-ai/reme`](../../typescript/README.md#deepseek-harness) as a DSH profile bundle. | Long-term memory guidance, `reme_search`, automatic capture of completed main-agent turns, and scheduled Auto Dream. |
|
||||
| **OpenClaw** | Install [`@agentscope-ai/reme`](../../typescript/README.md#openclaw) as the native memory plugin. | Recall before conversational root-agent runs, explicit search, automatic turn capture, and scheduled Auto Dream. |
|
||||
| **QwenPaw** | Embed ReMe in-process through the Python API. | Reuse the host application's lifecycle and model configuration while keeping memories local and file-based. |
|
||||
| **Claude Code** | Start the streamable HTTP MCP Service and install [`integrations/claude_code/reme`](../../integrations/claude_code/reme). | MCP memory-recall tools, the `reme-memory` skill, and a Stop hook that automatically records sessions. |
|
||||
| **Hermes** | Start the HTTP Service and install [`integrations/hermes_agent`](../../integrations/hermes_agent). | Automatically recall relevant memories before model calls and invoke `auto_memory` asynchronously after each conversation turn. |
|
||||
| **Codex and other CLI-capable agents** | Copy or install [`skills/reme_memory/SKILL.md`](../../skills/reme_memory/SKILL.md). | Search, read, and write memories through the CLI; automatic recording requires the host agent to integrate explicitly with the conversation lifecycle. |
|
||||
|
||||
For installation, configuration, and integration demos, see the [README](../../README.md).
|
||||
|
||||
## Contributions Welcome
|
||||
|
||||
ReMe is open source, and we welcome the community's help in making this self-evolving memory system more complete:
|
||||
|
||||
- Integrate more agents and harnesses so different runtime environments can use the same user-owned long-term memory;
|
||||
- Contribute new Auto Resource sources and workflows so papers, news, and other public materials can continuously enter the knowledge base;
|
||||
- Improve Auto Memory, Auto Dream, Auto Link, hybrid search, and Proactive so memories are organized more accurately, relationships are clearer, and retrieval is more reliable;
|
||||
- Add application examples, evaluation tasks, and diagnostic reports to help us understand successes and failures in real long-term use;
|
||||
- Improve documentation and tests, or share your needs and ideas for personal AI memory through an Issue.
|
||||
|
||||
Whether it is a code contribution, a use case, a bug report, or a new memory workflow, every contribution can bring ReMe closer to a truly readable, controllable, and continuously evolving personal knowledge base.
|
||||
|
||||
Contribution guide: [https://docs.agentscope.io/reme/latest/en/contribution](https://docs.agentscope.io/reme/latest/en/contribution)
|
||||
473
docs/en/reme_scene.md
Normal file
|
|
@ -0,0 +1,473 @@
|
|||
# ReMe Application Scenarios
|
||||
|
||||
This document describes how ReMe is used in real agent workflows. Directory names, Job names, and capability boundaries are
|
||||
based on the latest code under `reme/`.
|
||||
|
||||
The common ReMe pattern is:
|
||||
|
||||
```text
|
||||
Conversations / external resources
|
||||
|
|
||||
+--> auto_memory / auto_resource
|
||||
| write to daily/
|
||||
|
|
||||
+--> auto_dream
|
||||
| distill daily/ into digest/{personal,procedure,wiki}/
|
||||
| and write daily/<date>/interests.yaml
|
||||
|
|
||||
+--> search / node_search / read / traverse / proactive
|
||||
let agents retrieve, associate, read, and inspect interest topics
|
||||
```
|
||||
|
||||
## Scenario 1: A Supply-Chain Knowledge Base for a Financial Analyst
|
||||
|
||||
**Persona**: Analyst Wang, a new-energy industry researcher. Every day, Wang processes research reports, industry news,
|
||||
company interviews, and spoken post-market notes.
|
||||
|
||||
**Pain point**: Information is scattered across text reports, web clippings, group messages, interview notes, and
|
||||
conversations. A few days later, when asking, "How did the cobalt-price issue come up in the last CATL interview?", it is
|
||||
difficult to reconnect the original event, company, material route, and upstream mining companies.
|
||||
|
||||
### Day 1: Post-market discussion and reports enter Daily
|
||||
|
||||
Analyst Wang synchronizes three reports to `resource/2026-05-18/`, then tells the agent:
|
||||
|
||||
```text
|
||||
Glencore released its third-quarter report today, with cobalt output down 18% year over year.
|
||||
We need to closely track how mining-rights policy changes in the DRC affect CMOC's KFM mine.
|
||||
Downstream ternary-cathode manufacturers continue to move toward high-nickel, low-cobalt chemistry.
|
||||
```
|
||||
|
||||
ReMe produces two kinds of lightly processed files:
|
||||
|
||||
```text
|
||||
resource/
|
||||
└── 2026-05-18/
|
||||
├── glencore-q3.md
|
||||
├── cobalt-policy.md
|
||||
└── cathode-trend.md
|
||||
|
||||
session/
|
||||
└── dialog/
|
||||
└── 2026-05-18-close.jsonl
|
||||
|
||||
daily/
|
||||
├── 2026-05-18.md
|
||||
└── 2026-05-18/
|
||||
├── cobalt-supply-risk.md
|
||||
├── glencore-output-update.md
|
||||
├── drc-cobalt-policy.md
|
||||
├── high-nickel-cathode-trend.md
|
||||
└── interests.yaml # generated after auto_dream
|
||||
```
|
||||
|
||||
The corresponding flow is:
|
||||
|
||||
- `auto_memory` saves a filtered source conversation record to `session/dialog/<session_id>.jsonl`, then asks the agent to write
|
||||
important facts to a topic-named `daily/<date>/<generated_name>.md`. The note keeps `session_id` and
|
||||
`source_conversation` in frontmatter for stable lookup and provenance.
|
||||
- `resource_watch_loop` watches text-file changes under `resource/` and triggers `auto_resource_step` to write a daily note
|
||||
with `source_resource`. The agent suggests a content-based filename, which the system sanitizes and de-duplicates; it is
|
||||
not guaranteed to match the resource filename.
|
||||
- Auto Memory, Auto Resource, and Auto Dream refresh `daily/<date>.md` after writing.
|
||||
|
||||
### Day 1 evening: Auto Dream writes to Digest
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
reme auto_dream date=2026-05-18
|
||||
```
|
||||
|
||||
`auto_dream` is a four-step pipeline:
|
||||
|
||||
```text
|
||||
dream_extract_step
|
||||
scan the daily window from 2026-05-17 through 2026-05-18 by default
|
||||
output at most 5 units plus topics from changed files
|
||||
dream_integrate_step
|
||||
recall existing digest nodes with node_search for each unit
|
||||
decide CREATE / CORROBORATE / REFINE / CORRECT
|
||||
dream_topics_step
|
||||
write daily/2026-05-18/interests.yaml
|
||||
dream_finish_step
|
||||
checkpoint successfully processed daily inputs
|
||||
```
|
||||
|
||||
Outputs in this scenario:
|
||||
|
||||
```text
|
||||
digest/
|
||||
└── wiki/
|
||||
├── glencore.md
|
||||
├── cobalt.md
|
||||
└── ternary-cathodes.md
|
||||
```
|
||||
|
||||
Example `digest/wiki/cobalt.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: Cobalt
|
||||
description: A key raw material for lithium-battery cathodes, with production concentrated in the DRC
|
||||
---
|
||||
|
||||
# Cobalt
|
||||
|
||||
Used by [[digest/wiki/ternary-cathodes.md]]; a major producer is [[digest/wiki/glencore.md]].
|
||||
|
||||
## Supply
|
||||
Glencore's third-quarter cobalt output fell 18% year over year. Continue monitoring how tighter supply affects prices.
|
||||
|
||||
## Policy risk
|
||||
Changes to mining-rights policy in the DRC may affect KFM mine operations and should be tracked together with CMOC.
|
||||
|
||||
## Sources
|
||||
|
||||
The production decline and policy risk were recorded in [[daily/2026-05-18/cobalt-supply-risk.md]].
|
||||
```
|
||||
|
||||
Note that wikilinks use literal path semantics. Prefer complete workspace-relative paths with the `.md` extension. ReMe
|
||||
does not automatically resolve `[[cobalt]]` to a particular file.
|
||||
|
||||
### Day 2: Interview findings refine existing nodes
|
||||
|
||||
Analyst Wang attends a CATL investor interview:
|
||||
|
||||
```text
|
||||
CATL is switching fully to high-nickel 9-series ternary cathodes this year, so cobalt usage will keep falling.
|
||||
Capacity utilization is 85%, five percentage points higher than last quarter.
|
||||
```
|
||||
|
||||
`auto_memory` writes:
|
||||
|
||||
```text
|
||||
daily/2026-05-19/catl-interview.md
|
||||
```
|
||||
|
||||
During `auto_dream date=2026-05-19`:
|
||||
|
||||
- `dream_extract_step` extracts "CATL's switch to high-nickel ternary cathodes" and "CATL capacity utilization."
|
||||
- `dream_integrate_step` uses `node_search` to recall `digest/wiki/ternary-cathodes.md` and
|
||||
`digest/wiki/cobalt.md` from `digest/`.
|
||||
- The agent applies `REFINE` to `ternary-cathodes.md`, adding CATL's 9-series transition as a case.
|
||||
- The agent applies `CREATE` to, or updates, `digest/wiki/catl.md`.
|
||||
|
||||
The graph gradually grows into:
|
||||
|
||||
```text
|
||||
digest/wiki/
|
||||
├── glencore.md
|
||||
├── cobalt.md
|
||||
├── ternary-cathodes.md # REFINE: high-nickel, low-cobalt trend + CATL case
|
||||
└── catl.md # CREATE: capacity utilization + 9-series transition
|
||||
```
|
||||
|
||||
### Day 5: The user searches for "upstream and downstream battery companies"
|
||||
|
||||
Analyst Wang asks:
|
||||
|
||||
```text
|
||||
Help me analyze the upstream and downstream lithium-battery supply chain.
|
||||
```
|
||||
|
||||
The agent calls:
|
||||
|
||||
```bash
|
||||
reme search query="lithium battery upstream downstream ternary cathode cobalt CATL" limit=5
|
||||
```
|
||||
|
||||
`search` returns chunk content, line numbers, scores, and outlink/inlink directories for matched files. With the default
|
||||
configuration, results come from BM25 plus graph expansion.
|
||||
|
||||
The result shape is:
|
||||
|
||||
```text
|
||||
========== digest/wiki/cobalt.md:8-20 [score=0.0148 keyword=3.7112] ==========
|
||||
# Cobalt
|
||||
## Supply
|
||||
Glencore's third-quarter cobalt output fell 18% year over year...
|
||||
|
||||
outlinks:
|
||||
-> digest/wiki/ternary-cathodes.md name="Ternary Cathodes"
|
||||
-> digest/wiki/glencore.md name="Glencore"
|
||||
inlinks:
|
||||
<- digest/wiki/ternary-cathodes.md name="Ternary Cathodes"
|
||||
|
||||
========== digest/wiki/ternary-cathodes.md:5-18 [score=0.0139 keyword=3.2017] ==========
|
||||
...
|
||||
```
|
||||
|
||||
The agent can assemble a supply-chain outline from the neighbor directory alone. When it needs details, it can call:
|
||||
|
||||
```bash
|
||||
reme read path=digest/wiki/catl.md
|
||||
reme traverse path=digest/wiki/cobalt.md depth=2 direction=both
|
||||
```
|
||||
|
||||
The final response might be:
|
||||
|
||||
```text
|
||||
The lithium-battery chain can be divided into three segments:
|
||||
1. Upstream raw materials: cobalt supply is concentrated in the DRC. Glencore is a major producer, and the policy impact
|
||||
on CMOC's KFM mine should be monitored.
|
||||
2. Midstream materials: ternary cathodes continue to move toward high-nickel, low-cobalt chemistry.
|
||||
3. Downstream batteries: CATL's move to 9-series high-nickel ternary cathodes confirms the downstream demand direction.
|
||||
|
||||
These conclusions come from the post-market conversation on 2026-05-18, the Glencore quarterly-report resource note, and
|
||||
the CATL interview record on 2026-05-19.
|
||||
```
|
||||
|
||||
### Proactive: Read the day's interest topics
|
||||
|
||||
`auto_dream` writes:
|
||||
|
||||
```text
|
||||
daily/2026-05-18/interests.yaml
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
date: 2026-05-18
|
||||
topic_count: 3
|
||||
diversity_days: 7
|
||||
topics:
|
||||
- title: Impact of DRC mining-rights policy on cobalt supply
|
||||
reason: The user repeatedly mentioned KFM and cobalt-price risk today
|
||||
keywords: [cobalt, DRC, CMOC, KFM]
|
||||
paths:
|
||||
- daily/2026-05-18/cobalt-supply-risk.md
|
||||
```
|
||||
|
||||
Call:
|
||||
|
||||
```bash
|
||||
reme proactive date=2026-05-18
|
||||
```
|
||||
|
||||
The `proactive` Job returns the topics from `interests.yaml` and, optionally, the raw YAML content.
|
||||
|
||||
### Value of this scenario
|
||||
|
||||
- The analyst focuses on reading materials and expressing judgments. ReMe writes facts to daily and distills long-lived
|
||||
concepts into digest.
|
||||
- `node_search` lets dream find existing digest nodes before writing, preventing a new file for the same concept every day.
|
||||
- Graph expansion in `search` lets the agent inspect structure before reading full content, reducing wasted context.
|
||||
- Every conclusion is stored in Markdown and can be audited with an ordinary editor.
|
||||
|
||||
## Scenario 2: Cross-session Procedural Memory for a Coding Agent
|
||||
|
||||
**Persona**: Developer Zhang, who works on project issues over time in Claude Code, AgentScope, or other agents.
|
||||
|
||||
**Pain point**: The same kind of bug appears repeatedly, but the agent starts its investigation from scratch each time. The
|
||||
user's coding style, testing habits, and project preferences exist only in the current conversation.
|
||||
|
||||
### First session: The build stalls
|
||||
|
||||
The user says:
|
||||
|
||||
```text
|
||||
pnpm build stalls at 92%. CPU usage is low, but memory keeps growing.
|
||||
```
|
||||
|
||||
The agent's investigation:
|
||||
|
||||
```text
|
||||
1. Clear caches: no effect.
|
||||
2. Upgrade the terser plugin: no effect.
|
||||
3. Discover that fork-ts-checker is running out of memory.
|
||||
4. Set NODE_OPTIONS=--max-old-space-size=8192: the build succeeds.
|
||||
```
|
||||
|
||||
`auto_memory` writes:
|
||||
|
||||
```text
|
||||
session/dialog/build-oom-2026-03-10.jsonl
|
||||
daily/2026-03-10/build-oom-2026-03-10.md
|
||||
```
|
||||
|
||||
After `auto_dream`, ReMe generates:
|
||||
|
||||
```text
|
||||
digest/
|
||||
├── procedure/
|
||||
│ └── typescript-build-oom.md
|
||||
└── personal/
|
||||
└── code-style.md
|
||||
```
|
||||
|
||||
Example `digest/procedure/typescript-build-oom.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: TypeScript project build OOM diagnostic path
|
||||
description: When a build stalls and memory grows, check the type-checking process first
|
||||
---
|
||||
|
||||
# TypeScript Project Build OOM Diagnostic Path
|
||||
|
||||
Apply [[digest/personal/code-style.md]] while following this runbook.
|
||||
|
||||
## Symptoms
|
||||
The build stalls near the end. CPU usage is low, but memory keeps growing.
|
||||
|
||||
## Preferred path
|
||||
1. Check whether fork-ts-checker or another type-checking subprocess is running out of memory.
|
||||
2. Try `NODE_OPTIONS=--max-old-space-size=8192` first.
|
||||
3. Clear caches or upgrade the minification plugin only when there is specific evidence to do so.
|
||||
|
||||
## Known ineffective paths
|
||||
- Deleting `.cache` alone did not resolve the issue on 2026-03-10.
|
||||
- Upgrading the terser plugin did not resolve the issue on 2026-03-10.
|
||||
|
||||
## Sources
|
||||
|
||||
The failed attempts and successful memory adjustment were recorded in [[daily/2026-03-10/build-oom-2026-03-10.md]].
|
||||
```
|
||||
|
||||
Example `digest/personal/code-style.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: User coding-style preferences
|
||||
description: Engineering preferences repeatedly expressed by the user
|
||||
---
|
||||
|
||||
# User Coding-style Preferences
|
||||
|
||||
## Comments
|
||||
The user dislikes comments that restate what the code literally does. Comments should explain WHY or a complex constraint.
|
||||
|
||||
## Tests
|
||||
The user prefers focused tests around the risk and dislikes broad, unrelated refactoring.
|
||||
```
|
||||
|
||||
### Second session: Quickly recalling a similar problem
|
||||
|
||||
Six weeks later, the user asks:
|
||||
|
||||
```text
|
||||
vite build also stalls during bundling. Is it the same kind of issue?
|
||||
```
|
||||
|
||||
The agent first calls:
|
||||
|
||||
```bash
|
||||
reme search query="vite build stalls memory growth TypeScript OOM" limit=5
|
||||
```
|
||||
|
||||
Matches:
|
||||
|
||||
```text
|
||||
digest/procedure/typescript-build-oom.md
|
||||
daily/2026-03-10/build-oom-2026-03-10.md
|
||||
```
|
||||
|
||||
The agent can skip low-value paths in its response:
|
||||
|
||||
```text
|
||||
The previous similar issue was an out-of-memory failure in the TypeScript type-checking process. I suggest checking memory
|
||||
during the build and the type-checking subprocess first, then trying NODE_OPTIONS=--max-old-space-size=8192. Clearing caches
|
||||
and upgrading the minification plugin did not help last time.
|
||||
```
|
||||
|
||||
### Value of this scenario
|
||||
|
||||
- `digest/procedure/` stores both "how to do it" and "which paths failed," letting the agent reuse diagnostic experience.
|
||||
- `digest/personal/` stores user preferences so the agent can follow the same engineering style across sessions.
|
||||
- The source conversation record remains under `session/dialog/`; daily records stay traceable, and digest is only the
|
||||
long-term distilled result.
|
||||
|
||||
## Scenario 3: A Personal Second Brain
|
||||
|
||||
**Persona**: Engineer Li, who talks with an agent about work, books, family plans, running, and travel.
|
||||
|
||||
**Pain point**: Ordinary chat history accumulates chronologically. Three months later, it supports only full-text search and
|
||||
struggles with associative questions such as "What was the book Alice recommended?" or "Why did I change my training plan?"
|
||||
|
||||
### Daily input
|
||||
|
||||
One day produces:
|
||||
|
||||
```text
|
||||
daily/2026-04-20/
|
||||
├── lunch-with-alice.md
|
||||
├── running-plan.md
|
||||
└── frontend-design-review.md
|
||||
```
|
||||
|
||||
`auto_dream` extracts:
|
||||
|
||||
```text
|
||||
digest/
|
||||
├── personal/
|
||||
│ ├── alice.md
|
||||
│ └── exercise-preferences.md
|
||||
├── procedure/
|
||||
│ └── frontend-review-checklist.md
|
||||
└── wiki/
|
||||
└── deep-work.md
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: Alice
|
||||
description: A friend of the user who often recommends reading material
|
||||
---
|
||||
|
||||
# Alice
|
||||
|
||||
## Reading recommendations
|
||||
At lunch on 2026-04-20, Alice recommended [[digest/wiki/deep-work.md]], a book about attention and deep work.
|
||||
|
||||
## Sources
|
||||
|
||||
The recommendation was recorded in [[daily/2026-04-20/lunch-with-alice.md]].
|
||||
```
|
||||
|
||||
### An associative recall
|
||||
|
||||
The user asks:
|
||||
|
||||
```text
|
||||
What was the book about attention that Alice recommended last time?
|
||||
```
|
||||
|
||||
The agent can search first:
|
||||
|
||||
```bash
|
||||
reme search query="Alice recommendation attention book deep work" limit=5
|
||||
```
|
||||
|
||||
Matches:
|
||||
|
||||
```text
|
||||
digest/personal/alice.md
|
||||
outlinks:
|
||||
-> digest/wiki/deep-work.md
|
||||
daily/2026-04-20/lunch-with-alice.md
|
||||
```
|
||||
|
||||
Then read:
|
||||
|
||||
```bash
|
||||
reme read path=digest/wiki/deep-work.md
|
||||
```
|
||||
|
||||
Final response:
|
||||
|
||||
```text
|
||||
It was "Deep Work." The record shows that Alice recommended it at lunch on 2026-04-20, and you later categorized it under
|
||||
attention and working methods.
|
||||
```
|
||||
|
||||
### Value of this scenario
|
||||
|
||||
- daily preserves "what happened at the time."
|
||||
- digest/personal records people, preferences, and long-term relationships.
|
||||
- digest/wiki records books, concepts, and topics.
|
||||
- Wikilinks connect "person -> book -> topic -> original event," which is closer to human recall than browsing chat history
|
||||
only by time.
|
||||
|
|
@ -1,129 +1,117 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="640" viewBox="0 0 1200 640" role="img" aria-labelledby="title desc">
|
||||
<title id="title">ReMe auto dream and proactive flow</title>
|
||||
<desc id="desc">A left-to-right flow from changed daily notes to digest integration, interest topic writing, catalog checkpointing, and proactive reads.</desc>
|
||||
<defs>
|
||||
<style>
|
||||
.bg { fill: #f7f8fb; }
|
||||
.title { font: 700 28px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #172033; }
|
||||
.subtitle { font: 14px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #607086; }
|
||||
.step-num { font: 700 12px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #ffffff; }
|
||||
.step-title { font: 700 18px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #172033; }
|
||||
.step-subtitle { font: 13px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #607086; }
|
||||
.chip-title { font: 700 13px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #253246; }
|
||||
.chip-text { font: 12px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #627087; }
|
||||
.note { font: 12px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #5d6a7d; }
|
||||
.panel { fill: #ffffff; stroke: #d7dde7; stroke-width: 1.2; rx: 10; ry: 10; }
|
||||
.chip { fill: #f9fafc; stroke: #dfe5ee; stroke-width: 1; rx: 8; ry: 8; }
|
||||
.badge { fill: #44546a; }
|
||||
.arrow { stroke: #8794a8; stroke-width: 2; fill: none; marker-end: url(#arrow); }
|
||||
.soft-arrow { stroke: #a3adbd; stroke-width: 1.6; stroke-dasharray: 5 5; fill: none; marker-end: url(#arrow-soft); }
|
||||
.line { stroke: #e1e6ee; stroke-width: 1; }
|
||||
</style>
|
||||
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,6 L8,3 z" fill="#8794a8"/>
|
||||
</marker>
|
||||
<marker id="arrow-soft" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,6 L8,3 z" fill="#a3adbd"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="640" viewBox="0 0 1200 640" role="img"
|
||||
aria-labelledby="title desc">
|
||||
<title id="title">ReMe auto dream and proactive flow</title>
|
||||
<desc id="desc">A left-to-right flow from a recent changed-daily window to digest integration, interest topic
|
||||
writing, catalog checkpointing, and proactive reads.
|
||||
</desc>
|
||||
<defs>
|
||||
<style>.bg { fill: #fffdf8; } .title { font: 700 30px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1f2430; } .subtitle { font: 14px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #556276; } .step-num { font: 700 12px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #ffffff; } .step-title { font: 700 18px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1f2430; } .step-subtitle { font: 13px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #556276; } .chip-title { font: 700 13px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1f2430; } .chip-text { font: 12px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #5e6a7c; } .note { font: 12px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #4f5c6f; } .panel { fill: #ffffff; stroke: #1f2430; stroke-width: 2.2; rx: 18; ry: 18; stroke-linecap: round; stroke-linejoin: round; } .chip { fill: #f8fbff; stroke: #1f2430; stroke-width: 1.6; rx: 11; ry: 11; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 6 5; } .badge { fill: #44546a; } .arrow { stroke: #7f8b9d; stroke-width: 1.45; fill: none; stroke-linecap: round; stroke-linejoin: round; marker-end: url(#arrow); } .soft-arrow { stroke: #a3adbd; stroke-width: 1.25; stroke-dasharray: 6 6; fill: none; stroke-linecap: round; stroke-linejoin: round; marker-end: url(#arrow-soft); } .line { stroke: #a3adbd; stroke-width: 1.25; stroke-linecap: round; }</style>
|
||||
<marker id="arrow" markerWidth="6" markerHeight="6" refX="5" refY="2" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,4 L5,2 z" fill="#7f8b9d"/>
|
||||
</marker>
|
||||
<marker id="arrow-soft" markerWidth="6" markerHeight="6" refX="5" refY="2" orient="auto"
|
||||
markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,4 L5,2 z" fill="#a3adbd"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect class="bg" x="0" y="0" width="1200" height="640"/>
|
||||
<text class="title" x="600" y="54" text-anchor="middle">Auto Dream and Proactive</text>
|
||||
<text class="subtitle" x="600" y="80" text-anchor="middle">Scan changed daily memory, integrate reusable units into digest, then expose proactive topics.</text>
|
||||
<rect class="bg" x="0" y="0" width="1200" height="640"/>
|
||||
<text class="title" x="600" y="54" text-anchor="middle">Auto Dream and Proactive</text>
|
||||
<text class="subtitle" x="600" y="80" text-anchor="middle">Scan a recent daily window, integrate a compact set of reusable units, then expose proactive topics.</text>
|
||||
|
||||
<rect class="panel" x="38" y="132" width="196" height="300"/>
|
||||
<circle class="badge" cx="72" cy="170" r="15"/>
|
||||
<text class="step-num" x="72" y="174" text-anchor="middle">1</text>
|
||||
<text class="step-title" x="100" y="176">Extract</text>
|
||||
<text class="step-subtitle" x="66" y="206">dream_extract_step</text>
|
||||
<rect class="chip" x="66" y="232" width="140" height="44"/>
|
||||
<text class="chip-title" x="136" y="251" text-anchor="middle">refresh index</text>
|
||||
<text class="chip-text" x="136" y="269" text-anchor="middle">daily/<date>.md</text>
|
||||
<rect class="chip" x="66" y="296" width="140" height="44"/>
|
||||
<text class="chip-title" x="136" y="315" text-anchor="middle">compare catalog</text>
|
||||
<text class="chip-text" x="136" y="333" text-anchor="middle">changed daily</text>
|
||||
<rect class="chip" x="66" y="360" width="140" height="44"/>
|
||||
<text class="chip-title" x="136" y="379" text-anchor="middle">LLM extract</text>
|
||||
<text class="chip-text" x="136" y="397" text-anchor="middle">units + topics</text>
|
||||
<rect class="panel" x="38" y="132" width="196" height="300"/>
|
||||
<circle class="badge" cx="72" cy="170" r="15"/>
|
||||
<text class="step-num" x="72" y="174" text-anchor="middle">1</text>
|
||||
<text class="step-title" x="100" y="176">Extract</text>
|
||||
<text class="step-subtitle" x="66" y="206">dream_extract_step</text>
|
||||
<rect class="chip" x="66" y="232" width="140" height="44"/>
|
||||
<text class="chip-title" x="136" y="251" text-anchor="middle">refresh index</text>
|
||||
<text class="chip-text" x="136" y="269" text-anchor="middle">recent 2 days</text>
|
||||
<rect class="chip" x="66" y="296" width="140" height="44"/>
|
||||
<text class="chip-title" x="136" y="315" text-anchor="middle">compare catalog</text>
|
||||
<text class="chip-text" x="136" y="333" text-anchor="middle">changed daily</text>
|
||||
<rect class="chip" x="66" y="360" width="140" height="44"/>
|
||||
<text class="chip-title" x="136" y="379" text-anchor="middle">LLM extract</text>
|
||||
<text class="chip-text" x="136" y="397" text-anchor="middle">≤ 5 units + topics</text>
|
||||
|
||||
<rect class="panel" x="270" y="132" width="196" height="300"/>
|
||||
<circle class="badge" cx="304" cy="170" r="15"/>
|
||||
<text class="step-num" x="304" y="174" text-anchor="middle">2</text>
|
||||
<text class="step-title" x="332" y="176">Integrate</text>
|
||||
<text class="step-subtitle" x="298" y="206">dream_integrate_step</text>
|
||||
<rect class="chip" x="298" y="232" width="140" height="44"/>
|
||||
<text class="chip-title" x="368" y="251" text-anchor="middle">node_search</text>
|
||||
<text class="chip-text" x="368" y="269" text-anchor="middle">recall digest</text>
|
||||
<rect class="chip" x="298" y="296" width="140" height="44"/>
|
||||
<text class="chip-title" x="368" y="315" text-anchor="middle">auto link</text>
|
||||
<text class="chip-text" x="368" y="333" text-anchor="middle">dedup + links</text>
|
||||
<rect class="chip" x="298" y="360" width="140" height="44"/>
|
||||
<text class="chip-title" x="368" y="379" text-anchor="middle">write digest</text>
|
||||
<text class="chip-text" x="368" y="397" text-anchor="middle">create / update</text>
|
||||
<rect class="panel" x="270" y="132" width="196" height="300"/>
|
||||
<circle class="badge" cx="304" cy="170" r="15"/>
|
||||
<text class="step-num" x="304" y="174" text-anchor="middle">2</text>
|
||||
<text class="step-title" x="332" y="176">Integrate</text>
|
||||
<text class="step-subtitle" x="298" y="206">dream_integrate_step</text>
|
||||
<rect class="chip" x="298" y="232" width="140" height="44"/>
|
||||
<text class="chip-title" x="368" y="251" text-anchor="middle">node_search</text>
|
||||
<text class="chip-text" x="368" y="269" text-anchor="middle">recall digest</text>
|
||||
<rect class="chip" x="298" y="296" width="140" height="44"/>
|
||||
<text class="chip-title" x="368" y="315" text-anchor="middle">auto link</text>
|
||||
<text class="chip-text" x="368" y="333" text-anchor="middle">dedup + links</text>
|
||||
<rect class="chip" x="298" y="360" width="140" height="44"/>
|
||||
<text class="chip-title" x="368" y="379" text-anchor="middle">write digest</text>
|
||||
<text class="chip-text" x="368" y="397" text-anchor="middle">create / update</text>
|
||||
|
||||
<rect class="panel" x="502" y="132" width="196" height="300"/>
|
||||
<circle class="badge" cx="536" cy="170" r="15"/>
|
||||
<text class="step-num" x="536" y="174" text-anchor="middle">3</text>
|
||||
<text class="step-title" x="564" y="176">Topics</text>
|
||||
<text class="step-subtitle" x="530" y="206">dream_topics_step</text>
|
||||
<rect class="chip" x="530" y="232" width="140" height="44"/>
|
||||
<text class="chip-title" x="600" y="251" text-anchor="middle">merge topics</text>
|
||||
<text class="chip-text" x="600" y="269" text-anchor="middle">same day kept</text>
|
||||
<rect class="chip" x="530" y="296" width="140" height="44"/>
|
||||
<text class="chip-title" x="600" y="315" text-anchor="middle">avoid repeats</text>
|
||||
<text class="chip-text" x="600" y="333" text-anchor="middle">last 7 days</text>
|
||||
<rect class="chip" x="530" y="360" width="140" height="44"/>
|
||||
<text class="chip-title" x="600" y="379" text-anchor="middle">write YAML</text>
|
||||
<text class="chip-text" x="600" y="397" text-anchor="middle">interests.yaml</text>
|
||||
<rect class="panel" x="502" y="132" width="196" height="300"/>
|
||||
<circle class="badge" cx="536" cy="170" r="15"/>
|
||||
<text class="step-num" x="536" y="174" text-anchor="middle">3</text>
|
||||
<text class="step-title" x="564" y="176">Topics</text>
|
||||
<text class="step-subtitle" x="530" y="206">dream_topics_step</text>
|
||||
<rect class="chip" x="530" y="232" width="140" height="44"/>
|
||||
<text class="chip-title" x="600" y="251" text-anchor="middle">merge topics</text>
|
||||
<text class="chip-text" x="600" y="269" text-anchor="middle">same day kept</text>
|
||||
<rect class="chip" x="530" y="296" width="140" height="44"/>
|
||||
<text class="chip-title" x="600" y="315" text-anchor="middle">avoid repeats</text>
|
||||
<text class="chip-text" x="600" y="333" text-anchor="middle">last 7 days</text>
|
||||
<rect class="chip" x="530" y="360" width="140" height="44"/>
|
||||
<text class="chip-title" x="600" y="379" text-anchor="middle">write YAML</text>
|
||||
<text class="chip-text" x="600" y="397" text-anchor="middle">interests.yaml</text>
|
||||
|
||||
<rect class="panel" x="734" y="132" width="196" height="300"/>
|
||||
<circle class="badge" cx="768" cy="170" r="15"/>
|
||||
<text class="step-num" x="768" y="174" text-anchor="middle">4</text>
|
||||
<text class="step-title" x="796" y="176">Finish</text>
|
||||
<text class="step-subtitle" x="762" y="206">dream_finish_step</text>
|
||||
<rect class="chip" x="762" y="232" width="140" height="44"/>
|
||||
<text class="chip-title" x="832" y="251" text-anchor="middle">checkpoint</text>
|
||||
<text class="chip-text" x="832" y="269" text-anchor="middle">skip failures</text>
|
||||
<rect class="chip" x="762" y="296" width="140" height="44"/>
|
||||
<text class="chip-title" x="832" y="315" text-anchor="middle">persist catalog</text>
|
||||
<text class="chip-text" x="832" y="333" text-anchor="middle">file_catalog</text>
|
||||
<rect class="chip" x="762" y="360" width="140" height="44"/>
|
||||
<text class="chip-title" x="832" y="379" text-anchor="middle">return summary</text>
|
||||
<text class="chip-text" x="832" y="397" text-anchor="middle">counts + errors</text>
|
||||
<rect class="panel" x="734" y="132" width="196" height="300"/>
|
||||
<circle class="badge" cx="768" cy="170" r="15"/>
|
||||
<text class="step-num" x="768" y="174" text-anchor="middle">4</text>
|
||||
<text class="step-title" x="796" y="176">Finish</text>
|
||||
<text class="step-subtitle" x="762" y="206">dream_finish_step</text>
|
||||
<rect class="chip" x="762" y="232" width="140" height="44"/>
|
||||
<text class="chip-title" x="832" y="251" text-anchor="middle">checkpoint</text>
|
||||
<text class="chip-text" x="832" y="269" text-anchor="middle">skip failures</text>
|
||||
<rect class="chip" x="762" y="296" width="140" height="44"/>
|
||||
<text class="chip-title" x="832" y="315" text-anchor="middle">persist catalog</text>
|
||||
<text class="chip-text" x="832" y="333" text-anchor="middle">file_catalog</text>
|
||||
<rect class="chip" x="762" y="360" width="140" height="44"/>
|
||||
<text class="chip-title" x="832" y="379" text-anchor="middle">return summary</text>
|
||||
<text class="chip-text" x="832" y="397" text-anchor="middle">counts + errors</text>
|
||||
|
||||
<rect class="panel" x="966" y="132" width="196" height="300"/>
|
||||
<circle class="badge" cx="1000" cy="170" r="15"/>
|
||||
<text class="step-num" x="1000" y="174" text-anchor="middle">5</text>
|
||||
<text class="step-title" x="1028" y="176">Proactive</text>
|
||||
<text class="step-subtitle" x="994" y="206">proactive_step</text>
|
||||
<rect class="chip" x="994" y="232" width="140" height="44"/>
|
||||
<text class="chip-title" x="1064" y="251" text-anchor="middle">read YAML</text>
|
||||
<text class="chip-text" x="1064" y="269" text-anchor="middle">interests.yaml</text>
|
||||
<rect class="chip" x="994" y="296" width="140" height="44"/>
|
||||
<text class="chip-title" x="1064" y="315" text-anchor="middle">return topics</text>
|
||||
<text class="chip-text" x="1064" y="333" text-anchor="middle">metadata</text>
|
||||
<rect class="chip" x="994" y="360" width="140" height="44"/>
|
||||
<text class="chip-title" x="1064" y="379" text-anchor="middle">agent decides</text>
|
||||
<text class="chip-text" x="1064" y="397" text-anchor="middle">whether to notify</text>
|
||||
<rect class="panel" x="966" y="132" width="196" height="300"/>
|
||||
<circle class="badge" cx="1000" cy="170" r="15"/>
|
||||
<text class="step-num" x="1000" y="174" text-anchor="middle">5</text>
|
||||
<text class="step-title" x="1028" y="176">Proactive</text>
|
||||
<text class="step-subtitle" x="994" y="206">proactive_step</text>
|
||||
<rect class="chip" x="994" y="232" width="140" height="44"/>
|
||||
<text class="chip-title" x="1064" y="251" text-anchor="middle">read YAML</text>
|
||||
<text class="chip-text" x="1064" y="269" text-anchor="middle">interests.yaml</text>
|
||||
<rect class="chip" x="994" y="296" width="140" height="44"/>
|
||||
<text class="chip-title" x="1064" y="315" text-anchor="middle">return topics</text>
|
||||
<text class="chip-text" x="1064" y="333" text-anchor="middle">metadata</text>
|
||||
<rect class="chip" x="994" y="360" width="140" height="44"/>
|
||||
<text class="chip-title" x="1064" y="379" text-anchor="middle">agent decides</text>
|
||||
<text class="chip-text" x="1064" y="397" text-anchor="middle">whether to notify</text>
|
||||
|
||||
<path class="arrow" d="M234 282 H270"/>
|
||||
<path class="arrow" d="M466 282 H502"/>
|
||||
<path class="arrow" d="M698 282 H734"/>
|
||||
<path class="arrow" d="M930 282 H966"/>
|
||||
<path class="arrow" d="M234 282 H270"/>
|
||||
<path class="arrow" d="M466 282 H502"/>
|
||||
<path class="arrow" d="M698 282 H734"/>
|
||||
<path class="arrow" d="M930 282 H966"/>
|
||||
|
||||
<rect class="panel" x="80" y="502" width="1040" height="82"/>
|
||||
<text class="note" x="126" y="532">Inputs</text>
|
||||
<text class="chip-text" x="126" y="554">daily/<date>.md and daily/<date>/**/*.md</text>
|
||||
<line class="line" x1="358" y1="518" x2="358" y2="566"/>
|
||||
<text class="chip-title" x="402" y="532">Long-term memory</text>
|
||||
<text class="chip-text" x="402" y="554">digest/procedure, digest/personal, digest/wiki</text>
|
||||
<line class="line" x1="704" y1="518" x2="704" y2="566"/>
|
||||
<text class="chip-title" x="748" y="532">Proactive material</text>
|
||||
<text class="chip-text" x="748" y="554">daily/<date>/interests.yaml</text>
|
||||
<line class="line" x1="942" y1="518" x2="942" y2="566"/>
|
||||
<text class="chip-title" x="986" y="532">Boundary</text>
|
||||
<text class="chip-text" x="986" y="554">Read only; caller decides.</text>
|
||||
<rect class="panel" x="80" y="502" width="1040" height="82"/>
|
||||
<text class="note" x="126" y="532">Inputs</text>
|
||||
<text class="chip-text" x="126" y="554">recent scan_days; changed files</text>
|
||||
<line class="line" x1="358" y1="518" x2="358" y2="566"/>
|
||||
<text class="chip-title" x="402" y="532">Long-term memory</text>
|
||||
<text class="chip-text" x="402" y="554">digest/{procedure, personal, wiki}</text>
|
||||
<line class="line" x1="704" y1="518" x2="704" y2="566"/>
|
||||
<text class="chip-title" x="748" y="532">Proactive material</text>
|
||||
<text class="chip-text" x="748" y="554">daily/<date>/interests.yaml</text>
|
||||
<line class="line" x1="942" y1="518" x2="942" y2="566"/>
|
||||
<text class="chip-title" x="972" y="532">Boundary</text>
|
||||
<text class="chip-text" x="972" y="554" style="font-size:11px">Read-only; caller decides</text>
|
||||
|
||||
<path class="soft-arrow" d="M1064 432 C1064 476 600 472 600 432"/>
|
||||
<text class="note" x="834" y="474" text-anchor="middle">proactive reads interests.yaml after auto_dream writes it</text>
|
||||
<path class="soft-arrow" d="M1064 432 C1064 476 600 472 600 432"/>
|
||||
<text class="note" x="834" y="474" text-anchor="middle">proactive reads interests.yaml after auto_dream writes it</text>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 9.2 KiB |
|
|
@ -1,98 +1,86 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="640" viewBox="0 0 1200 640" role="img" aria-labelledby="title desc">
|
||||
<title id="title">ReMe auto index and memory search flow</title>
|
||||
<desc id="desc">A concise left-to-right flow from automatic workspace indexing to memory search and progressive link expansion.</desc>
|
||||
<defs>
|
||||
<style>
|
||||
.bg { fill: #f7f8fb; }
|
||||
.title { font: 700 28px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #172033; }
|
||||
.subtitle { font: 14px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #607086; }
|
||||
.step-num { font: 700 12px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #ffffff; }
|
||||
.step-title { font: 700 18px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #172033; }
|
||||
.step-subtitle { font: 13px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #607086; }
|
||||
.chip-title { font: 700 13px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #253246; }
|
||||
.chip-text { font: 12px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #627087; }
|
||||
.note { font: 12px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #5d6a7d; }
|
||||
.panel { fill: #ffffff; stroke: #d7dde7; stroke-width: 1.2; rx: 10; ry: 10; }
|
||||
.chip { fill: #f9fafc; stroke: #dfe5ee; stroke-width: 1; rx: 8; ry: 8; }
|
||||
.badge { fill: #44546a; }
|
||||
.arrow { stroke: #8794a8; stroke-width: 2; fill: none; marker-end: url(#arrow); }
|
||||
.soft-arrow { stroke: #a3adbd; stroke-width: 1.6; stroke-dasharray: 5 5; fill: none; marker-end: url(#arrow-soft); }
|
||||
.line { stroke: #e1e6ee; stroke-width: 1; }
|
||||
</style>
|
||||
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,6 L8,3 z" fill="#8794a8"/>
|
||||
</marker>
|
||||
<marker id="arrow-soft" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,6 L8,3 z" fill="#a3adbd"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="640" viewBox="0 0 1200 640" role="img"
|
||||
aria-labelledby="title desc">
|
||||
<title id="title">ReMe auto index and memory search flow</title>
|
||||
<desc id="desc">A concise left-to-right flow from live daily and digest Markdown indexing to memory search and
|
||||
progressive link expansion.
|
||||
</desc>
|
||||
<defs>
|
||||
<style>.bg { fill: #fffdf8; } .title { font: 700 30px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1f2430; } .subtitle { font: 14px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #556276; } .step-num { font: 700 12px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #ffffff; } .step-title { font: 700 18px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1f2430; } .step-subtitle { font: 13px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #556276; } .chip-title { font: 700 13px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1f2430; } .chip-text { font: 12px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #5e6a7c; } .note { font: 12px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #4f5c6f; } .panel { fill: #ffffff; stroke: #1f2430; stroke-width: 2.2; rx: 18; ry: 18; stroke-linecap: round; stroke-linejoin: round; } .chip { fill: #f8fbff; stroke: #1f2430; stroke-width: 1.6; rx: 11; ry: 11; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 6 5; } .badge { fill: #44546a; } .arrow { stroke: #7f8b9d; stroke-width: 1.45; fill: none; stroke-linecap: round; stroke-linejoin: round; marker-end: url(#arrow); } .soft-arrow { stroke: #a3adbd; stroke-width: 1.25; stroke-dasharray: 6 6; fill: none; stroke-linecap: round; stroke-linejoin: round; marker-end: url(#arrow-soft); } .line { stroke: #a3adbd; stroke-width: 1.25; stroke-linecap: round; }</style>
|
||||
<marker id="arrow" markerWidth="6" markerHeight="6" refX="5" refY="2" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,4 L5,2 z" fill="#7f8b9d"/>
|
||||
</marker>
|
||||
<marker id="arrow-soft" markerWidth="6" markerHeight="6" refX="5" refY="2" orient="auto"
|
||||
markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,4 L5,2 z" fill="#a3adbd"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect class="bg" x="0" y="0" width="1200" height="640"/>
|
||||
<text class="title" x="600" y="54" text-anchor="middle">Auto Index and Memory Search</text>
|
||||
<text class="subtitle" x="600" y="80" text-anchor="middle">Keep workspace indexes fresh, recall relevant chunks, then expand nearby wikilink context.</text>
|
||||
<rect class="bg" x="0" y="0" width="1200" height="640"/>
|
||||
<text class="title" x="600" y="54" text-anchor="middle">Auto Index and Memory Search</text>
|
||||
<text class="subtitle" x="600" y="80" text-anchor="middle">Keep workspace indexes fresh, recall relevant chunks, then expand nearby wikilink context.</text>
|
||||
|
||||
<rect class="panel" x="64" y="150" width="232" height="260"/>
|
||||
<circle class="badge" cx="100" cy="188" r="15"/>
|
||||
<text class="step-num" x="100" y="192" text-anchor="middle">1</text>
|
||||
<text class="step-title" x="128" y="194">Watch memory</text>
|
||||
<text class="step-subtitle" x="92" y="224">index_update_loop</text>
|
||||
<rect class="chip" x="92" y="250" width="176" height="44"/>
|
||||
<text class="chip-title" x="180" y="269" text-anchor="middle">daily / digest / resource</text>
|
||||
<text class="chip-text" x="180" y="287" text-anchor="middle">md and jsonl files</text>
|
||||
<rect class="chip" x="92" y="314" width="176" height="44"/>
|
||||
<text class="chip-title" x="180" y="333" text-anchor="middle">init + watch changes</text>
|
||||
<text class="chip-text" x="180" y="351" text-anchor="middle">added / modified / deleted</text>
|
||||
<rect class="panel" x="64" y="150" width="232" height="260"/>
|
||||
<circle class="badge" cx="100" cy="188" r="15"/>
|
||||
<text class="step-num" x="100" y="192" text-anchor="middle">1</text>
|
||||
<text class="step-title" x="128" y="194">Watch memory</text>
|
||||
<text class="step-subtitle" x="92" y="224">index_update_loop</text>
|
||||
<rect class="chip" x="92" y="250" width="176" height="44"/>
|
||||
<text class="chip-title" x="180" y="269" text-anchor="middle">daily / digest</text>
|
||||
<text class="chip-text" x="180" y="287" text-anchor="middle">Markdown live watch</text>
|
||||
<rect class="chip" x="92" y="314" width="176" height="44"/>
|
||||
<text class="chip-title" x="180" y="333" text-anchor="middle">init + watch changes</text>
|
||||
<text class="chip-text" x="180" y="351" text-anchor="middle">added / modified / deleted</text>
|
||||
|
||||
<rect class="panel" x="348" y="150" width="232" height="260"/>
|
||||
<circle class="badge" cx="384" cy="188" r="15"/>
|
||||
<text class="step-num" x="384" y="192" text-anchor="middle">2</text>
|
||||
<text class="step-title" x="412" y="194">Build index</text>
|
||||
<text class="step-subtitle" x="376" y="224">update_index_step</text>
|
||||
<rect class="chip" x="376" y="250" width="176" height="44"/>
|
||||
<text class="chip-title" x="464" y="269" text-anchor="middle">chunk file</text>
|
||||
<text class="chip-text" x="464" y="287" text-anchor="middle">FileNode + FileChunk[]</text>
|
||||
<rect class="chip" x="376" y="314" width="176" height="44"/>
|
||||
<text class="chip-title" x="464" y="333" text-anchor="middle">store structures</text>
|
||||
<text class="chip-text" x="464" y="351" text-anchor="middle">BM25 + graph + chunks</text>
|
||||
<rect class="panel" x="348" y="150" width="232" height="260"/>
|
||||
<circle class="badge" cx="384" cy="188" r="15"/>
|
||||
<text class="step-num" x="384" y="192" text-anchor="middle">2</text>
|
||||
<text class="step-title" x="412" y="194">Build index</text>
|
||||
<text class="step-subtitle" x="376" y="224">update_index_step</text>
|
||||
<rect class="chip" x="376" y="250" width="176" height="44"/>
|
||||
<text class="chip-title" x="464" y="269" text-anchor="middle">chunk file</text>
|
||||
<text class="chip-text" x="464" y="287" text-anchor="middle">FileNode + FileChunk[]</text>
|
||||
<rect class="chip" x="376" y="314" width="176" height="44"/>
|
||||
<text class="chip-title" x="464" y="333" text-anchor="middle">store structures</text>
|
||||
<text class="chip-text" x="464" y="351" text-anchor="middle">BM25 + graph + chunks</text>
|
||||
|
||||
<rect class="panel" x="632" y="150" width="232" height="260"/>
|
||||
<circle class="badge" cx="668" cy="188" r="15"/>
|
||||
<text class="step-num" x="668" y="192" text-anchor="middle">3</text>
|
||||
<text class="step-title" x="696" y="194">Recall chunks</text>
|
||||
<text class="step-subtitle" x="660" y="224">search_step</text>
|
||||
<rect class="chip" x="660" y="250" width="176" height="44"/>
|
||||
<text class="chip-title" x="748" y="269" text-anchor="middle">BM25 search</text>
|
||||
<text class="chip-text" x="748" y="287" text-anchor="middle">keyword-ranked chunks</text>
|
||||
<rect class="chip" x="660" y="314" width="176" height="44"/>
|
||||
<text class="chip-title" x="748" y="333" text-anchor="middle">optional vector search</text>
|
||||
<text class="chip-text" x="748" y="351" text-anchor="middle">RRF fusion when enabled</text>
|
||||
<rect class="panel" x="632" y="150" width="232" height="260"/>
|
||||
<circle class="badge" cx="668" cy="188" r="15"/>
|
||||
<text class="step-num" x="668" y="192" text-anchor="middle">3</text>
|
||||
<text class="step-title" x="696" y="194">Recall chunks</text>
|
||||
<text class="step-subtitle" x="660" y="224">search_step</text>
|
||||
<rect class="chip" x="660" y="250" width="176" height="44"/>
|
||||
<text class="chip-title" x="748" y="269" text-anchor="middle">BM25 search</text>
|
||||
<text class="chip-text" x="748" y="287" text-anchor="middle">keyword-ranked chunks</text>
|
||||
<rect class="chip" x="660" y="314" width="176" height="44"/>
|
||||
<text class="chip-title" x="748" y="333" text-anchor="middle">optional vector search</text>
|
||||
<text class="chip-text" x="748" y="351" text-anchor="middle">RRF fusion when enabled</text>
|
||||
|
||||
<rect class="panel" x="916" y="150" width="220" height="260"/>
|
||||
<circle class="badge" cx="952" cy="188" r="15"/>
|
||||
<text class="step-num" x="952" y="192" text-anchor="middle">4</text>
|
||||
<text class="step-title" x="980" y="194">Expand context</text>
|
||||
<text class="step-subtitle" x="944" y="224">expand_links</text>
|
||||
<rect class="chip" x="944" y="250" width="164" height="44"/>
|
||||
<text class="chip-title" x="1026" y="269" text-anchor="middle">top chunks</text>
|
||||
<text class="chip-text" x="1026" y="287" text-anchor="middle">path + line range</text>
|
||||
<rect class="chip" x="944" y="314" width="164" height="44"/>
|
||||
<text class="chip-title" x="1026" y="333" text-anchor="middle">outlinks + inlinks</text>
|
||||
<text class="chip-text" x="1026" y="351" text-anchor="middle">name, description, via</text>
|
||||
<rect class="panel" x="916" y="150" width="220" height="260"/>
|
||||
<circle class="badge" cx="952" cy="188" r="15"/>
|
||||
<text class="step-num" x="952" y="192" text-anchor="middle">4</text>
|
||||
<text class="step-title" x="980" y="194">Expand context</text>
|
||||
<text class="step-subtitle" x="944" y="224">expand_links</text>
|
||||
<rect class="chip" x="944" y="250" width="164" height="44"/>
|
||||
<text class="chip-title" x="1026" y="269" text-anchor="middle">top chunks</text>
|
||||
<text class="chip-text" x="1026" y="287" text-anchor="middle">path + line range</text>
|
||||
<rect class="chip" x="944" y="314" width="164" height="44"/>
|
||||
<text class="chip-title" x="1026" y="333" text-anchor="middle">outlinks + inlinks</text>
|
||||
<text class="chip-text" x="1026" y="351" text-anchor="middle">name, description, via</text>
|
||||
|
||||
<path class="arrow" d="M296 280 H348"/>
|
||||
<path class="arrow" d="M580 280 H632"/>
|
||||
<path class="arrow" d="M864 280 H916"/>
|
||||
<path class="arrow" d="M296 280 H348"/>
|
||||
<path class="arrow" d="M580 280 H632"/>
|
||||
<path class="arrow" d="M864 280 H916"/>
|
||||
|
||||
<rect class="panel" x="142" y="484" width="916" height="76"/>
|
||||
<text class="note" x="190" y="514">Default path</text>
|
||||
<text class="chip-text" x="190" y="536">BM25 first, vector optional.</text>
|
||||
<line class="line" x1="410" y1="500" x2="410" y2="544"/>
|
||||
<text class="chip-title" x="454" y="514">BM25 is enabled by default</text>
|
||||
<text class="chip-text" x="454" y="536">embedding_store is empty unless configured.</text>
|
||||
<line class="line" x1="720" y1="500" x2="720" y2="544"/>
|
||||
<text class="chip-title" x="764" y="514">Results stay compact first</text>
|
||||
<text class="chip-text" x="764" y="536">Use read or traverse for deeper expansion.</text>
|
||||
<rect class="panel" x="142" y="484" width="916" height="76"/>
|
||||
<text class="note" x="190" y="514">Rebuild scope</text>
|
||||
<text class="chip-text" x="190" y="536">reindex adds resource + JSONL.</text>
|
||||
<line class="line" x1="410" y1="500" x2="410" y2="544"/>
|
||||
<text class="chip-title" x="454" y="514">BM25 is enabled by default</text>
|
||||
<text class="chip-text" x="454" y="536">Recall cap: 200; embeddings opt-in.</text>
|
||||
<line class="line" x1="720" y1="500" x2="720" y2="544"/>
|
||||
<text class="chip-title" x="764" y="514">Results stay compact first</text>
|
||||
<text class="chip-text" x="764" y="536">Use read or traverse for deeper expansion.</text>
|
||||
|
||||
<path class="soft-arrow" d="M1026 410 C1026 456 748 452 748 410"/>
|
||||
<text class="note" x="888" y="454" text-anchor="middle">link expansion is contextual, not a full-workspace dump</text>
|
||||
<path class="soft-arrow" d="M1026 410 C1026 456 748 452 748 410"/>
|
||||
<text class="note" x="888" y="454" text-anchor="middle">link expansion is contextual, not a full-workspace dump</text>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 7 KiB |
|
|
@ -1,91 +1,79 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="640" viewBox="0 0 1200 640" role="img" aria-labelledby="title desc">
|
||||
<title id="title">ReMe auto memory and auto resource flow</title>
|
||||
<desc id="desc">A concise flow where auto memory and auto resource both create daily cards, which are indexed in the daily memory layer and then used by dream and search.</desc>
|
||||
<defs>
|
||||
<style>
|
||||
.bg { fill: #f7f8fb; }
|
||||
.title { font: 700 28px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #172033; }
|
||||
.subtitle { font: 14px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #607086; }
|
||||
.step-num { font: 700 12px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #ffffff; }
|
||||
.step-title { font: 700 18px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #172033; }
|
||||
.step-subtitle { font: 13px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #607086; }
|
||||
.chip-title { font: 700 13px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #253246; }
|
||||
.chip-text { font: 12px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #627087; }
|
||||
.note { font: 12px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #5d6a7d; }
|
||||
.panel { fill: #ffffff; stroke: #d7dde7; stroke-width: 1.2; rx: 10; ry: 10; }
|
||||
.chip { fill: #f9fafc; stroke: #dfe5ee; stroke-width: 1; rx: 8; ry: 8; }
|
||||
.badge { fill: #44546a; }
|
||||
.arrow { stroke: #8794a8; stroke-width: 2; fill: none; marker-end: url(#arrow); }
|
||||
.soft-arrow { stroke: #a3adbd; stroke-width: 1.6; stroke-dasharray: 5 5; fill: none; marker-end: url(#arrow-soft); }
|
||||
.line { stroke: #e1e6ee; stroke-width: 1; }
|
||||
</style>
|
||||
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,6 L8,3 z" fill="#8794a8"/>
|
||||
</marker>
|
||||
<marker id="arrow-soft" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,6 L8,3 z" fill="#a3adbd"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="640" viewBox="0 0 1200 640" role="img"
|
||||
aria-labelledby="title desc">
|
||||
<title id="title">ReMe auto memory and auto resource flow</title>
|
||||
<desc id="desc">A concise flow where auto memory and auto resource both create daily cards, which are indexed in the
|
||||
daily memory layer and then used by dream and search.
|
||||
</desc>
|
||||
<defs>
|
||||
<style>.bg { fill: #fffdf8; } .title { font: 700 30px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1f2430; } .subtitle { font: 14px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #556276; } .step-num { font: 700 12px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #ffffff; } .step-title { font: 700 18px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1f2430; } .step-subtitle { font: 13px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #556276; } .chip-title { font: 700 13px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1f2430; } .chip-text { font: 12px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #5e6a7c; } .note { font: 12px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #4f5c6f; } .panel { fill: #ffffff; stroke: #1f2430; stroke-width: 2.2; rx: 18; ry: 18; stroke-linecap: round; stroke-linejoin: round; } .chip { fill: #f8fbff; stroke: #1f2430; stroke-width: 1.6; rx: 11; ry: 11; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 6 5; } .badge { fill: #44546a; } .arrow { stroke: #7f8b9d; stroke-width: 1.45; fill: none; stroke-linecap: round; stroke-linejoin: round; marker-end: url(#arrow); } .soft-arrow { stroke: #a3adbd; stroke-width: 1.25; stroke-dasharray: 6 6; fill: none; stroke-linecap: round; stroke-linejoin: round; marker-end: url(#arrow-soft); } .line { stroke: #a3adbd; stroke-width: 1.25; stroke-linecap: round; }</style>
|
||||
<marker id="arrow" markerWidth="6" markerHeight="6" refX="5" refY="2" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,4 L5,2 z" fill="#7f8b9d"/>
|
||||
</marker>
|
||||
<marker id="arrow-soft" markerWidth="6" markerHeight="6" refX="5" refY="2" orient="auto"
|
||||
markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,4 L5,2 z" fill="#a3adbd"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect class="bg" x="0" y="0" width="1200" height="640"/>
|
||||
<text class="title" x="600" y="54" text-anchor="middle">Auto Memory & Auto Resource</text>
|
||||
<text class="subtitle" x="600" y="80" text-anchor="middle">Two ingestion paths create daily cards, then share one daily index and downstream memory flow.</text>
|
||||
<rect class="bg" x="0" y="0" width="1200" height="640"/>
|
||||
<text class="title" x="600" y="54" text-anchor="middle">Auto Memory & Auto Resource</text>
|
||||
<text class="subtitle" x="600" y="80" text-anchor="middle">Two ingestion paths create daily cards, then share one daily index and downstream memory flow.</text>
|
||||
|
||||
<rect class="panel" x="84" y="132" width="300" height="300"/>
|
||||
<circle class="badge" cx="118" cy="170" r="15"/>
|
||||
<text class="step-num" x="118" y="174" text-anchor="middle">1</text>
|
||||
<text class="step-title" x="146" y="176">Inputs</text>
|
||||
<text class="step-subtitle" x="112" y="206">memory + resource</text>
|
||||
<rect class="chip" x="112" y="234" width="244" height="54"/>
|
||||
<text class="chip-title" x="234" y="255" text-anchor="middle">Auto Memory</text>
|
||||
<text class="chip-text" x="234" y="273" text-anchor="middle">conversation -> session daily card</text>
|
||||
<rect class="chip" x="112" y="312" width="244" height="54"/>
|
||||
<text class="chip-title" x="234" y="333" text-anchor="middle">Auto Resource</text>
|
||||
<text class="chip-text" x="234" y="351" text-anchor="middle">dated file -> resource daily card</text>
|
||||
<rect class="chip" x="112" y="388" width="244" height="28"/>
|
||||
<text class="chip-text" x="234" y="407" text-anchor="middle">original sources remain verifiable</text>
|
||||
<rect class="panel" x="84" y="132" width="300" height="300"/>
|
||||
<circle class="badge" cx="118" cy="170" r="15"/>
|
||||
<text class="step-num" x="118" y="174" text-anchor="middle">1</text>
|
||||
<text class="step-title" x="146" y="176">Inputs</text>
|
||||
<text class="step-subtitle" x="112" y="206">memory + resource</text>
|
||||
<rect class="chip" x="112" y="234" width="244" height="54"/>
|
||||
<text class="chip-title" x="234" y="255" text-anchor="middle">Auto Memory</text>
|
||||
<text class="chip-text" x="234" y="273" text-anchor="middle">conversation -> topic-named card</text>
|
||||
<rect class="chip" x="112" y="312" width="244" height="54"/>
|
||||
<text class="chip-title" x="234" y="333" text-anchor="middle">Auto Resource</text>
|
||||
<text class="chip-text" x="234" y="351" text-anchor="middle">source file -> topic-named card</text>
|
||||
<rect class="chip" x="112" y="388" width="244" height="28"/>
|
||||
<text class="chip-text" x="234" y="407" text-anchor="middle">original sources remain verifiable</text>
|
||||
|
||||
<rect class="panel" x="450" y="132" width="300" height="300"/>
|
||||
<circle class="badge" cx="484" cy="170" r="15"/>
|
||||
<text class="step-num" x="484" y="174" text-anchor="middle">2</text>
|
||||
<text class="step-title" x="512" y="176">Daily Memory Layer</text>
|
||||
<text class="step-subtitle" x="478" y="206">daily/<date>/</text>
|
||||
<rect class="chip" x="478" y="234" width="244" height="54"/>
|
||||
<text class="chip-title" x="600" y="255" text-anchor="middle">daily cards</text>
|
||||
<text class="chip-text" x="600" y="273" text-anchor="middle">facts, context, actions</text>
|
||||
<rect class="chip" x="478" y="312" width="244" height="54"/>
|
||||
<text class="chip-title" x="600" y="333" text-anchor="middle">day index</text>
|
||||
<text class="chip-text" x="600" y="351" text-anchor="middle">daily/<date>.md links cards</text>
|
||||
<rect class="chip" x="478" y="388" width="244" height="28"/>
|
||||
<text class="chip-text" x="600" y="407" text-anchor="middle">one readable daily memory stream</text>
|
||||
<rect class="panel" x="450" y="132" width="300" height="300"/>
|
||||
<circle class="badge" cx="484" cy="170" r="15"/>
|
||||
<text class="step-num" x="484" y="174" text-anchor="middle">2</text>
|
||||
<text class="step-title" x="512" y="176">Daily Memory Layer</text>
|
||||
<text class="step-subtitle" x="478" y="206">daily/<date>/</text>
|
||||
<rect class="chip" x="478" y="234" width="244" height="54"/>
|
||||
<text class="chip-title" x="600" y="255" text-anchor="middle">daily cards</text>
|
||||
<text class="chip-text" x="600" y="273" text-anchor="middle">facts, context, actions</text>
|
||||
<rect class="chip" x="478" y="312" width="244" height="54"/>
|
||||
<text class="chip-title" x="600" y="333" text-anchor="middle">day index</text>
|
||||
<text class="chip-text" x="600" y="351" text-anchor="middle">daily/<date>.md links cards</text>
|
||||
<rect class="chip" x="478" y="388" width="244" height="28"/>
|
||||
<text class="chip-text" x="600" y="407" text-anchor="middle">one readable daily memory stream</text>
|
||||
|
||||
<rect class="panel" x="816" y="132" width="300" height="300"/>
|
||||
<circle class="badge" cx="850" cy="170" r="15"/>
|
||||
<text class="step-num" x="850" y="174" text-anchor="middle">3</text>
|
||||
<text class="step-title" x="878" y="176">Downstream</text>
|
||||
<text class="step-subtitle" x="844" y="206">dream + search</text>
|
||||
<rect class="chip" x="844" y="234" width="244" height="54"/>
|
||||
<text class="chip-title" x="966" y="255" text-anchor="middle">Auto Dream</text>
|
||||
<text class="chip-text" x="966" y="273" text-anchor="middle">daily material -> digest memory</text>
|
||||
<rect class="chip" x="844" y="312" width="244" height="54"/>
|
||||
<text class="chip-title" x="966" y="333" text-anchor="middle">Memory Search</text>
|
||||
<text class="chip-text" x="966" y="351" text-anchor="middle">retrieve daily and digest nodes</text>
|
||||
<rect class="chip" x="844" y="388" width="244" height="28"/>
|
||||
<text class="chip-text" x="966" y="407" text-anchor="middle">daily is the shared ingestion layer</text>
|
||||
<rect class="panel" x="816" y="132" width="300" height="300"/>
|
||||
<circle class="badge" cx="850" cy="170" r="15"/>
|
||||
<text class="step-num" x="850" y="174" text-anchor="middle">3</text>
|
||||
<text class="step-title" x="878" y="176">Downstream</text>
|
||||
<text class="step-subtitle" x="844" y="206">dream + search</text>
|
||||
<rect class="chip" x="844" y="234" width="244" height="54"/>
|
||||
<text class="chip-title" x="966" y="255" text-anchor="middle">Auto Dream</text>
|
||||
<text class="chip-text" x="966" y="273" text-anchor="middle">daily material -> digest memory</text>
|
||||
<rect class="chip" x="844" y="312" width="244" height="54"/>
|
||||
<text class="chip-title" x="966" y="333" text-anchor="middle">Memory Search</text>
|
||||
<text class="chip-text" x="966" y="351" text-anchor="middle">retrieve daily and digest nodes</text>
|
||||
<rect class="chip" x="844" y="388" width="244" height="28"/>
|
||||
<text class="chip-text" x="966" y="407" text-anchor="middle">daily is the shared ingestion layer</text>
|
||||
|
||||
<path class="arrow" d="M384 282 H450"/>
|
||||
<path class="arrow" d="M750 282 H816"/>
|
||||
<path class="arrow" d="M384 282 H450"/>
|
||||
<path class="arrow" d="M750 282 H816"/>
|
||||
|
||||
<rect class="panel" x="126" y="502" width="948" height="82"/>
|
||||
<text class="note" x="176" y="532">Conversation source</text>
|
||||
<text class="chip-text" x="176" y="554">session/dialog/<session_id>.jsonl</text>
|
||||
<line class="line" x1="454" y1="518" x2="454" y2="566"/>
|
||||
<text class="chip-title" x="498" y="532">Resource source</text>
|
||||
<text class="chip-text" x="498" y="554">resource/<date>/<resource_file></text>
|
||||
<line class="line" x1="796" y1="518" x2="796" y2="566"/>
|
||||
<text class="chip-title" x="838" y="532">Daily output</text>
|
||||
<text class="chip-text" x="838" y="554">daily cards plus daily/<date>.md</text>
|
||||
<rect class="panel" x="126" y="502" width="948" height="82"/>
|
||||
<text class="note" x="176" y="532">Conversation source</text>
|
||||
<text class="chip-text" x="176" y="554">session/dialog/<session_id>.jsonl</text>
|
||||
<line class="line" x1="454" y1="518" x2="454" y2="566"/>
|
||||
<text class="chip-title" x="498" y="532">Resource source</text>
|
||||
<text class="chip-text" x="498" y="554">resource/[<date>/]<resource_file></text>
|
||||
<line class="line" x1="796" y1="518" x2="796" y2="566"/>
|
||||
<text class="chip-title" x="838" y="532">Daily output</text>
|
||||
<text class="chip-text" x="838" y="554">daily/<date>/<name>.md</text>
|
||||
|
||||
<path class="soft-arrow" d="M966 432 C966 474 600 474 600 432"/>
|
||||
<text class="note" x="782" y="478" text-anchor="middle">downstream flows read the same daily memory layer</text>
|
||||
<path class="soft-arrow" d="M966 432 C966 474 600 474 600 432"/>
|
||||
<text class="note" x="782" y="478" text-anchor="middle">downstream flows read the same daily memory layer</text>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.8 KiB |
BIN
docs/figure/cc-auto-dream.gif
Normal file
|
After Width: | Height: | Size: 502 KiB |
BIN
docs/figure/cc-auto-memory.gif
Normal file
|
After Width: | Height: | Size: 590 KiB |
|
|
@ -1,97 +1,83 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="520" viewBox="0 0 1200 520" role="img" aria-labelledby="title desc">
|
||||
<title id="title">ReMe design philosophy</title>
|
||||
<desc id="desc">A simple diagram showing ReMe's design philosophy: file-native memory, progressive abstraction, and explicit relationship graph for agents.</desc>
|
||||
<defs>
|
||||
<style>
|
||||
.bg { fill: #f7f8fb; }
|
||||
.title { font: 700 30px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #172033; }
|
||||
.subtitle { font: 14px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #607086; }
|
||||
.panel { fill: #ffffff; stroke: #d8dee8; stroke-width: 1.2; }
|
||||
.panel-blue { fill: #eef7ff; stroke: #bfd8f2; stroke-width: 1.2; }
|
||||
.panel-green { fill: #effaf4; stroke: #bfdcc9; stroke-width: 1.2; }
|
||||
.panel-yellow { fill: #fff7e5; stroke: #ead59a; stroke-width: 1.2; }
|
||||
.label { font: 700 18px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #172033; }
|
||||
.small { font: 13px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #607086; }
|
||||
.tiny { font: 12px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #66758a; }
|
||||
.chip { fill: #ffffff; stroke: #dfe5ee; stroke-width: 1; }
|
||||
.chip-text { font: 700 13px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #334155; }
|
||||
.arrow { stroke: #8996aa; stroke-width: 2.2; fill: none; marker-end: url(#arrow); }
|
||||
.soft-arrow { stroke: #a8b2c2; stroke-width: 1.8; fill: none; stroke-dasharray: 6 6; marker-end: url(#arrow-soft); }
|
||||
.node { fill: #ffffff; stroke: #64748b; stroke-width: 1.7; }
|
||||
.link { stroke: #93a0b2; stroke-width: 1.5; fill: none; }
|
||||
.file-line { stroke: #c9d3df; stroke-width: 1.4; }
|
||||
</style>
|
||||
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,6 L8,3 z" fill="#8996aa"/>
|
||||
</marker>
|
||||
<marker id="arrow-soft" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,6 L8,3 z" fill="#a8b2c2"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="520" viewBox="0 0 1200 520" role="img"
|
||||
aria-labelledby="title desc">
|
||||
<title id="title">ReMe design philosophy</title>
|
||||
<desc id="desc">A simple diagram showing ReMe's design philosophy: file-native memory, progressive abstraction, and
|
||||
explicit relationship graph for agents.
|
||||
</desc>
|
||||
<defs>
|
||||
<style>.bg { fill: #ffffff; } .title { font: 700 30px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1f2430; } .subtitle { font: 14px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #556276; } .panel { fill: #ffffff; stroke: #1f2430; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; } .panel-blue { fill: #eef7ff; stroke: #1f2430; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; } .panel-green { fill: #f0fbf5; stroke: #1f2430; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; } .panel-yellow { fill: #fff7e5; stroke: #1f2430; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; } .label { font: 700 18px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1f2430; } .small { font: 13px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #556276; } .tiny { font: 12px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #5e6a7c; } .chip { fill: #ffffff; stroke: #1f2430; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 6 5; } .chip-text { font: 700 13px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1f2430; } .arrow { stroke: #7f8b9d; stroke-width: 1.45; fill: none; stroke-linecap: round; stroke-linejoin: round; marker-end: url(#arrow); } .soft-arrow { stroke: #a3adbd; stroke-width: 1.25; fill: none; stroke-dasharray: 6 6; stroke-linecap: round; stroke-linejoin: round; marker-end: url(#arrow-soft); } .node { fill: #ffffff; stroke: #1f2430; stroke-width: 1.6; } .link { stroke: #a3adbd; stroke-width: 1.45; fill: none; stroke-linecap: round; stroke-linejoin: round; } .file-line { stroke: #7f8b9d; stroke-width: 1.45; stroke-linecap: round; }</style>
|
||||
<marker id="arrow" markerWidth="6" markerHeight="6" refX="5" refY="2" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,4 L5,2 z" fill="#7f8b9d"/>
|
||||
</marker>
|
||||
<marker id="arrow-soft" markerWidth="6" markerHeight="6" refX="5" refY="2" orient="auto"
|
||||
markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,4 L5,2 z" fill="#a3adbd"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect class="bg" x="0" y="0" width="1200" height="520"/>
|
||||
<text class="title" x="600" y="54" text-anchor="middle">ReMe Design Philosophy</text>
|
||||
<text class="subtitle" x="600" y="80" text-anchor="middle">File-native memory, progressive abstraction, and explicit links for reusable agent context.</text>
|
||||
<rect class="bg" x="0" y="0" width="1200" height="520"/>
|
||||
<text class="title" x="600" y="54" text-anchor="middle">ReMe Design Philosophy</text>
|
||||
<text class="subtitle" x="600" y="80" text-anchor="middle">File-native memory, progressive abstraction, and explicit links for reusable agent context.</text>
|
||||
|
||||
<rect class="panel-blue" x="64" y="134" width="304" height="220" rx="14"/>
|
||||
<text class="label" x="216" y="170" text-anchor="middle">Foundation: Memory as File</text>
|
||||
<text class="small" x="216" y="196" text-anchor="middle">Readable, editable knowledge assets</text>
|
||||
<g transform="translate(126 224)">
|
||||
<rect class="panel" x="0" y="0" width="120" height="82" rx="8"/>
|
||||
<path class="file-line" d="M22 26 H98"/>
|
||||
<path class="file-line" d="M22 43 H86"/>
|
||||
<path class="file-line" d="M22 60 H72"/>
|
||||
<rect class="chip" x="136" y="6" width="96" height="28" rx="8"/>
|
||||
<text class="chip-text" x="184" y="25" text-anchor="middle">Markdown</text>
|
||||
<rect class="chip" x="136" y="48" width="96" height="28" rx="8"/>
|
||||
<text class="chip-text" x="184" y="67" text-anchor="middle">YAML/JSONL</text>
|
||||
</g>
|
||||
<text class="tiny" x="216" y="332" text-anchor="middle">Human-readable. Agent-operable.</text>
|
||||
<rect class="panel-blue" x="64" y="134" width="304" height="220" rx="14"/>
|
||||
<text class="label" x="216" y="170" text-anchor="middle">Foundation: Memory as File</text>
|
||||
<text class="small" x="216" y="196" text-anchor="middle">Readable, editable knowledge assets</text>
|
||||
<g transform="translate(126 224)">
|
||||
<rect class="panel" x="0" y="0" width="120" height="82" rx="8"/>
|
||||
<path class="file-line" d="M22 26 H98"/>
|
||||
<path class="file-line" d="M22 43 H86"/>
|
||||
<path class="file-line" d="M22 60 H72"/>
|
||||
<rect class="chip" x="136" y="6" width="96" height="28" rx="8"/>
|
||||
<text class="chip-text" x="184" y="25" text-anchor="middle">Markdown</text>
|
||||
<rect class="chip" x="136" y="48" width="96" height="28" rx="8"/>
|
||||
<text class="chip-text" x="184" y="67" text-anchor="middle">YAML/JSONL</text>
|
||||
</g>
|
||||
<text class="tiny" x="216" y="332" text-anchor="middle">Human-readable. Agent-operable.</text>
|
||||
|
||||
<rect class="panel-yellow" x="448" y="134" width="304" height="220" rx="14"/>
|
||||
<text class="label" x="600" y="170" text-anchor="middle">Core: Progressive Abstraction</text>
|
||||
<text class="small" x="600" y="196" text-anchor="middle">From raw material to durable memory</text>
|
||||
<g transform="translate(472 235)">
|
||||
<rect class="chip" x="0" y="0" width="66" height="42" rx="10"/>
|
||||
<text class="chip-text" x="33" y="26" text-anchor="middle">Raw</text>
|
||||
<rect class="chip" x="95" y="0" width="66" height="42" rx="10"/>
|
||||
<text class="chip-text" x="128" y="26" text-anchor="middle">Daily</text>
|
||||
<rect class="chip" x="190" y="0" width="66" height="42" rx="10"/>
|
||||
<text class="chip-text" x="223" y="26" text-anchor="middle">Digest</text>
|
||||
<path class="arrow" d="M68 21 H89"/>
|
||||
<path class="arrow" d="M163 21 H184"/>
|
||||
</g>
|
||||
<text class="tiny" x="600" y="310" text-anchor="middle">session/ + resource/ -> daily/ -> digest/</text>
|
||||
<text class="tiny" x="600" y="332" text-anchor="middle">Continuous consolidation, not one-shot summary.</text>
|
||||
<rect class="panel-yellow" x="448" y="134" width="304" height="220" rx="14"/>
|
||||
<text class="label" x="600" y="170" text-anchor="middle">Core: Progressive Abstraction</text>
|
||||
<text class="small" x="600" y="196" text-anchor="middle">From raw material to durable memory</text>
|
||||
<g transform="translate(472 235)">
|
||||
<rect class="chip" x="0" y="0" width="66" height="42" rx="10"/>
|
||||
<text class="chip-text" x="33" y="26" text-anchor="middle">Raw</text>
|
||||
<rect class="chip" x="95" y="0" width="66" height="42" rx="10"/>
|
||||
<text class="chip-text" x="128" y="26" text-anchor="middle">Daily</text>
|
||||
<rect class="chip" x="190" y="0" width="66" height="42" rx="10"/>
|
||||
<text class="chip-text" x="223" y="26" text-anchor="middle">Digest</text>
|
||||
<path class="arrow" d="M68 21 H89"/>
|
||||
<path class="arrow" d="M163 21 H184"/>
|
||||
</g>
|
||||
<text class="tiny" x="600" y="310" text-anchor="middle">session/resource -> daily -> digest</text>
|
||||
<text class="tiny" x="600" y="332" text-anchor="middle">Continuous consolidation; not one-shot.</text>
|
||||
|
||||
<rect class="panel-green" x="832" y="134" width="304" height="220" rx="14"/>
|
||||
<text class="label" x="984" y="170" text-anchor="middle">Innovation: Link Graph</text>
|
||||
<text class="small" x="984" y="196" text-anchor="middle">Explicit links expand context</text>
|
||||
<g transform="translate(930 207) scale(0.74)">
|
||||
<path class="link" d="M74 40 L26 90"/>
|
||||
<path class="link" d="M74 40 L130 84"/>
|
||||
<path class="link" d="M26 90 L130 84"/>
|
||||
<path class="link" d="M74 40 L152 26"/>
|
||||
<circle class="node" cx="74" cy="40" r="24"/>
|
||||
<circle class="node" cx="26" cy="90" r="20"/>
|
||||
<circle class="node" cx="130" cy="84" r="20"/>
|
||||
<circle class="node" cx="152" cy="26" r="18"/>
|
||||
<text class="chip-text" x="74" y="38" text-anchor="middle">[[ ]]</text>
|
||||
<text class="tiny" x="74" y="55" text-anchor="middle">Node</text>
|
||||
<text class="tiny" x="26" y="95" text-anchor="middle">Src</text>
|
||||
<text class="tiny" x="130" y="89" text-anchor="middle">Risk</text>
|
||||
<text class="tiny" x="152" y="31" text-anchor="middle">Dep</text>
|
||||
</g>
|
||||
<text class="tiny" x="984" y="318" text-anchor="middle">Beyond similarity: source, cause, dependency, risk.</text>
|
||||
<text class="tiny" x="984" y="338" text-anchor="middle">Wikilinks guide expansion across nodes.</text>
|
||||
<rect class="panel-green" x="832" y="134" width="304" height="220" rx="14"/>
|
||||
<text class="label" x="984" y="170" text-anchor="middle">Innovation: Link Graph</text>
|
||||
<text class="small" x="984" y="196" text-anchor="middle">Explicit links expand context</text>
|
||||
<g transform="translate(930 207) scale(0.74)">
|
||||
<path class="link" d="M74 40 L26 90"/>
|
||||
<path class="link" d="M74 40 L130 84"/>
|
||||
<path class="link" d="M26 90 L130 84"/>
|
||||
<path class="link" d="M74 40 L152 26"/>
|
||||
<circle class="node" cx="74" cy="40" r="24"/>
|
||||
<circle class="node" cx="26" cy="90" r="20"/>
|
||||
<circle class="node" cx="130" cy="84" r="20"/>
|
||||
<circle class="node" cx="152" cy="26" r="18"/>
|
||||
<text class="chip-text" x="74" y="38" text-anchor="middle">[[ ]]</text>
|
||||
<text class="tiny" x="74" y="55" text-anchor="middle">Node</text>
|
||||
<text class="tiny" x="26" y="95" text-anchor="middle">Src</text>
|
||||
<text class="tiny" x="130" y="89" text-anchor="middle">Risk</text>
|
||||
<text class="tiny" x="152" y="31" text-anchor="middle">Dep</text>
|
||||
</g>
|
||||
<text class="tiny" x="984" y="318" text-anchor="middle">Source · cause · dependency · risk</text>
|
||||
<text class="tiny" x="984" y="338" text-anchor="middle">Wikilinks guide expansion across nodes.</text>
|
||||
|
||||
<path class="arrow" d="M368 244 H448"/>
|
||||
<path class="arrow" d="M752 244 H832"/>
|
||||
<path class="arrow" d="M368 244 H448"/>
|
||||
<path class="arrow" d="M752 244 H832"/>
|
||||
|
||||
<rect class="panel" x="220" y="414" width="760" height="56" rx="14"/>
|
||||
<text class="small" x="600" y="438" text-anchor="middle">One shared file tree acts as both memory store and operating surface.</text>
|
||||
<text class="chip-text" x="600" y="460" text-anchor="middle">capture -> refine -> link -> retrieve -> expand</text>
|
||||
<path class="soft-arrow" d="M216 354 C216 392 428 392 454 414"/>
|
||||
<path class="soft-arrow" d="M600 354 V414"/>
|
||||
<path class="soft-arrow" d="M984 354 C984 392 772 392 746 414"/>
|
||||
<rect class="panel" x="220" y="414" width="760" height="56" rx="14"/>
|
||||
<text class="small" x="600" y="438" text-anchor="middle">One shared file tree acts as both memory store and operating surface.</text>
|
||||
<text class="chip-text" x="600" y="460" text-anchor="middle">capture -> refine -> link -> retrieve -> expand</text>
|
||||
<path class="soft-arrow" d="M216 354 C216 392 428 392 454 414"/>
|
||||
<path class="soft-arrow" d="M600 354 V414"/>
|
||||
<path class="soft-arrow" d="M984 354 C984 392 772 392 746 414"/>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.9 KiB |
|
|
@ -1,158 +1,149 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="920" viewBox="0 0 1200 920" role="img" aria-labelledby="title desc">
|
||||
<title id="title">ReMe framework structure</title>
|
||||
<desc id="desc">An architectural map of ReMe from file-backed workspace storage through knowledge kernel, workflows, application wiring, and external service surfaces.</desc>
|
||||
<defs>
|
||||
<linearGradient id="paper" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#fbfaf6"/>
|
||||
<stop offset="0.55" stop-color="#f5f7fb"/>
|
||||
<stop offset="1" stop-color="#eef7f4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="serviceGrad" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0" stop-color="#d9f3ec"/>
|
||||
<stop offset="1" stop-color="#dcecf7"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="appGrad" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0" stop-color="#e5e8fb"/>
|
||||
<stop offset="1" stop-color="#f1e4f5"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="jobGrad" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0" stop-color="#fae4d8"/>
|
||||
<stop offset="1" stop-color="#f4eccd"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="kernelGrad" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0" stop-color="#d9f1f4"/>
|
||||
<stop offset="1" stop-color="#e1e8f7"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="workspaceGrad" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0" stop-color="#f0e5d3"/>
|
||||
<stop offset="1" stop-color="#e3efd9"/>
|
||||
</linearGradient>
|
||||
<filter id="softShadow" x="-8%" y="-18%" width="116%" height="145%">
|
||||
<feDropShadow dx="0" dy="16" stdDeviation="16" flood-color="#1c2b3a" flood-opacity="0.13"/>
|
||||
</filter>
|
||||
<filter id="nodeShadow" x="-15%" y="-30%" width="130%" height="170%">
|
||||
<feDropShadow dx="0" dy="8" stdDeviation="8" flood-color="#1c2b3a" flood-opacity="0.12"/>
|
||||
</filter>
|
||||
<style>
|
||||
.bg { fill: url(#paper); }
|
||||
.grain { fill: none; stroke: #d9ded8; stroke-width: 1; opacity: 0.38; }
|
||||
text { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif; }
|
||||
.title { font-size: 30px; font-weight: 800; fill: #172033; }
|
||||
.subtitle { font-size: 14px; fill: #667489; }
|
||||
.layer-title { font-size: 18px; font-weight: 800; fill: #263548; }
|
||||
.layer-note { font-size: 13px; fill: rgba(38,53,72,0.72); }
|
||||
.section-label { font-size: 11px; font-weight: 800; fill: #647187; letter-spacing: 1.8px; }
|
||||
.node-title { font-size: 14px; font-weight: 700; fill: #1d2838; }
|
||||
.node-text { font-size: 12px; fill: #667489; }
|
||||
.micro { font-size: 11px; fill: #728096; }
|
||||
.plate { filter: url(#softShadow); }
|
||||
.plate-bg { fill: #ffffff; stroke: rgba(52, 65, 84, 0.12); stroke-width: 1; }
|
||||
.side { opacity: 0.18; }
|
||||
.node { fill: #ffffff; stroke: rgba(90, 105, 128, 0.22); stroke-width: 1; filter: url(#nodeShadow); }
|
||||
</style>
|
||||
</defs>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="920" viewBox="0 0 1200 920" role="img"
|
||||
aria-labelledby="title desc">
|
||||
<title id="title">ReMe framework structure</title>
|
||||
<desc id="desc">An architectural map of ReMe from file-backed workspace storage through knowledge kernel, workflows,
|
||||
application wiring, and external service surfaces.
|
||||
</desc>
|
||||
<defs>
|
||||
<linearGradient id="paper" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#fbfaf6"/>
|
||||
<stop offset="0.55" stop-color="#f5f7fb"/>
|
||||
<stop offset="1" stop-color="#eef7f4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="serviceGrad" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0" stop-color="#d9f3ec"/>
|
||||
<stop offset="1" stop-color="#dcecf7"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="appGrad" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0" stop-color="#e5e8fb"/>
|
||||
<stop offset="1" stop-color="#f1e4f5"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="jobGrad" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0" stop-color="#fae4d8"/>
|
||||
<stop offset="1" stop-color="#f4eccd"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="kernelGrad" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0" stop-color="#d9f1f4"/>
|
||||
<stop offset="1" stop-color="#e1e8f7"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="workspaceGrad" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0" stop-color="#f0e5d3"/>
|
||||
<stop offset="1" stop-color="#e3efd9"/>
|
||||
</linearGradient>
|
||||
<filter id="softShadow" x="-8%" y="-18%" width="116%" height="145%">
|
||||
<feDropShadow dx="0" dy="16" stdDeviation="16" flood-color="#1c2b3a" flood-opacity="0.13"/>
|
||||
</filter>
|
||||
<filter id="nodeShadow" x="-15%" y="-30%" width="130%" height="170%">
|
||||
<feDropShadow dx="0" dy="8" stdDeviation="8" flood-color="#1c2b3a" flood-opacity="0.12"/>
|
||||
</filter>
|
||||
<style>.bg { fill: url(#paper); } .grain { fill: none; stroke: #d9ded8; stroke-width: 1; opacity: 0.38; } text { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif; } .title { font-size: 30px; font-weight: 800; fill: #172033; } .subtitle { font-size: 14px; fill: #667489; } .layer-title { font-size: 18px; font-weight: 800; fill: #263548; } .layer-note { font-size: 13px; fill: rgba(38,53,72,0.72); } .section-label { font-size: 11px; font-weight: 800; fill: #647187; letter-spacing: 1.8px; } .node-title { font-size: 14px; font-weight: 700; fill: #1d2838; } .node-text { font-size: 12px; fill: #667489; } .micro { font-size: 11px; fill: #728096; } .plate { filter: url(#softShadow); } .plate-bg { fill: #ffffff; stroke: rgba(52, 65, 84, 0.12); stroke-width: 1; } .side { opacity: 0.18; } .node { fill: #ffffff; stroke: rgba(90, 105, 128, 0.22); stroke-width: 1; filter: url(#nodeShadow); }</style>
|
||||
</defs>
|
||||
|
||||
<rect class="bg" width="1200" height="920"/>
|
||||
<path class="grain" d="M98 138 C246 104 337 155 475 121 C616 86 752 124 890 96 C1019 70 1082 98 1136 126"/>
|
||||
<path class="grain" d="M64 768 C218 720 374 790 514 742 C660 692 812 740 966 706 C1066 684 1118 708 1146 732"/>
|
||||
<rect class="bg" width="1200" height="920"/>
|
||||
<path class="grain" d="M98 138 C246 104 337 155 475 121 C616 86 752 124 890 96 C1019 70 1082 98 1136 126"/>
|
||||
<path class="grain" d="M64 768 C218 720 374 790 514 742 C660 692 812 740 966 706 C1066 684 1118 708 1146 732"/>
|
||||
|
||||
<text class="title" x="600" y="56" text-anchor="middle">ReMe Framework Structure</text>
|
||||
<text class="subtitle" x="600" y="83" text-anchor="middle">File-backed memory, searchable knowledge kernel, composable jobs, app wiring, and public service surfaces.</text>
|
||||
<text class="title" x="600" y="56" text-anchor="middle">ReMe Framework Structure</text>
|
||||
<text class="subtitle" x="600" y="83" text-anchor="middle">File-backed memory, searchable knowledge kernel, composable jobs, app wiring, and public service surfaces.</text>
|
||||
|
||||
<g class="plate" transform="translate(0,0)">
|
||||
<path class="side" d="M126 196 L1074 196 L1042 226 L158 226 Z" fill="#83cbbb"/>
|
||||
<rect class="plate-bg" x="126" y="128" width="948" height="96" rx="18"/>
|
||||
<rect x="126" y="128" width="280" height="96" rx="18" fill="url(#serviceGrad)"/>
|
||||
<text class="layer-title" x="178" y="166">Service</text>
|
||||
<text class="layer-note" x="178" y="188">Public interfaces</text>
|
||||
<g class="plate" transform="translate(0,0)">
|
||||
<path class="side" d="M126 196 L1074 196 L1042 226 L158 226 Z" fill="#83cbbb"/>
|
||||
<rect class="plate-bg" x="126" y="128" width="948" height="96" rx="18"/>
|
||||
<rect x="126" y="128" width="280" height="96" rx="18" fill="url(#serviceGrad)"/>
|
||||
<text class="layer-title" x="178" y="166">Service</text>
|
||||
<text class="layer-note" x="178" y="188">Public interfaces</text>
|
||||
|
||||
<rect class="node" x="436" y="151" width="138" height="50" rx="13"/>
|
||||
<text class="node-title" x="505" y="172" text-anchor="middle">HTTP API</text>
|
||||
<text class="node-text" x="505" y="190" text-anchor="middle">server routes</text>
|
||||
<rect class="node" x="626" y="151" width="138" height="50" rx="13"/>
|
||||
<text class="node-title" x="695" y="172" text-anchor="middle">MCP Tools</text>
|
||||
<text class="node-text" x="695" y="190" text-anchor="middle">agent actions</text>
|
||||
<rect class="node" x="816" y="151" width="138" height="50" rx="13"/>
|
||||
<text class="node-title" x="885" y="172" text-anchor="middle">CLI Client</text>
|
||||
<text class="node-text" x="885" y="190" text-anchor="middle">local access</text>
|
||||
</g>
|
||||
<rect class="node" x="436" y="151" width="138" height="50" rx="13"/>
|
||||
<text class="node-title" x="505" y="172" text-anchor="middle">HTTP API</text>
|
||||
<text class="node-text" x="505" y="190" text-anchor="middle">API + Studio</text>
|
||||
<rect class="node" x="626" y="151" width="138" height="50" rx="13"/>
|
||||
<text class="node-title" x="695" y="172" text-anchor="middle">MCP Tools</text>
|
||||
<text class="node-text" x="695" y="190" text-anchor="middle">agent actions</text>
|
||||
<rect class="node" x="816" y="151" width="138" height="50" rx="13"/>
|
||||
<text class="node-title" x="885" y="172" text-anchor="middle">CLI Client</text>
|
||||
<text class="node-text" x="885" y="190" text-anchor="middle">service calls</text>
|
||||
</g>
|
||||
|
||||
<g class="plate" transform="translate(0,0)">
|
||||
<path class="side" d="M94 336 L1106 336 L1074 366 L126 366 Z" fill="#aab5ec"/>
|
||||
<rect class="plate-bg" x="94" y="264" width="1012" height="100" rx="18"/>
|
||||
<rect x="94" y="264" width="280" height="100" rx="18" fill="url(#appGrad)"/>
|
||||
<text class="layer-title" x="146" y="303">Application</text>
|
||||
<text class="layer-note" x="146" y="325">Config, wiring, lifecycle</text>
|
||||
<g class="plate" transform="translate(0,0)">
|
||||
<path class="side" d="M94 336 L1106 336 L1074 366 L126 366 Z" fill="#aab5ec"/>
|
||||
<rect class="plate-bg" x="94" y="264" width="1012" height="100" rx="18"/>
|
||||
<rect x="94" y="264" width="280" height="100" rx="18" fill="url(#appGrad)"/>
|
||||
<text class="layer-title" x="146" y="303">Application</text>
|
||||
<text class="layer-note" x="146" y="325">Config, wiring, lifecycle</text>
|
||||
|
||||
<rect class="node" x="424" y="290" width="122" height="48" rx="12"/>
|
||||
<text class="node-title" x="485" y="319" text-anchor="middle">Context</text>
|
||||
<rect class="node" x="574" y="290" width="122" height="48" rx="12"/>
|
||||
<text class="node-title" x="635" y="319" text-anchor="middle">Wiring</text>
|
||||
<rect class="node" x="724" y="290" width="122" height="48" rx="12"/>
|
||||
<text class="node-title" x="785" y="319" text-anchor="middle">Lifecycle</text>
|
||||
<rect class="node" x="874" y="290" width="122" height="48" rx="12"/>
|
||||
<text class="node-title" x="935" y="319" text-anchor="middle">Job APIs</text>
|
||||
</g>
|
||||
<rect class="node" x="424" y="290" width="122" height="48" rx="12"/>
|
||||
<text class="node-title" x="485" y="319" text-anchor="middle">Context</text>
|
||||
<rect class="node" x="574" y="290" width="122" height="48" rx="12"/>
|
||||
<text class="node-title" x="635" y="319" text-anchor="middle">Wiring</text>
|
||||
<rect class="node" x="724" y="290" width="122" height="48" rx="12"/>
|
||||
<text class="node-title" x="785" y="319" text-anchor="middle">Lifecycle</text>
|
||||
<rect class="node" x="874" y="290" width="122" height="48" rx="12"/>
|
||||
<text class="node-title" x="935" y="319" text-anchor="middle">Job APIs</text>
|
||||
</g>
|
||||
|
||||
<g class="plate" transform="translate(0,0)">
|
||||
<path class="side" d="M126 500 L1074 500 L1042 530 L158 530 Z" fill="#e9ad8f"/>
|
||||
<rect class="plate-bg" x="126" y="404" width="948" height="124" rx="18"/>
|
||||
<rect x="126" y="404" width="280" height="124" rx="18" fill="url(#jobGrad)"/>
|
||||
<text class="layer-title" x="178" y="448">Steps / Jobs</text>
|
||||
<text class="layer-note" x="178" y="470">Composable workflows</text>
|
||||
<g class="plate" transform="translate(0,0)">
|
||||
<path class="side" d="M126 500 L1074 500 L1042 530 L158 530 Z" fill="#e9ad8f"/>
|
||||
<rect class="plate-bg" x="126" y="404" width="948" height="124" rx="18"/>
|
||||
<rect x="126" y="404" width="280" height="124" rx="18" fill="url(#jobGrad)"/>
|
||||
<text class="layer-title" x="178" y="448">Steps / Jobs</text>
|
||||
<text class="layer-note" x="178" y="470">Composable workflows</text>
|
||||
|
||||
<rect class="node" x="424" y="428" width="232" height="76" rx="14"/>
|
||||
<text class="node-title" x="448" y="456">Jobs</text>
|
||||
<text class="node-text" x="448" y="479">base · stream · background · cron</text>
|
||||
<rect class="node" x="704" y="428" width="300" height="76" rx="14"/>
|
||||
<text class="node-title" x="728" y="456">Step Modules</text>
|
||||
<text class="node-text" x="728" y="479">file_io · index · evolve · transfer · channel · common</text>
|
||||
</g>
|
||||
<rect class="node" x="424" y="428" width="232" height="76" rx="14"/>
|
||||
<text class="node-title" x="448" y="456">Jobs</text>
|
||||
<text class="node-text" x="448" y="479">base · stream · background · cron</text>
|
||||
<rect class="node" x="704" y="428" width="300" height="76" rx="14"/>
|
||||
<text class="node-title" x="728" y="456">Step Modules</text>
|
||||
<text class="node-text" x="728" y="479">file_io · index · evolve · common</text>
|
||||
<text class="node-text" x="728" y="497">transfer · cookbook · benchmark</text>
|
||||
</g>
|
||||
|
||||
<g class="plate" transform="translate(0,0)">
|
||||
<path class="side" d="M94 674 L1106 674 L1074 704 L126 704 Z" fill="#8fcbd5"/>
|
||||
<rect class="plate-bg" x="94" y="568" width="1012" height="134" rx="18"/>
|
||||
<rect x="94" y="568" width="280" height="134" rx="18" fill="url(#kernelGrad)"/>
|
||||
<text class="layer-title" x="146" y="615">Knowledge Kernel</text>
|
||||
<text class="layer-note" x="146" y="637">Index, watch, schema</text>
|
||||
<g class="plate" transform="translate(0,0)">
|
||||
<path class="side" d="M94 674 L1106 674 L1074 704 L126 704 Z" fill="#8fcbd5"/>
|
||||
<rect class="plate-bg" x="94" y="568" width="1012" height="134" rx="18"/>
|
||||
<rect x="94" y="568" width="280" height="134" rx="18" fill="url(#kernelGrad)"/>
|
||||
<text class="layer-title" x="146" y="615">Knowledge Kernel</text>
|
||||
<text class="layer-note" x="146" y="637">Index, watch, schema</text>
|
||||
|
||||
<rect class="node" x="424" y="594" width="228" height="82" rx="14"/>
|
||||
<text class="node-title" x="448" y="623">Index Stores</text>
|
||||
<text class="node-text" x="448" y="646">file_store · keyword_index</text>
|
||||
<text class="node-text" x="448" y="664">embedding_store · file_graph</text>
|
||||
<rect class="node" x="698" y="594" width="172" height="82" rx="14"/>
|
||||
<text class="node-title" x="722" y="623">File Watcher</text>
|
||||
<text class="node-text" x="722" y="646">scanner · chunker</text>
|
||||
<text class="node-text" x="722" y="664">catalog</text>
|
||||
<rect class="node" x="916" y="594" width="142" height="82" rx="14"/>
|
||||
<text class="node-title" x="940" y="623">Memory</text>
|
||||
<text class="node-text" x="940" y="646">FileNode</text>
|
||||
<text class="node-text" x="940" y="664">FileChunk · FileLink</text>
|
||||
</g>
|
||||
<rect class="node" x="424" y="594" width="228" height="82" rx="14"/>
|
||||
<text class="node-title" x="448" y="623">Index Stores</text>
|
||||
<text class="node-text" x="448" y="646">file_store · keyword_index</text>
|
||||
<text class="node-text" x="448" y="664">embedding_store · file_graph</text>
|
||||
<rect class="node" x="698" y="594" width="172" height="82" rx="14"/>
|
||||
<text class="node-title" x="722" y="623">File Watcher</text>
|
||||
<text class="node-text" x="722" y="646">scanner · chunker</text>
|
||||
<text class="node-text" x="722" y="664">catalog</text>
|
||||
<rect class="node" x="916" y="594" width="142" height="82" rx="14"/>
|
||||
<text class="node-title" x="940" y="623">Memory</text>
|
||||
<text class="node-text" x="940" y="646">FileNode</text>
|
||||
<text class="node-text" x="940" y="664">FileChunk · FileLink</text>
|
||||
</g>
|
||||
|
||||
<g class="plate" transform="translate(0,0)">
|
||||
<path class="side" d="M126 818 L1074 818 L1042 848 L158 848 Z" fill="#d0b98f"/>
|
||||
<rect class="plate-bg" x="126" y="738" width="948" height="108" rx="18"/>
|
||||
<rect x="126" y="738" width="280" height="108" rx="18" fill="url(#workspaceGrad)"/>
|
||||
<text class="layer-title" x="178" y="777">Workspace Layout</text>
|
||||
<text class="layer-note" x="178" y="799">File-backed memory</text>
|
||||
<g class="plate" transform="translate(0,0)">
|
||||
<path class="side" d="M126 818 L1074 818 L1042 848 L158 848 Z" fill="#d0b98f"/>
|
||||
<rect class="plate-bg" x="126" y="738" width="948" height="108" rx="18"/>
|
||||
<rect x="126" y="738" width="280" height="108" rx="18" fill="url(#workspaceGrad)"/>
|
||||
<text class="layer-title" x="178" y="777">Workspace Layout</text>
|
||||
<text class="layer-note" x="178" y="799">File-backed memory</text>
|
||||
|
||||
<rect class="node" x="424" y="767" width="126" height="50" rx="13"/>
|
||||
<text class="node-title" x="487" y="788" text-anchor="middle">daily/</text>
|
||||
<text class="node-text" x="487" y="806" text-anchor="middle">working notes</text>
|
||||
<rect class="node" x="584" y="767" width="126" height="50" rx="13"/>
|
||||
<text class="node-title" x="647" y="788" text-anchor="middle">digest/</text>
|
||||
<text class="node-text" x="647" y="806" text-anchor="middle">long-term</text>
|
||||
<rect class="node" x="744" y="767" width="126" height="50" rx="13"/>
|
||||
<text class="node-title" x="807" y="788" text-anchor="middle">resource/</text>
|
||||
<text class="node-text" x="807" y="806" text-anchor="middle">resources</text>
|
||||
<rect class="node" x="904" y="767" width="126" height="50" rx="13"/>
|
||||
<text class="node-title" x="967" y="788" text-anchor="middle">metadata/</text>
|
||||
<text class="node-text" x="967" y="806" text-anchor="middle">state</text>
|
||||
</g>
|
||||
<rect class="node" x="400" y="767" width="160" height="50" rx="13"/>
|
||||
<text class="node-title" x="480" y="788" text-anchor="middle">session directories</text>
|
||||
<text class="node-text" x="480" y="806" text-anchor="middle">sources · agent runtime</text>
|
||||
<rect class="node" x="572" y="767" width="100" height="50" rx="13"/>
|
||||
<text class="node-title" x="622" y="788" text-anchor="middle">daily/</text>
|
||||
<text class="node-text" x="622" y="806" text-anchor="middle">working</text>
|
||||
<rect class="node" x="684" y="767" width="100" height="50" rx="13"/>
|
||||
<text class="node-title" x="734" y="788" text-anchor="middle">digest/</text>
|
||||
<text class="node-text" x="734" y="806" text-anchor="middle">long-term</text>
|
||||
<rect class="node" x="796" y="767" width="100" height="50" rx="13"/>
|
||||
<text class="node-title" x="846" y="788" text-anchor="middle">resource/</text>
|
||||
<text class="node-text" x="846" y="806" text-anchor="middle">sources</text>
|
||||
<rect class="node" x="908" y="767" width="122" height="50" rx="13"/>
|
||||
<text class="node-title" x="969" y="788" text-anchor="middle">metadata/</text>
|
||||
<text class="node-text" x="969" y="806" text-anchor="middle">derived state</text>
|
||||
</g>
|
||||
|
||||
<text class="section-label" x="92" y="118">EXTERNAL SURFACES</text>
|
||||
<text class="section-label" x="92" y="734">PERSISTENT MEMORY BASE</text>
|
||||
<text class="micro" x="600" y="878" text-anchor="middle">Structure reads bottom-up during boot and top-down during use.</text>
|
||||
<text class="micro" x="600" y="896" text-anchor="middle">Workspace files feed the kernel; workflows compose operations; application wiring exposes stable service entry points.</text>
|
||||
<text class="section-label" x="92" y="118">EXTERNAL SURFACES</text>
|
||||
<text class="section-label" x="92" y="734">PERSISTENT MEMORY BASE</text>
|
||||
<text class="micro" x="600" y="878" text-anchor="middle">Structure reads bottom-up during boot and top-down during use.</text>
|
||||
<text class="micro" x="600" y="896" text-anchor="middle">Workspace files feed the kernel; workflows compose operations; application wiring exposes stable service entry points.</text>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 10 KiB |
|
|
@ -1,110 +1,97 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="640" viewBox="0 0 1200 640" role="img" aria-labelledby="title desc">
|
||||
<title id="title">ReMe memory as file model</title>
|
||||
<desc id="desc">A diagram showing ReMe workspace files as both a human readable memory interface and an agent operable memory graph, flowing from raw input to daily notes, digest nodes, and metadata indexes.</desc>
|
||||
<defs>
|
||||
<style>
|
||||
.bg { fill: #f7f8fb; }
|
||||
.title { font: 700 28px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #172033; }
|
||||
.subtitle { font: 14px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #607086; }
|
||||
.section-title { font: 700 18px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #172033; }
|
||||
.section-subtitle { font: 13px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #607086; }
|
||||
.chip-title { font: 700 13px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #253246; }
|
||||
.chip-text { font: 12px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #627087; }
|
||||
.note { font: 12px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #5d6a7d; }
|
||||
.label { font: 700 12px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #ffffff; }
|
||||
.panel { fill: #ffffff; stroke: #d7dde7; stroke-width: 1.2; rx: 10; ry: 10; }
|
||||
.chip { fill: #f9fafc; stroke: #dfe5ee; stroke-width: 1; rx: 8; ry: 8; }
|
||||
.badge { fill: #44546a; }
|
||||
.soft { fill: #eef2f7; stroke: #d7dde7; stroke-width: 1; rx: 8; ry: 8; }
|
||||
.arrow { stroke: #8794a8; stroke-width: 2; fill: none; marker-end: url(#arrow); }
|
||||
.soft-arrow { stroke: #a3adbd; stroke-width: 1.6; stroke-dasharray: 5 5; fill: none; marker-end: url(#arrow-soft); }
|
||||
.line { stroke: #e1e6ee; stroke-width: 1; }
|
||||
</style>
|
||||
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,6 L8,3 z" fill="#8794a8"/>
|
||||
</marker>
|
||||
<marker id="arrow-soft" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,6 L8,3 z" fill="#a3adbd"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="670" viewBox="0 0 1200 670" role="img"
|
||||
aria-labelledby="title desc">
|
||||
<title id="title">ReMe memory as file model</title>
|
||||
<desc id="desc">A diagram showing ReMe workspace files as both a human readable memory interface and an agent
|
||||
operable memory graph, flowing from raw input to daily notes, digest nodes, and metadata indexes.
|
||||
</desc>
|
||||
<defs>
|
||||
<style>.bg { fill: #fffdf8; } .title { font: 700 30px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1f2430; } .subtitle { font: 14px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #556276; } .section-title { font: 700 18px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif; fill: #1f2430; } .section-subtitle { font: 13px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif; fill: #556276; } .chip-title { font: 700 13px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1f2430; } .chip-text { font: 12px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #5e6a7c; } .note { font: 12px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #4f5c6f; } .label { font: 700 12px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #ffffff; } .panel { fill: #ffffff; stroke: #1f2430; stroke-width: 2.2; rx: 18; ry: 18; stroke-linecap: round; stroke-linejoin: round; } .chip { fill: #f8fbff; stroke: #1f2430; stroke-width: 1.6; rx: 11; ry: 11; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 6 5; } .badge { fill: #44546a; } .soft { fill: #eef7ff; stroke: #1f2430; stroke-width: 1.6; rx: 11; ry: 11; stroke-linecap: round; stroke-linejoin: round; } .arrow { stroke: #7f8b9d; stroke-width: 1.45; fill: none; stroke-linecap: round; stroke-linejoin: round; marker-end: url(#arrow); } .soft-arrow { stroke: #a3adbd; stroke-width: 1.25; stroke-dasharray: 6 6; fill: none; stroke-linecap: round; stroke-linejoin: round; marker-end: url(#arrow-soft); } .line { stroke: #a3adbd; stroke-width: 1.25; stroke-linecap: round; }</style>
|
||||
<marker id="arrow" markerWidth="6" markerHeight="6" refX="5" refY="2" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,4 L5,2 z" fill="#7f8b9d"/>
|
||||
</marker>
|
||||
<marker id="arrow-soft" markerWidth="6" markerHeight="6" refX="5" refY="2" orient="auto"
|
||||
markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,4 L5,2 z" fill="#a3adbd"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect class="bg" x="0" y="0" width="1200" height="640"/>
|
||||
<text class="title" x="600" y="54" text-anchor="middle">Memory as File</text>
|
||||
<text class="subtitle" x="600" y="80" text-anchor="middle">Workspace files are the readable memory surface and the operable graph/index substrate.</text>
|
||||
<rect class="bg" x="0" y="0" width="1200" height="670"/>
|
||||
<text class="title" x="600" y="54" text-anchor="middle">Memory as File</text>
|
||||
<text class="subtitle" x="600" y="80" text-anchor="middle">Workspace files are the readable memory surface and the operable graph/index substrate.</text>
|
||||
|
||||
<rect class="panel" x="72" y="124" width="280" height="168"/>
|
||||
<rect class="badge" x="102" y="150" width="94" height="24" rx="12" ry="12"/>
|
||||
<text class="label" x="149" y="167" text-anchor="middle">Human</text>
|
||||
<text class="section-title" x="102" y="202">Read and edit files</text>
|
||||
<text class="section-subtitle" x="102" y="226">Markdown, YAML, JSONL, resources</text>
|
||||
<rect class="chip" x="102" y="248" width="220" height="28"/>
|
||||
<text class="chip-text" x="212" y="267" text-anchor="middle">open, revise, move, delete</text>
|
||||
<rect class="panel" x="72" y="124" width="280" height="168"/>
|
||||
<rect class="badge" x="102" y="150" width="94" height="24" rx="12" ry="12"/>
|
||||
<text class="label" x="149" y="167" text-anchor="middle">Human</text>
|
||||
<text class="section-title" x="102" y="202">Read and edit files</text>
|
||||
<text class="section-subtitle" x="102" y="226" style="font-size:11px">Markdown · YAML · JSONL · resources</text>
|
||||
<rect class="chip" x="102" y="248" width="220" height="28"/>
|
||||
<text class="chip-text" x="212" y="267" text-anchor="middle">open, revise, move, delete</text>
|
||||
|
||||
<rect class="panel" x="460" y="120" width="280" height="176"/>
|
||||
<text class="section-title" x="600" y="152" text-anchor="middle">Workspace directory</text>
|
||||
<text class="section-subtitle" x="600" y="176" text-anchor="middle">the shared memory interface</text>
|
||||
<rect class="soft" x="504" y="204" width="192" height="30"/>
|
||||
<text class="chip-title" x="600" y="224" text-anchor="middle">Memory as File</text>
|
||||
<rect class="soft" x="504" y="248" width="192" height="30"/>
|
||||
<text class="chip-title" x="600" y="268" text-anchor="middle">File as Memory</text>
|
||||
<rect class="panel" x="460" y="120" width="280" height="176"/>
|
||||
<text class="section-title" x="600" y="152" text-anchor="middle">Workspace directory</text>
|
||||
<text class="section-subtitle" x="600" y="176" text-anchor="middle">the shared memory interface</text>
|
||||
<rect class="soft" x="504" y="204" width="192" height="30"/>
|
||||
<text class="chip-title" x="600" y="224" text-anchor="middle">Memory as File</text>
|
||||
<rect class="soft" x="504" y="248" width="192" height="30"/>
|
||||
<text class="chip-title" x="600" y="268" text-anchor="middle">File as Memory</text>
|
||||
|
||||
<rect class="panel" x="848" y="124" width="280" height="168"/>
|
||||
<rect class="badge" x="878" y="150" width="94" height="24" rx="12" ry="12"/>
|
||||
<text class="label" x="925" y="167" text-anchor="middle">Agent</text>
|
||||
<text class="section-title" x="878" y="202">Parse and operate graph</text>
|
||||
<text class="section-subtitle" x="878" y="226">frontmatter, chunks, wikilinks</text>
|
||||
<rect class="chip" x="878" y="248" width="220" height="28"/>
|
||||
<text class="chip-text" x="988" y="267" text-anchor="middle">search, link, rewrite, index</text>
|
||||
<rect class="panel" x="848" y="124" width="280" height="168"/>
|
||||
<rect class="badge" x="878" y="150" width="94" height="24" rx="12" ry="12"/>
|
||||
<text class="label" x="925" y="167" text-anchor="middle">Agent</text>
|
||||
<text class="section-title" x="878" y="202">Parse and operate graph</text>
|
||||
<text class="section-subtitle" x="878" y="226">frontmatter, chunks, wikilinks</text>
|
||||
<rect class="chip" x="878" y="248" width="220" height="28"/>
|
||||
<text class="chip-text" x="988" y="267" text-anchor="middle">search, link, rewrite, index</text>
|
||||
|
||||
<path class="arrow" d="M352 208 H460"/>
|
||||
<path class="arrow" d="M740 208 H848"/>
|
||||
<path class="soft-arrow" d="M848 250 C740 326 460 326 352 250"/>
|
||||
<text class="note" x="600" y="336" text-anchor="middle">people and agents see the same file tree, so edits and evidence links stay inspectable</text>
|
||||
<path class="arrow" d="M352 208 H460"/>
|
||||
<path class="arrow" d="M740 208 H848"/>
|
||||
<path class="soft-arrow" d="M848 250 C740 326 460 326 352 250"/>
|
||||
<text class="note" x="600" y="336" text-anchor="middle">people and agents see the same file tree, so edits and evidence links stay inspectable</text>
|
||||
|
||||
<rect class="panel" x="54" y="398" width="224" height="144"/>
|
||||
<circle class="badge" cx="88" cy="432" r="15"/>
|
||||
<text class="label" x="88" y="436" text-anchor="middle">1</text>
|
||||
<text class="section-title" x="116" y="438">Raw input</text>
|
||||
<text class="section-subtitle" x="82" y="468">keep the original scene</text>
|
||||
<rect class="chip" x="82" y="494" width="168" height="28"/>
|
||||
<text class="chip-text" x="166" y="513" text-anchor="middle">session/ + resource/</text>
|
||||
<rect class="panel" x="54" y="398" width="224" height="144"/>
|
||||
<circle class="badge" cx="88" cy="432" r="15"/>
|
||||
<text class="label" x="88" y="436" text-anchor="middle">1</text>
|
||||
<text class="section-title" x="116" y="438">Raw input</text>
|
||||
<text class="section-subtitle" x="82" y="468">keep the original scene</text>
|
||||
<rect class="chip" x="82" y="494" width="168" height="28"/>
|
||||
<text class="chip-text" x="166" y="513" text-anchor="middle">session/ + resource/</text>
|
||||
|
||||
<rect class="panel" x="326" y="398" width="224" height="144"/>
|
||||
<circle class="badge" cx="360" cy="432" r="15"/>
|
||||
<text class="label" x="360" y="436" text-anchor="middle">2</text>
|
||||
<text class="section-title" x="388" y="438">Daily</text>
|
||||
<text class="section-subtitle" x="354" y="468">shallow working memory</text>
|
||||
<rect class="chip" x="354" y="494" width="168" height="28"/>
|
||||
<text class="chip-text" x="438" y="513" text-anchor="middle">daily/YYYY-MM-DD/*.md</text>
|
||||
<rect class="panel" x="326" y="398" width="224" height="144"/>
|
||||
<circle class="badge" cx="360" cy="432" r="15"/>
|
||||
<text class="label" x="360" y="436" text-anchor="middle">2</text>
|
||||
<text class="section-title" x="388" y="438">Daily</text>
|
||||
<text class="section-subtitle" x="354" y="468">shallow working memory</text>
|
||||
<rect class="chip" x="354" y="494" width="168" height="28"/>
|
||||
<text class="chip-text" x="438" y="513" text-anchor="middle">daily/<date>/<name>.md</text>
|
||||
|
||||
<rect class="panel" x="598" y="398" width="224" height="144"/>
|
||||
<circle class="badge" cx="632" cy="432" r="15"/>
|
||||
<text class="label" x="632" y="436" text-anchor="middle">3</text>
|
||||
<text class="section-title" x="660" y="438">Digest</text>
|
||||
<text class="section-subtitle" x="626" y="468">long-term reusable nodes</text>
|
||||
<rect class="chip" x="626" y="494" width="168" height="28"/>
|
||||
<text class="chip-text" x="710" y="513" text-anchor="middle">personal / procedure / wiki</text>
|
||||
<rect class="panel" x="598" y="398" width="224" height="144"/>
|
||||
<circle class="badge" cx="632" cy="432" r="15"/>
|
||||
<text class="label" x="632" y="436" text-anchor="middle">3</text>
|
||||
<text class="section-title" x="660" y="438">Digest</text>
|
||||
<text class="section-subtitle" x="626" y="468">long-term reusable nodes</text>
|
||||
<rect class="chip" x="626" y="494" width="168" height="28"/>
|
||||
<text class="chip-text" x="710" y="513" text-anchor="middle">personal / procedure / wiki</text>
|
||||
|
||||
<rect class="panel" x="870" y="398" width="224" height="144"/>
|
||||
<circle class="badge" cx="904" cy="432" r="15"/>
|
||||
<text class="label" x="904" y="436" text-anchor="middle">4</text>
|
||||
<text class="section-title" x="932" y="438">Metadata</text>
|
||||
<text class="section-subtitle" x="898" y="468">system state and indexes</text>
|
||||
<rect class="chip" x="898" y="494" width="168" height="28"/>
|
||||
<text class="chip-text" x="982" y="513" text-anchor="middle">catalog + chunks + links</text>
|
||||
<rect class="panel" x="870" y="398" width="224" height="144"/>
|
||||
<circle class="badge" cx="904" cy="432" r="15"/>
|
||||
<text class="label" x="904" y="436" text-anchor="middle">4</text>
|
||||
<text class="section-title" x="932" y="438">Metadata</text>
|
||||
<text class="section-subtitle" x="898" y="468">system state and indexes</text>
|
||||
<rect class="chip" x="898" y="494" width="168" height="28"/>
|
||||
<text class="chip-text" x="982" y="513" text-anchor="middle">catalog + chunks + links</text>
|
||||
|
||||
<path class="arrow" d="M278 470 H326"/>
|
||||
<path class="arrow" d="M550 470 H598"/>
|
||||
<path class="arrow" d="M822 470 H870"/>
|
||||
<path class="soft-arrow" d="M982 398 C982 356 710 356 710 398"/>
|
||||
<path class="soft-arrow" d="M982 542 C982 592 438 592 438 542"/>
|
||||
<path class="arrow" d="M278 470 H326"/>
|
||||
<path class="arrow" d="M550 470 H598"/>
|
||||
<path class="arrow" d="M822 470 H870"/>
|
||||
<path class="soft-arrow" d="M982 398 C982 356 710 356 710 398"/>
|
||||
<path class="soft-arrow" d="M982 542 C982 592 438 592 438 542"/>
|
||||
|
||||
<rect class="panel" x="126" y="594" width="948" height="46"/>
|
||||
<text class="note" x="176" y="622">Stable paths</text>
|
||||
<line class="line" x1="308" y1="606" x2="308" y2="628"/>
|
||||
<text class="chip-text" x="360" y="622">workspace-relative wikilinks</text>
|
||||
<line class="line" x1="548" y1="606" x2="548" y2="628"/>
|
||||
<text class="chip-text" x="600" y="622">derived_from evidence edges</text>
|
||||
<line class="line" x1="814" y1="606" x2="814" y2="628"/>
|
||||
<text class="chip-text" x="862" y="622">search expands structure and links</text>
|
||||
<rect class="panel" x="126" y="594" width="948" height="46"/>
|
||||
<text class="note" x="176" y="622">Stable paths</text>
|
||||
<line class="line" x1="308" y1="606" x2="308" y2="628"/>
|
||||
<text class="chip-text" x="360" y="622">workspace-relative wikilinks</text>
|
||||
<line class="line" x1="548" y1="606" x2="548" y2="628"/>
|
||||
<text class="chip-text" x="600" y="622">derived_from evidence edges</text>
|
||||
<line class="line" x1="814" y1="606" x2="814" y2="628"/>
|
||||
<text class="chip-text" x="862" y="622">search expands linked context</text>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 7 KiB After Width: | Height: | Size: 7.7 KiB |
BIN
docs/figure/qwenpaw-auto-dream.gif
Normal file
|
After Width: | Height: | Size: 3.2 MiB |
BIN
docs/figure/qwenpaw-auto-memory.gif
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
76
docs/figure/reme-blog/reme-blog-architecture.svg
Normal file
|
After Width: | Height: | Size: 47 KiB |
89
docs/figure/reme-blog/reme-blog-auto-dream.svg
Normal file
|
After Width: | Height: | Size: 385 KiB |
64
docs/figure/reme-blog/reme-blog-auto-memory.svg
Normal file
|
After Width: | Height: | Size: 46 KiB |
80
docs/figure/reme-blog/reme-blog-auto-resource.svg
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="640" viewBox="0 0 1200 640" role="img"
|
||||
aria-labelledby="title desc">
|
||||
<title id="title">Auto Resource</title>
|
||||
<desc id="desc">A general workflow that imports external information, interprets it with traceable sources, and
|
||||
integrates it into a user-owned personal knowledge base.
|
||||
</desc>
|
||||
<defs>
|
||||
<style>.bg{fill:#fffdf8}.title{font:800 30px Arial,sans-serif;fill:#1f2430}.sub{font:14px Arial,sans-serif;fill:#667085}.panel{fill:#fff;stroke:#1f2430;stroke-width:2}.head{font:700 18px Arial,sans-serif;fill:#1f2430}.text{font:13px Arial,sans-serif;fill:#5e6a7c}.small{font:12px Arial,sans-serif;fill:#667085}.label{font:700 13px Arial,sans-serif;fill:#1f2430}.path{font:700 13px Arial,sans-serif;fill:#e66f11}.chip{fill:#f8fbff;stroke:#1f2430;stroke-width:1.3;stroke-dasharray:6 5}.orange{fill:#fff3e7}.blue{fill:#eef7ff}.green{fill:#eef9f4}.arrow{fill:none;stroke:#8793a3;stroke-width:2.5;marker-end:url(#a)}</style>
|
||||
<marker id="a" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto">
|
||||
<path d="M0 0v9l9-4.5z" fill="#8793a3"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect class="bg" width="1200" height="640"/>
|
||||
<text text-anchor="middle"><tspan class="title" x="600" y="50">Auto Resource</tspan><tspan class="sub" x="600" y="77">Bring external information into a traceable, user-owned knowledge base</tspan></text>
|
||||
<text x="1080" y="51" style="font:800 25px Arial,sans-serif;fill:#19bfc3">Re</text>
|
||||
<text x="1111" y="51" style="font:800 25px Arial,sans-serif;fill:#3274d9">Me</text>
|
||||
|
||||
<rect class="panel" x="46" y="112" width="304" height="458" rx="18"/>
|
||||
<circle cx="92" cy="159" r="24" fill="#ff963d"/>
|
||||
<path d="M80 149h21l7 7v19H80z" fill="#fff"/>
|
||||
<path d="M101 149v8h8" fill="none" stroke="#ff963d" stroke-width="2"/>
|
||||
<text class="head" x="128" y="155">External Information</text>
|
||||
<text class="text" x="128" y="179" style="font-size:13px">Originals remain verifiable</text>
|
||||
|
||||
<rect class="chip orange" x="76" y="214" width="244" height="68" rx="11"/>
|
||||
<text class="label" x="198" y="240" text-anchor="middle">Documents · notes · web archives</text>
|
||||
<text class="small" x="198" y="263" text-anchor="middle">research · reports · structured data</text>
|
||||
<rect class="chip" x="76" y="302" width="244" height="68" rx="11"/>
|
||||
<text class="label" x="198" y="328" text-anchor="middle">md · txt · json · jsonl</text>
|
||||
<text class="small" x="198" y="351" text-anchor="middle">csv · yaml · html</text>
|
||||
<rect class="chip" x="76" y="390" width="244" height="68" rx="11"/>
|
||||
<text class="label" x="198" y="416" text-anchor="middle">Create · update · delete</text>
|
||||
<text class="small" x="198" y="439" text-anchor="middle" style="font-size:12px">Changes stay visible and recoverable</text>
|
||||
<text class="path" x="198" y="505" text-anchor="middle">resource/</text>
|
||||
<text class="text" x="198" y="532" text-anchor="middle">Original files remain user-owned</text>
|
||||
|
||||
<path class="arrow" d="M350 341h62"/>
|
||||
|
||||
<rect class="panel" x="412" y="112" width="376" height="458" rx="18"/>
|
||||
<circle cx="458" cy="159" r="24" fill="#5b91dc"/>
|
||||
<path d="M445 149h26v22h-26zM451 143h14v6" fill="#fff" stroke="#fff" stroke-width="3" stroke-linejoin="round"/>
|
||||
<text class="head" x="494" y="155">Auto Resource Ingestion</text>
|
||||
<text class="text" x="494" y="179">Parse, interpret, and preserve provenance</text>
|
||||
|
||||
<rect class="chip blue" x="442" y="214" width="316" height="68" rx="11"/>
|
||||
<text class="label" x="468" y="240">1 Parse content and structure</text>
|
||||
<text class="small" x="468" y="263">Read text, sections, fields, and metadata</text>
|
||||
<rect class="chip orange" x="442" y="302" width="316" height="68" rx="11"/>
|
||||
<text class="label" x="468" y="328">2 Interpret reusable information</text>
|
||||
<text class="small" x="468" y="351" style="font-size:12px">Extract themes, facts, and actionable details</text>
|
||||
<rect class="chip green" x="442" y="390" width="316" height="68" rx="11"/>
|
||||
<text class="label" x="468" y="416">3 Attach traceable provenance</text>
|
||||
<text class="small" x="468" y="439" style="font-size:12px">Link each memory with source_resource</text>
|
||||
<text class="path" x="600" y="505" text-anchor="middle">daily/</text>
|
||||
<text class="text" x="600" y="532" text-anchor="middle">Write readable resource memory</text>
|
||||
|
||||
<path class="arrow" d="M788 341h62"/>
|
||||
|
||||
<rect class="panel" x="850" y="112" width="304" height="458" rx="18"/>
|
||||
<circle cx="896" cy="159" r="24" fill="#ff963d"/>
|
||||
<path d="M883 171c7-19 18-26 27-27-1 12-6 25-27 27z" fill="#fff"/>
|
||||
<path d="M896 151v23" stroke="#fff" stroke-width="3" stroke-linecap="round"/>
|
||||
<text class="head" x="932" y="155">Knowledge Base</text>
|
||||
<text class="text" x="932" y="179" style="font-size:13px">One memory system per source</text>
|
||||
|
||||
<rect class="chip blue" x="880" y="214" width="244" height="68" rx="11"/>
|
||||
<text class="label" x="1002" y="240" text-anchor="middle">daily/</text>
|
||||
<text class="small" x="1002" y="263" text-anchor="middle" style="font-size:12px">Readable, traceable resource memory</text>
|
||||
<rect class="chip green" x="880" y="302" width="244" height="68" rx="11"/>
|
||||
<text class="label" x="1002" y="328" text-anchor="middle">digest/</text>
|
||||
<text class="small" x="1002" y="351" text-anchor="middle" style="font-size:12px">Long-term memory via Auto Dream</text>
|
||||
<rect class="chip" x="880" y="390" width="244" height="68" rx="11"/>
|
||||
<text class="label" x="1002" y="416" text-anchor="middle">metadata/</text>
|
||||
<text class="small" x="1002" y="439" text-anchor="middle" style="font-size:12px">Rebuildable index and link graph</text>
|
||||
<text class="path" x="1002" y="505" text-anchor="middle">Search · link · reuse</text>
|
||||
<text class="text" x="1002" y="532" text-anchor="middle">External knowledge returns to future tasks</text>
|
||||
|
||||
<text class="small" x="600" y="610" text-anchor="middle">User files stay authoritative · derived indexes remain rebuildable</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6 KiB |
BIN
docs/figure/reme-blog/reme-blog-cover-benchmark.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
44
docs/figure/reme-blog/reme-blog-cover-benchmark.svg
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="1680" height="973"
|
||||
viewBox="0 0 1680 973" role="img" aria-labelledby="title desc">
|
||||
<title id="title">ReMe Hero and Public Benchmark Results</title>
|
||||
<desc id="desc">ReMe self-evolving personal knowledge base hero with LongMemEval 89.4%, BEAM 100K 66.1%, and BEAM 1M
|
||||
65.0% results.
|
||||
</desc>
|
||||
<defs>
|
||||
<linearGradient id="glass" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0" stop-color="#171b25" stop-opacity="0.96"/>
|
||||
<stop offset="0.58" stop-color="#202530" stop-opacity="0.94"/>
|
||||
<stop offset="1" stop-color="#252b36" stop-opacity="0.92"/>
|
||||
</linearGradient>
|
||||
<filter id="shadow" x="-20%" y="-30%" width="140%" height="180%">
|
||||
<feDropShadow dx="0" dy="-8" stdDeviation="16" flood-color="#000" flood-opacity="0.16"/>
|
||||
</filter>
|
||||
<style>.sans{font-family:Arial,sans-serif} .eyebrow{font-size:17px;font-weight:750;letter-spacing:2px;fill:#ffad61} .metric{font-size:49px;font-weight:900;letter-spacing:-1.5px;fill:#fff} .label{font-size:17px;font-weight:720;fill:#fff} .small{font-size:14px;font-weight:520;fill:#d0d5dd}</style>
|
||||
</defs>
|
||||
<image xlink:href="reme-blog-cover.png" x="0" y="0" width="1680" height="973" preserveAspectRatio="xMidYMid slice"/>
|
||||
<rect x="0" y="711" width="1680" height="262" fill="url(#glass)" filter="url(#shadow)"/>
|
||||
<g class="sans">
|
||||
<text class="eyebrow" x="84" y="765">PUBLIC BENCHMARK RESULTS</text>
|
||||
<text class="small" x="84" y="798">Agentic scores across multi-session and long-context settings</text>
|
||||
|
||||
<line x1="84" y1="829" x2="1596" y2="829" stroke="#fff" stroke-opacity="0.17"/>
|
||||
|
||||
<text class="metric" x="84" y="898">89.4%</text>
|
||||
<text class="label" x="277" y="875">LongMemEval cleaned-s</text>
|
||||
<text class="small" x="277" y="903">500 questions · cross-session retrieval</text>
|
||||
|
||||
<line x1="590" y1="852" x2="590" y2="923" stroke="#fff" stroke-opacity="0.18"/>
|
||||
|
||||
<text class="metric" x="650" y="898">66.1%</text>
|
||||
<text class="label" x="833" y="875">BEAM 100K</text>
|
||||
<text class="small" x="833" y="903">20 cases · 400 questions</text>
|
||||
|
||||
<line x1="1080" y1="852" x2="1080" y2="923" stroke="#fff" stroke-opacity="0.18"/>
|
||||
|
||||
<text class="metric" x="1140" y="898">65.0%</text>
|
||||
<text class="label" x="1323" y="875">BEAM 1M</text>
|
||||
<text class="small" x="1323" y="903">35 cases · 700 questions</text>
|
||||
|
||||
<text x="1596" y="950" text-anchor="end" style="font-size:12px;font-weight:520;fill:#98a2b3">Scores from different benchmarks are not compared directly</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
BIN
docs/figure/reme-blog/reme-blog-cover.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
53
docs/figure/reme-blog/reme-blog-memory-as-file.svg
Normal file
|
After Width: | Height: | Size: 382 KiB |
65
docs/figure/reme-blog/reme-blog-memory-index.svg
Normal file
|
After Width: | Height: | Size: 46 KiB |
64
docs/figure/reme-blog/reme-blog-memory-search.svg
Normal file
|
After Width: | Height: | Size: 46 KiB |
67
docs/figure/reme-blog/reme-blog-proactive.svg
Normal file
|
After Width: | Height: | Size: 47 KiB |
|
|
@ -1,204 +1,184 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="640" viewBox="0 0 1200 640" role="img" aria-labelledby="title desc">
|
||||
<title id="title">ReMe overview</title>
|
||||
<desc id="desc">A hand-drawn style overview of ReMe, showing Auto Memory plus Auto Resource, Auto Dream plus Proactive, Auto Index plus Memory Search, and Memory as File.</desc>
|
||||
<defs>
|
||||
<style>
|
||||
.bg { fill: #fffdf8; }
|
||||
.ink { stroke: #1f2430; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
|
||||
.thin { stroke-width: 1.6; }
|
||||
.dash { stroke-dasharray: 8 7; }
|
||||
.title { font: 700 30px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1f2430; }
|
||||
.subtitle { font: 14px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #556276; }
|
||||
.head { font: 700 17px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1f2430; }
|
||||
.label { font: 700 13px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1f2430; }
|
||||
.text { font: 12px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #4f5c6f; }
|
||||
.tiny { font: 11px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #5e6a7c; }
|
||||
.box { fill: #ffffff; }
|
||||
.blue { fill: #eef7ff; }
|
||||
.green { fill: #f0fbf5; }
|
||||
.yellow { fill: #fff7e5; }
|
||||
.pink { fill: #fff2f6; }
|
||||
.violet { fill: #f4f1ff; }
|
||||
.mint { fill: #eefafa; }
|
||||
.peach { fill: #fff2ea; }
|
||||
.paper { fill: #f8fbff; }
|
||||
.tab { fill: #ffffff; }
|
||||
.step { stroke-dasharray: 6 5; }
|
||||
.arrow { fill: none; stroke: #7f8b9d; stroke-width: 1.45; stroke-linecap: round; stroke-linejoin: round; marker-end: url(#arrow); }
|
||||
.soft-arrow { fill: none; stroke: #a3adbd; stroke-width: 1.25; stroke-dasharray: 6 6; stroke-linecap: round; stroke-linejoin: round; marker-end: url(#arrow-soft); }
|
||||
</style>
|
||||
<marker id="arrow" markerWidth="6" markerHeight="6" refX="5" refY="2" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,4 L5,2 z" fill="#7f8b9d"/>
|
||||
</marker>
|
||||
<marker id="arrow-soft" markerWidth="6" markerHeight="6" refX="5" refY="2" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,4 L5,2 z" fill="#a3adbd"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="640" viewBox="0 0 1200 640" role="img"
|
||||
aria-labelledby="title desc">
|
||||
<title id="title">ReMe overview</title>
|
||||
<desc id="desc">A hand-drawn style overview of ReMe, showing Auto Memory plus Auto Resource, Auto Dream plus
|
||||
Proactive, Auto Index plus Memory Search, and Memory as File.
|
||||
</desc>
|
||||
<defs>
|
||||
<style>.bg { fill: #fffdf8; } .ink { stroke: #1f2430; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; } .thin { stroke-width: 1.6; } .dash { stroke-dasharray: 8 7; } .title { font: 700 30px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1f2430; } .subtitle { font: 14px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #556276; } .head { font: 700 17px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1f2430; } .label { font: 700 13px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #1f2430; } .text { font: 12px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #4f5c6f; } .tiny { font: 11px "Comic Sans MS", "Bradley Hand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #5e6a7c; } .box { fill: #ffffff; } .blue { fill: #eef7ff; } .green { fill: #f0fbf5; } .yellow { fill: #fff7e5; } .pink { fill: #fff2f6; } .violet { fill: #f4f1ff; } .mint { fill: #eefafa; } .peach { fill: #fff2ea; } .paper { fill: #f8fbff; } .tab { fill: #ffffff; } .step { stroke-dasharray: 6 5; } .arrow { fill: none; stroke: #7f8b9d; stroke-width: 1.45; stroke-linecap: round; stroke-linejoin: round; marker-end: url(#arrow); } .soft-arrow { fill: none; stroke: #a3adbd; stroke-width: 1.25; stroke-dasharray: 6 6; stroke-linecap: round; stroke-linejoin: round; marker-end: url(#arrow-soft); }</style>
|
||||
<marker id="arrow" markerWidth="6" markerHeight="6" refX="5" refY="2" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,4 L5,2 z" fill="#7f8b9d"/>
|
||||
</marker>
|
||||
<marker id="arrow-soft" markerWidth="6" markerHeight="6" refX="5" refY="2" orient="auto"
|
||||
markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,4 L5,2 z" fill="#a3adbd"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect class="bg" x="0" y="0" width="1200" height="640"/>
|
||||
<text class="title" x="600" y="44" text-anchor="middle">ReMe</text>
|
||||
<text class="subtitle" x="600" y="68" text-anchor="middle">A file-native memory loop: capture, consolidate, link, search, and proactively surface what matters.</text>
|
||||
<rect class="bg" x="0" y="0" width="1200" height="640"/>
|
||||
<text class="title" x="600" y="44" text-anchor="middle">ReMe</text>
|
||||
<text class="subtitle" x="600" y="68" text-anchor="middle">A file-native memory loop: capture, consolidate, link, search, and proactively surface what matters.</text>
|
||||
|
||||
<!-- Top workflow -->
|
||||
<g transform="translate(40 120)">
|
||||
<rect class="blue ink thin" x="0" y="0" width="300" height="260" rx="18"/>
|
||||
<text class="head" x="150" y="31" text-anchor="middle">Auto Memory + Auto Resource</text>
|
||||
<text class="text" x="150" y="56" text-anchor="middle">Capture sessions and resources into daily cards.</text>
|
||||
<!-- Top workflow -->
|
||||
<g transform="translate(40 120)">
|
||||
<rect class="blue ink thin" x="0" y="0" width="300" height="260" rx="18"/>
|
||||
<text class="head" x="150" y="31" text-anchor="middle">Auto Memory + Auto Resource</text>
|
||||
<text class="text" x="150" y="56" text-anchor="middle" style="font-size:12px">Capture sessions and resources into daily cards.</text>
|
||||
|
||||
<g transform="translate(24 78)">
|
||||
<rect class="blue ink thin dash" x="0" y="0" width="252" height="64" rx="13"/>
|
||||
<text class="label" x="54" y="27" text-anchor="middle">Auto</text>
|
||||
<text class="label" x="54" y="45" text-anchor="middle">Memory</text>
|
||||
<g transform="translate(24 78)">
|
||||
<rect class="blue ink thin dash" x="0" y="0" width="252" height="64" rx="13"/>
|
||||
<text class="label" x="54" y="27" text-anchor="middle">Auto</text>
|
||||
<text class="label" x="54" y="45" text-anchor="middle">Memory</text>
|
||||
|
||||
<rect class="box ink thin step" x="100" y="12" width="62" height="40" rx="9"/>
|
||||
<text class="label" x="131" y="29" text-anchor="middle">Capture</text>
|
||||
<text class="tiny" x="131" y="44" text-anchor="middle">chat</text>
|
||||
<rect class="box ink thin step" x="100" y="12" width="62" height="40" rx="9"/>
|
||||
<text class="label" x="131" y="29" text-anchor="middle">Capture</text>
|
||||
<text class="tiny" x="131" y="44" text-anchor="middle">chat</text>
|
||||
|
||||
<rect class="yellow ink thin step" x="178" y="12" width="56" height="40" rx="9"/>
|
||||
<text class="label" x="206" y="29" text-anchor="middle">Write</text>
|
||||
<text class="tiny" x="206" y="44" text-anchor="middle">daily</text>
|
||||
<rect class="yellow ink thin step" x="178" y="12" width="56" height="40" rx="9"/>
|
||||
<text class="label" x="206" y="29" text-anchor="middle">Write</text>
|
||||
<text class="tiny" x="206" y="44" text-anchor="middle">daily</text>
|
||||
|
||||
<path class="arrow" d="M164 32 H176"/>
|
||||
<path class="arrow" d="M164 32 H176"/>
|
||||
</g>
|
||||
|
||||
<g transform="translate(24 162)">
|
||||
<rect class="green ink thin dash" x="0" y="0" width="252" height="64" rx="13"/>
|
||||
<text class="label" x="54" y="27" text-anchor="middle">Auto</text>
|
||||
<text class="label" x="54" y="45" text-anchor="middle">Resource</text>
|
||||
|
||||
<rect class="box ink thin step" x="100" y="12" width="62" height="40" rx="9"/>
|
||||
<text class="label" x="131" y="29" text-anchor="middle">Read</text>
|
||||
<text class="tiny" x="131" y="44" text-anchor="middle">source</text>
|
||||
|
||||
<rect class="yellow ink thin step" x="178" y="12" width="56" height="40" rx="9"/>
|
||||
<text class="label" x="206" y="29" text-anchor="middle">Write</text>
|
||||
<text class="tiny" x="206" y="44" text-anchor="middle">daily</text>
|
||||
|
||||
<path class="arrow" d="M164 32 H176"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g transform="translate(24 162)">
|
||||
<rect class="green ink thin dash" x="0" y="0" width="252" height="64" rx="13"/>
|
||||
<text class="label" x="54" y="27" text-anchor="middle">Auto</text>
|
||||
<text class="label" x="54" y="45" text-anchor="middle">Resource</text>
|
||||
<g transform="translate(380 120)">
|
||||
<rect class="yellow ink thin" x="0" y="0" width="320" height="260" rx="18"/>
|
||||
<text class="head" x="160" y="32" text-anchor="middle">Auto Dream + Proactive</text>
|
||||
<text class="text" x="160" y="58" text-anchor="middle" style="font-size:12px">Consolidate notes, link knowledge, surface interests.</text>
|
||||
|
||||
<rect class="box ink thin step" x="100" y="12" width="62" height="40" rx="9"/>
|
||||
<text class="label" x="131" y="29" text-anchor="middle">Read</text>
|
||||
<text class="tiny" x="131" y="44" text-anchor="middle">source</text>
|
||||
<rect class="box ink thin step" x="38" y="84" width="112" height="56" rx="12"/>
|
||||
<text class="label" x="94" y="108" text-anchor="middle">Extract</text>
|
||||
<text class="tiny" x="94" y="126" text-anchor="middle">changed files</text>
|
||||
|
||||
<rect class="yellow ink thin step" x="178" y="12" width="56" height="40" rx="9"/>
|
||||
<text class="label" x="206" y="29" text-anchor="middle">Write</text>
|
||||
<text class="tiny" x="206" y="44" text-anchor="middle">daily</text>
|
||||
<rect class="peach ink thin step" x="170" y="84" width="112" height="56" rx="12"/>
|
||||
<text class="label" x="226" y="108" text-anchor="middle">Auto Link</text>
|
||||
<text class="tiny" x="226" y="126" text-anchor="middle">dedupe + edges</text>
|
||||
|
||||
<path class="arrow" d="M164 32 H176"/>
|
||||
</g>
|
||||
</g>
|
||||
<rect class="pink ink thin step" x="38" y="168" width="112" height="56" rx="12"/>
|
||||
<text class="label" x="94" y="192" text-anchor="middle">Integrate</text>
|
||||
<text class="tiny" x="94" y="210" text-anchor="middle">write digest</text>
|
||||
|
||||
<g transform="translate(380 120)">
|
||||
<rect class="yellow ink thin" x="0" y="0" width="320" height="260" rx="18"/>
|
||||
<text class="head" x="160" y="32" text-anchor="middle">Auto Dream + Proactive</text>
|
||||
<text class="text" x="160" y="58" text-anchor="middle">Consolidate notes, link knowledge, surface interests.</text>
|
||||
<rect class="mint ink thin step" x="170" y="168" width="112" height="56" rx="12"/>
|
||||
<text class="label" x="226" y="192" text-anchor="middle">Proactive</text>
|
||||
<text class="tiny" x="226" y="210" text-anchor="middle">interests.yaml</text>
|
||||
|
||||
<rect class="box ink thin step" x="38" y="84" width="112" height="56" rx="12"/>
|
||||
<text class="label" x="94" y="108" text-anchor="middle">Extract</text>
|
||||
<text class="tiny" x="94" y="126" text-anchor="middle">changed files</text>
|
||||
|
||||
<rect class="peach ink thin step" x="170" y="84" width="112" height="56" rx="12"/>
|
||||
<text class="label" x="226" y="108" text-anchor="middle">Auto Link</text>
|
||||
<text class="tiny" x="226" y="126" text-anchor="middle">dedupe + edges</text>
|
||||
|
||||
<rect class="pink ink thin step" x="38" y="168" width="112" height="56" rx="12"/>
|
||||
<text class="label" x="94" y="192" text-anchor="middle">Integrate</text>
|
||||
<text class="tiny" x="94" y="210" text-anchor="middle">write digest</text>
|
||||
|
||||
<rect class="mint ink thin step" x="170" y="168" width="112" height="56" rx="12"/>
|
||||
<text class="label" x="226" y="192" text-anchor="middle">Proactive</text>
|
||||
<text class="tiny" x="226" y="210" text-anchor="middle">interests.yaml</text>
|
||||
|
||||
<path class="arrow" d="M152 112 H168"/>
|
||||
<path class="arrow" d="M226 142 V166"/>
|
||||
<path class="arrow" d="M168 196 H152"/>
|
||||
<path class="arrow" d="M94 166 V142"/>
|
||||
</g>
|
||||
|
||||
<g transform="translate(730 120)">
|
||||
<rect class="violet ink thin" x="0" y="0" width="430" height="260" rx="18"/>
|
||||
<text class="head" x="215" y="31" text-anchor="middle">Auto Index + Memory Search</text>
|
||||
<text class="text" x="215" y="56" text-anchor="middle">Keep indexes fresh, recall chunks, then follow links.</text>
|
||||
|
||||
<g transform="translate(24 86)">
|
||||
<rect class="blue ink thin dash" x="0" y="0" width="178" height="136" rx="14"/>
|
||||
<text class="label" x="89" y="24" text-anchor="middle">Auto Index</text>
|
||||
|
||||
<rect class="box ink thin step" x="18" y="46" width="64" height="58" rx="10"/>
|
||||
<text class="label" x="50" y="70" text-anchor="middle">Watch</text>
|
||||
<text class="tiny" x="50" y="88" text-anchor="middle">changes</text>
|
||||
|
||||
<rect class="yellow ink thin step" x="98" y="46" width="64" height="58" rx="10"/>
|
||||
<text class="label" x="130" y="70" text-anchor="middle">Build</text>
|
||||
<text class="tiny" x="130" y="88" text-anchor="middle">index</text>
|
||||
|
||||
<path class="arrow" d="M84 75 H96"/>
|
||||
<path class="arrow" d="M152 112 H168"/>
|
||||
<path class="arrow" d="M226 142 V166"/>
|
||||
<path class="arrow" d="M168 196 H152"/>
|
||||
<path class="arrow" d="M94 166 V142"/>
|
||||
</g>
|
||||
|
||||
<g transform="translate(228 86)">
|
||||
<rect class="green ink thin dash" x="0" y="0" width="178" height="136" rx="14"/>
|
||||
<text class="label" x="89" y="24" text-anchor="middle">Memory Search</text>
|
||||
<g transform="translate(730 120)">
|
||||
<rect class="violet ink thin" x="0" y="0" width="430" height="260" rx="18"/>
|
||||
<text class="head" x="215" y="31" text-anchor="middle">Auto Index + Memory Search</text>
|
||||
<text class="text" x="215" y="56" text-anchor="middle" style="font-size:12px">Keep indexes fresh, recall chunks, then follow links.</text>
|
||||
|
||||
<rect class="box ink thin step" x="18" y="46" width="64" height="58" rx="10"/>
|
||||
<text class="label" x="50" y="70" text-anchor="middle">Recall</text>
|
||||
<text class="tiny" x="50" y="88" text-anchor="middle">BM25 + vec</text>
|
||||
<g transform="translate(24 86)">
|
||||
<rect class="blue ink thin dash" x="0" y="0" width="178" height="136" rx="14"/>
|
||||
<text class="label" x="89" y="24" text-anchor="middle">Auto Index</text>
|
||||
|
||||
<rect class="mint ink thin step" x="98" y="46" width="64" height="58" rx="10"/>
|
||||
<text class="label" x="130" y="70" text-anchor="middle">Expand</text>
|
||||
<text class="tiny" x="130" y="88" text-anchor="middle">links</text>
|
||||
<rect class="box ink thin step" x="18" y="46" width="64" height="58" rx="10"/>
|
||||
<text class="label" x="50" y="70" text-anchor="middle">Watch</text>
|
||||
<text class="tiny" x="50" y="88" text-anchor="middle">changes</text>
|
||||
|
||||
<path class="arrow" d="M84 75 H96"/>
|
||||
<rect class="yellow ink thin step" x="98" y="46" width="64" height="58" rx="10"/>
|
||||
<text class="label" x="130" y="70" text-anchor="middle">Build</text>
|
||||
<text class="tiny" x="130" y="88" text-anchor="middle">index</text>
|
||||
|
||||
<path class="arrow" d="M84 75 H96"/>
|
||||
</g>
|
||||
|
||||
<g transform="translate(228 86)">
|
||||
<rect class="green ink thin dash" x="0" y="0" width="178" height="136" rx="14"/>
|
||||
<text class="label" x="89" y="24" text-anchor="middle">Memory Search</text>
|
||||
|
||||
<rect class="box ink thin step" x="18" y="46" width="64" height="58" rx="10"/>
|
||||
<text class="label" x="50" y="70" text-anchor="middle">Recall</text>
|
||||
<text class="tiny" x="50" y="88" text-anchor="middle" style="font-size:10px">BM25 + vec</text>
|
||||
|
||||
<rect class="mint ink thin step" x="98" y="46" width="64" height="58" rx="10"/>
|
||||
<text class="label" x="130" y="70" text-anchor="middle">Expand</text>
|
||||
<text class="tiny" x="130" y="88" text-anchor="middle">links</text>
|
||||
|
||||
<path class="arrow" d="M84 75 H96"/>
|
||||
</g>
|
||||
|
||||
<path class="arrow" d="M204 161 H226"/>
|
||||
</g>
|
||||
|
||||
<path class="arrow" d="M204 161 H226"/>
|
||||
</g>
|
||||
<path class="arrow" d="M340 272 H380"/>
|
||||
|
||||
<path class="arrow" d="M340 272 H380"/>
|
||||
<!-- Bottom foundation -->
|
||||
<rect class="box ink" x="40" y="408" width="1120" height="202" rx="24"/>
|
||||
<text class="head" x="600" y="442" text-anchor="middle">Memory as File</text>
|
||||
<text class="text" x="600" y="466" text-anchor="middle">Every memory is readable, editable, indexable, linkable, and auditable as files.</text>
|
||||
|
||||
<!-- Bottom foundation -->
|
||||
<rect class="box ink" x="40" y="408" width="1120" height="202" rx="24"/>
|
||||
<text class="head" x="600" y="442" text-anchor="middle">Memory as File</text>
|
||||
<text class="text" x="600" y="466" text-anchor="middle">Every memory is readable, editable, indexable, linkable, and auditable as files.</text>
|
||||
<g transform="translate(94 500)">
|
||||
<rect class="paper ink thin" x="10" y="6" width="62" height="52"/>
|
||||
<rect class="paper ink thin" x="5" y="3" width="62" height="52"/>
|
||||
<rect class="paper ink thin" x="0" y="0" width="62" height="52"/>
|
||||
<rect class="tab ink thin" x="-12" y="14" width="44" height="22"/>
|
||||
<text class="label" x="10" y="30" text-anchor="middle">.jsonl</text>
|
||||
<line class="ink thin" x1="26" y1="28" x2="50" y2="28"/>
|
||||
<line class="ink thin" x1="22" y1="42" x2="52" y2="42"/>
|
||||
<text class="label" x="106" y="19">session/</text>
|
||||
<text class="tiny" x="106" y="39">source conversations</text>
|
||||
</g>
|
||||
|
||||
<g transform="translate(94 500)">
|
||||
<rect class="paper ink thin" x="10" y="6" width="62" height="52"/>
|
||||
<rect class="paper ink thin" x="5" y="3" width="62" height="52"/>
|
||||
<rect class="paper ink thin" x="0" y="0" width="62" height="52"/>
|
||||
<rect class="tab ink thin" x="-12" y="14" width="44" height="22"/>
|
||||
<text class="label" x="10" y="30" text-anchor="middle">.json</text>
|
||||
<line class="ink thin" x1="26" y1="28" x2="50" y2="28"/>
|
||||
<line class="ink thin" x1="22" y1="42" x2="52" y2="42"/>
|
||||
<text class="label" x="106" y="19">session/</text>
|
||||
<text class="tiny" x="106" y="39">raw session logs</text>
|
||||
</g>
|
||||
<g transform="translate(344 500)">
|
||||
<rect class="paper ink thin" x="10" y="6" width="62" height="52"/>
|
||||
<rect class="paper ink thin" x="5" y="3" width="62" height="52"/>
|
||||
<rect class="paper ink thin" x="0" y="0" width="62" height="52"/>
|
||||
<rect class="tab ink thin" x="-8" y="14" width="34" height="22"/>
|
||||
<text class="label" x="9" y="30" text-anchor="middle">.src</text>
|
||||
<line class="ink thin" x1="26" y1="28" x2="50" y2="28"/>
|
||||
<line class="ink thin" x1="22" y1="42" x2="52" y2="42"/>
|
||||
<text class="label" x="106" y="19">resource/</text>
|
||||
<text class="tiny" x="106" y="39">original source material</text>
|
||||
</g>
|
||||
|
||||
<g transform="translate(344 500)">
|
||||
<rect class="paper ink thin" x="10" y="6" width="62" height="52"/>
|
||||
<rect class="paper ink thin" x="5" y="3" width="62" height="52"/>
|
||||
<rect class="paper ink thin" x="0" y="0" width="62" height="52"/>
|
||||
<rect class="tab ink thin" x="-8" y="14" width="34" height="22"/>
|
||||
<text class="label" x="9" y="30" text-anchor="middle">.md</text>
|
||||
<line class="ink thin" x1="26" y1="28" x2="50" y2="28"/>
|
||||
<line class="ink thin" x1="22" y1="42" x2="52" y2="42"/>
|
||||
<text class="label" x="106" y="19">resource/</text>
|
||||
<text class="tiny" x="106" y="39">raw material with source</text>
|
||||
</g>
|
||||
<g transform="translate(604 500)">
|
||||
<rect class="paper ink thin" x="10" y="6" width="62" height="52"/>
|
||||
<rect class="paper ink thin" x="5" y="3" width="62" height="52"/>
|
||||
<rect class="paper ink thin" x="0" y="0" width="62" height="52"/>
|
||||
<rect class="tab ink thin" x="-8" y="14" width="34" height="22"/>
|
||||
<text class="label" x="9" y="30" text-anchor="middle">.md</text>
|
||||
<line class="ink thin" x1="26" y1="28" x2="50" y2="28"/>
|
||||
<line class="ink thin" x1="22" y1="42" x2="52" y2="42"/>
|
||||
<text class="label" x="106" y="19">daily/</text>
|
||||
<text class="tiny" x="106" y="39">working memory cards</text>
|
||||
</g>
|
||||
|
||||
<g transform="translate(604 500)">
|
||||
<rect class="paper ink thin" x="10" y="6" width="62" height="52"/>
|
||||
<rect class="paper ink thin" x="5" y="3" width="62" height="52"/>
|
||||
<rect class="paper ink thin" x="0" y="0" width="62" height="52"/>
|
||||
<rect class="tab ink thin" x="-8" y="14" width="34" height="22"/>
|
||||
<text class="label" x="9" y="30" text-anchor="middle">.md</text>
|
||||
<line class="ink thin" x1="26" y1="28" x2="50" y2="28"/>
|
||||
<line class="ink thin" x1="22" y1="42" x2="52" y2="42"/>
|
||||
<text class="label" x="106" y="19">daily/</text>
|
||||
<text class="tiny" x="106" y="39">working memory cards</text>
|
||||
</g>
|
||||
<g transform="translate(850 500)">
|
||||
<rect class="paper ink thin" x="10" y="6" width="62" height="52"/>
|
||||
<rect class="paper ink thin" x="5" y="3" width="62" height="52"/>
|
||||
<rect class="paper ink thin" x="0" y="0" width="62" height="52"/>
|
||||
<rect class="tab ink thin" x="-8" y="14" width="34" height="22"/>
|
||||
<text class="label" x="9" y="30" text-anchor="middle">.md</text>
|
||||
<line class="ink thin" x1="26" y1="28" x2="50" y2="28"/>
|
||||
<line class="ink thin" x1="22" y1="42" x2="52" y2="42"/>
|
||||
<text class="label" x="106" y="19">digest/</text>
|
||||
<text class="tiny" x="106" y="39">long-term knowledge nodes</text>
|
||||
</g>
|
||||
|
||||
<g transform="translate(850 500)">
|
||||
<rect class="paper ink thin" x="10" y="6" width="62" height="52"/>
|
||||
<rect class="paper ink thin" x="5" y="3" width="62" height="52"/>
|
||||
<rect class="paper ink thin" x="0" y="0" width="62" height="52"/>
|
||||
<rect class="tab ink thin" x="-8" y="14" width="34" height="22"/>
|
||||
<text class="label" x="9" y="30" text-anchor="middle">.md</text>
|
||||
<line class="ink thin" x1="26" y1="28" x2="50" y2="28"/>
|
||||
<line class="ink thin" x1="22" y1="42" x2="52" y2="42"/>
|
||||
<text class="label" x="106" y="19">digest/</text>
|
||||
<text class="tiny" x="106" y="39">long-term knowledge nodes</text>
|
||||
</g>
|
||||
|
||||
<path class="soft-arrow" d="M190 380 V408"/>
|
||||
<path class="soft-arrow" d="M540 380 V408"/>
|
||||
<path class="soft-arrow" d="M945 380 V408"/>
|
||||
<path class="soft-arrow" d="M190 380 V408"/>
|
||||
<path class="soft-arrow" d="M540 380 V408"/>
|
||||
<path class="soft-arrow" d="M945 380 V408"/>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
|
@ -1,330 +0,0 @@
|
|||
# auto-cognition 设计(顶层:心智循环)
|
||||
|
||||
> 本文档:reme 中**长期记忆系统**的顶层认知模型 —— 把 agent 的记忆生命周期类比人类睡眠/觉醒回路,推导出**三阶段分工**与**15 维能力清单**。
|
||||
>
|
||||
> **三阶段实现各有专属文档**:
|
||||
> - Stage 1 写入(REM 重放抽象) → `auto_dream_design.md`
|
||||
> - Stage 2 巩固(NREM 深度整合) → `auto_consolidate_design.md`
|
||||
> - Stage 3 检索(觉醒态提取) → `auto_recall_design.md`
|
||||
>
|
||||
> 配套阅读:
|
||||
> - `auto_memory_design.md`:入流端(daily 写入),与 cognition 平行 —— cognition 负责"已落地后的认知循环",memory 负责"经历落地"
|
||||
> - `structure.md` §4(retrieve 三种问法)
|
||||
>
|
||||
> **核心立场**:
|
||||
> - 长期记忆不是"存 + 取"两个动作,是**写入 → 巩固 → 提取**的循环 —— 三段时间尺度不同(同步 / 周期 / 同步),设计形态不同
|
||||
> - workspace 是**事实层**,只承载经过 LLM 写入认证的关系;`meta/` 是**派生层**,承载概率推断的统计信号
|
||||
> - 任一阶段独立演化,任一信号缺失系统降级而不崩
|
||||
|
||||
---
|
||||
|
||||
## 0. 心智循环:reme 的认知模型
|
||||
|
||||
agent 的长期记忆系统在概念上对应人脑的**海马—皮层回路 + 睡眠—觉醒周期**:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ 外部经验(daily / resource) │
|
||||
└─────────────┬───────────────────┘
|
||||
│ (auto-memory 写 daily)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ┌────────────────┐ 抽象 / 关系编织 │
|
||||
│ │ Stage 1 │ ◄─ 类比 REM 睡眠 │
|
||||
│ │ auto-dream │ "重放 + 写进 schema" │
|
||||
│ └───────┬────────┘ │
|
||||
│ │ 写 workspace(digest body + wikilink) │
|
||||
│ ▼ │
|
||||
│ ┌────────────────┐ │
|
||||
│ │ workspace(事实) │ │
|
||||
│ └───────┬────────┘ │
|
||||
│ │ 只读 │
|
||||
│ ▼ │
|
||||
│ ┌────────────────┐ 长期组织 / 派生指标 │
|
||||
│ │ Stage 2 │ ◄─ 类比 NREM 慢波睡眠 │
|
||||
│ │ auto-consol- │ "巩固 + 修剪 + 集群" │
|
||||
│ │ idate │ │
|
||||
│ └───────┬────────┘ │
|
||||
│ │ 写 meta/ + audit/(派生层) │
|
||||
│ ▼ │
|
||||
│ ┌────────────────┐ │
|
||||
│ │ meta(派生) │ │
|
||||
│ └───────┬────────┘ │
|
||||
│ │ 只读 │
|
||||
│ ▼ │
|
||||
│ ┌────────────────┐ query → 答案合成 │
|
||||
│ │ Stage 3 │ ◄─ 类比觉醒态 cue retrieval│
|
||||
│ │ auto-recall │ "融合 + pattern complete"│
|
||||
│ └───────┬────────┘ │
|
||||
│ │ │
|
||||
└───────────┼─────────────────────────────────────┘
|
||||
│ 召回结果给 agent
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ agent query │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
**心智循环回答四个根本问题**:
|
||||
|
||||
| 问题 | 谁回答 |
|
||||
|---|---|
|
||||
| 我经历过什么? | auto-memory(daily 入流) |
|
||||
| 我从中学到什么? | Stage 1 — auto-dream |
|
||||
| 这些知识如何长期组织? | Stage 2 — auto-consolidate |
|
||||
| 我需要时如何调用? | Stage 3 — auto-recall |
|
||||
|
||||
memory 负责"经历落地",cognition 三阶段负责"已落地经历的认知循环"。
|
||||
|
||||
---
|
||||
|
||||
## 1. 三阶段全景
|
||||
|
||||
| 阶段 | 神经科学类比 | 时间尺度 | 改 workspace | 实现归属 |
|
||||
|---|---|---|---|---|
|
||||
| **Stage 1 dream** | REM 重放抽象 | 同步(随入流即跑) | 是(写 digest body) | `auto_dream_design.md` |
|
||||
| **Stage 2 consolidate** | NREM 深度巩固 | 周期 / idle(daily / weekly)| **否**(写 `meta/` + `audit/`)| `auto_consolidate_design.md` |
|
||||
| **Stage 3 recall** | 觉醒态 cue retrieval | 同步(query 触发) | 否(只读;唯一对外写是 `meta/access_log.json`)| `auto_recall_design.md` |
|
||||
|
||||
**关键的不对称**:
|
||||
- 写入与检索是**同步**的(用户 / agent 等待),巩固是**离线**的(idle / 周期)
|
||||
- 改 workspace 的资格被严格限制在 **dream + consolidate 中的 split** —— 其它阶段全只读
|
||||
- 三阶段时间尺度差三个数量级,这是设计形态(同步 vs 异步 vs idle)的根本来源
|
||||
|
||||
---
|
||||
|
||||
## 2. 系统级能力(贯穿三阶段)
|
||||
|
||||
不属任何单阶段,但任一阶段不能违反:
|
||||
|
||||
| 能力 | 含义 |
|
||||
|---|---|
|
||||
| **事实层 vs 派生层分离** | workspace 只承载经 LLM 写入认证的关系(显式 wikilink);`meta/` 承载概率推断的派生指标(community / recency / archived);两者绝不混同 |
|
||||
| **不变量守恒** | F-invariants(0 文件移动 / 改正文限定 subject / wikilink 是 body 一部分)+ E-invariants(边守恒 E-1/E-2/E-3)横跨三阶段;详 `auto_dream_design.md` §4.3-§4.4 |
|
||||
| **阶段独立演化** | 任一阶段算法升级不破坏其它阶段(community 算法换 → dream 不变;打分公式调 → consolidate 不变) |
|
||||
| **缺失即降级** | 任一派生信号缺失,系统降级而不崩;冷启动可用 |
|
||||
| **全程可审计** | 每阶段产 audit / report / log,人 / agent 可检视追溯 |
|
||||
|
||||
---
|
||||
|
||||
## 3. Stage 1 — auto-dream:经验 → 抽象
|
||||
|
||||
**类比**:REM 睡眠的记忆重放与抽象提炼。脑在做梦时把白天事件拆解、重组,提取出可泛化的模式,登记进皮层 schema。
|
||||
|
||||
**根本目的**:把"原始经历"转化为"长期值得调取的教训",同时把它编织进已有知识图谱。
|
||||
|
||||
### 3.1 五个能力维度
|
||||
|
||||
逻辑递进 —— 输入 → 抽象 → 整合 → 编织 → 写入:
|
||||
|
||||
| # | 能力 | 它在问什么 | 失效后果 |
|
||||
|---|---|---|---|
|
||||
| 1 | **抽象判断**(gate) | 这段材料里有"值得长期记住"的东西吗? | 噪声进 workspace / 只蒸馏不抽象 |
|
||||
| 2 | **经验重放**(召回) | 这个抽象在已有记忆里**已经存在**吗?以什么形式? | 重复节点 / 错过整合机会 |
|
||||
| 3 | **整合决策** | 创建新节点,还是丰富已有节点?若已有 —— 是再次印证 / 精化范围 / 修正错误? | 已有信息丢失 / 错误没纠正 |
|
||||
| 4 | **关系编织** | 这个抽象与谁有关系?谁是它的来源? | wikilink 缺失,后续 retrieve 漏召 |
|
||||
| 5 | **写入安全** | 写入会不会破坏 workspace 既有事实?并发冲突如何处理? | 边丢失 / race condition |
|
||||
|
||||
### 3.2 关键定性
|
||||
|
||||
- dream 是 workspace 的**唯一写者**(在 cognition 三阶段里;memory 写 daily 不算)
|
||||
- **写入瞬间是关系建立的唯一可信时机** —— 错过的关系不靠后台扫回(那不是 consolidate 的工作)
|
||||
- 一次写入,所有未来检索受益(持久化优于实时计算)
|
||||
|
||||
详细机制见 `auto_dream_design.md`。
|
||||
|
||||
---
|
||||
|
||||
## 4. Stage 2 — auto-consolidate:抽象 → 网络
|
||||
|
||||
**类比**:NREM 慢波睡眠的系统巩固 + 突触代谢稳态。脑在深睡时把分散事件融入 schema、修剪弱连接、把长期不用的记忆淡出意识可达范围。
|
||||
|
||||
**根本目的**:跨时间累积地把 workspace 从"一堆节点"组织成"有结构、有权重、有时效的网络",但**只产派生信号,不污染事实层**。
|
||||
|
||||
### 4.1 五个能力维度
|
||||
|
||||
按作用尺度从微观到宏观:
|
||||
|
||||
| # | 能力 | 作用尺度 | 类比 | 输出形态 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | **结构维护** | 节点级 | 海马表征过密 → 分化新单元 | 改 workspace(split,唯一例外)|
|
||||
| 2 | **跨节点关系发现** | 节点对级 | 多次睡眠中识别"同一件事" → schema | `audit/` 报告 |
|
||||
| 3 | **主题集群形成** | 子图级 | 皮层网络的功能性分区 | `meta/communities.json` |
|
||||
| 4 | **时效性管理** | 节点级 / 时间维度 | 突触代谢稳态 + 遗忘 | `meta/access_log.json` + `meta/archived.json` |
|
||||
| 5 | **健康监控** | 系统级 | 神经环路诊断 | 告警 / 严重告警 |
|
||||
|
||||
### 4.2 关键定性
|
||||
|
||||
- consolidate 是**纯只读 + 派生写**(读 workspace,写 `meta/` + `audit/`)
|
||||
- **唯一例外是 split** —— 改 workspace 的维护任务,但触发严格(D3 inline 写后)且只改自身负责的 parent + children
|
||||
- **关系判断有错率 → 报告优先,人/agent 介入,不主动合并**(夸大置信度的代价是污染事实层)
|
||||
- 离线 / 周期 / idle —— 与前台不抢资源;失败不影响主流程,下次重跑
|
||||
|
||||
详细机制见 `auto_consolidate_design.md`。
|
||||
|
||||
---
|
||||
|
||||
## 5. Stage 3 — auto-recall:网络 → 答案
|
||||
|
||||
**类比**:觉醒态的 cue-driven retrieval + pattern completion。脑接到 query,激活相关皮层模式,补全成完整答案;同时召回过程本身强化被用到的记忆痕迹。
|
||||
|
||||
**根本目的**:接到当前 query 时,从 workspace + 派生信号合成最相关的过去经验 —— 既要**覆盖率**(不漏)也要**信噪比**(不冗余)。
|
||||
|
||||
### 5.1 五个能力维度
|
||||
|
||||
按召回流程从输入到输出:
|
||||
|
||||
| # | 能力 | 它在解决什么 |
|
||||
|---|---|---|
|
||||
| 1 | **多路召回** | 不同问法走不同算子(state / semantic / topological 三分立);agent 自选,不强加聚合 verb |
|
||||
| 2 | **多信号融合** | 单一文本相似度不够 —— 还要节点权威性 / 主题集群 / 时效性;乘法融合 |
|
||||
| 3 | **信噪比管理** | 节点级去重 + 节点级 surface(frontmatter 一同呈现)+ multi-hop 可控展开 + 冷藏过滤 |
|
||||
| 4 | **召回反馈** | 被命中的节点 → 写访问日志 → 影响下次 recency / archived 判定 |
|
||||
| 5 | **鲁棒降级** | 派生信号缺失 → 退到基础召回;version 不兼容 → warning + 跳过该因子 |
|
||||
|
||||
### 5.2 关键定性
|
||||
|
||||
- recall 是**只读** —— 唯一对外写入是 `meta/access_log.json`(经 ring buffer + consolidate 聚合)
|
||||
- recall **不引入新 L4 模块**(`structure.md` ✗-15)—— 三种问法分别由 L3 原子工具(`list_step` / `search_step` / `traverse_step`)直接覆盖
|
||||
- 默认路径 **0 LLM 调用**(信号都是离线维护好的);LLM rerank / query rewrite 是 SDK 上层选项
|
||||
|
||||
详细机制见 `auto_recall_design.md`。
|
||||
|
||||
---
|
||||
|
||||
## 6. 能力地图(横切视角)
|
||||
|
||||
15 维按"作用对象"重排,可以看到三阶段如何分工:
|
||||
|
||||
| 作用对象 | dream(写入) | consolidate(巩固)| recall(检索)|
|
||||
|---|---|---|---|
|
||||
| **节点(单个)** | 1 抽象判断 / 3 整合决策 / 5 写入安全 | 1 结构维护(split) | 3 信噪比(节点级合并/surface) |
|
||||
| **节点对 / 关系** | 4 关系编织(wikilink) | 2 跨节点关系发现(dups 报告) | (消费已有边,不产新关系) |
|
||||
| **子图 / 集群** | 2 经验重放(召回邻居) | 3 主题集群形成(community)| 2 多信号融合(community boost) |
|
||||
| **时间维度** | (写入瞬间) | 4 时效性管理(decay / archived)| 4 召回反馈(access log)|
|
||||
| **系统健康** | 5 守恒校验 | 5 健康监控(D1 / D10) | 5 鲁棒降级 |
|
||||
| **入口形态** | 异步 fan-out per sub-unit | 周期 batch / idle | 同步 query response |
|
||||
|
||||
**几个观察**:
|
||||
- "节点对 / 关系"列在 recall 是空 —— recall 不产新关系,只用已有边(避免 query-time 高成本推断)
|
||||
- "时间维度"行 dream 缺位 —— 写入瞬间无"时间维度"概念(那是 consolidate 后续才能提取的统计)
|
||||
- 每行至少有一个阶段负责 —— 没有能力被全阶段忽略
|
||||
|
||||
---
|
||||
|
||||
## 7. 跨阶段不变量
|
||||
|
||||
所有阶段共同遵守的硬约束。任何阶段越界 = 设计错误。
|
||||
|
||||
### 7.1 F-invariants(继承 `auto_dream_design.md` §4.3)
|
||||
|
||||
| # | 约束 | 跨阶段含义 |
|
||||
|---|---|---|
|
||||
| F-1 | 0 文件移动 | 没有任何阶段可以 move 文件;rename 走 `wikilink_handler.retarget_links` 显式路径 |
|
||||
| F-2 | 改正文限定 subject | dream 改 subject body / consolidate split 改 parent + children body;**recall 绝不改任何 body** |
|
||||
| F-3 | maintainer 只做 split | consolidate 内的结构维护只做 split;无 merge / dissolve / re-edge |
|
||||
| F-10 | inbound 不动 | split 后外部 wikilink 仍指 parent,不强制重定向 |
|
||||
| F-11 | wikilink 是 body 一部分 | 没有"独立的边";所有关系变化是 body 编辑副作用 |
|
||||
|
||||
### 7.2 E-invariants(边守恒)
|
||||
|
||||
- E-1:dream update 出边 ⊇ 原出边
|
||||
- E-2:split 后 `(parent_new ∪ ∪children_outbound) ⊇ parent_old`
|
||||
- E-3:inbound wikilink split 时不动
|
||||
|
||||
**recall 不写 body** → E-* 与之无关;但 recall 看到的 wikilink 图永远是 dream / split 守恒后的状态。
|
||||
|
||||
### 7.3 派生信号边界
|
||||
|
||||
- **consolidate / recall 不写 workspace** —— 关系判断、活跃度统计、社区划分都是概率推断,不污染事实层
|
||||
- **`meta/*.json` 不被 retrieve 召回** —— 只作权重信号,不进入"召回结果"集合
|
||||
- **audit/ 不被自动消费** —— 报告永远等待人 / agent 介入,不闭环回写
|
||||
|
||||
---
|
||||
|
||||
## 8. 跨阶段数据流(契约总览)
|
||||
|
||||
```
|
||||
┌──────────────┐ wikilink ┌──────────────┐
|
||||
│ auto-dream │─落 body──►│ workspace/ │
|
||||
│ (Stage 1) │ │ (事实层) │
|
||||
└──────────────┘ └──────┬──────┘
|
||||
│ 只读
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ auto-consolidate │
|
||||
│ (Stage 2) │
|
||||
└─┬────────┬───────┘
|
||||
│ │
|
||||
meta/ 元数据───┘ └─── audit/ 报告
|
||||
(派生层) (人工介入)
|
||||
│
|
||||
│ 只读
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ auto-recall │ ◄─ user query
|
||||
│ (Stage 3) │
|
||||
└──────┬───────┘
|
||||
│ 命中钩子(异步)
|
||||
▼
|
||||
meta/access_log.json
|
||||
(recall 唯一对外写入,经 consolidate 聚合)
|
||||
```
|
||||
|
||||
| 产物 | 路径 | 写入者 | 读取者 | 缺失行为 |
|
||||
|---|---|---|---|---|
|
||||
| **workspace wikilink** | `digest/**.md` body | dream / split | recall(图遍历) | — |
|
||||
| **dups 报告** | `audit/<date>/auto_link_dups.md` | consolidate | 人 / agent | — |
|
||||
| **communities** | `meta/communities.json` | consolidate | recall | 不做同社区 boost |
|
||||
| **access log** | `meta/access_log.json` | recall(写命中) + consolidate(聚合) | recall(读 recency)| recency_factor = 1.0 |
|
||||
| **archived list** | `meta/archived.json` | consolidate | recall(默认过滤)| 不过滤 |
|
||||
| **centrality** | `file_graph` 反向索引(实时,不存)| 自动 | recall(O(1) 查) | — |
|
||||
|
||||
**契约稳定性**:`meta/*.json` 都带 `version` + `computed_at`;recall 启动时校验 version,不兼容则降级。
|
||||
|
||||
**冷启动**:`meta/` 为空 → recall 仍能跑(base + centrality + 图)→ 排序略弱不崩。
|
||||
|
||||
---
|
||||
|
||||
## 9. 系统级断言(把"要什么"提炼到 5 条)
|
||||
|
||||
1. **抽象与事实分层** —— workspace 是经 LLM 写过的事实;`meta/` 是统计 / 算法的派生;两者绝不混同
|
||||
|
||||
2. **关系建立的时机集中在写入瞬间** —— dream 写入是关系唯一可信来源;consolidate 不补 workspace 关系,recall 不预存关系矩阵
|
||||
|
||||
3. **维护是离线的派生劳动,不是补救** —— consolidate 不修 dream 的疏漏(那叫返工),它做的是 dream 不擅长的事(全局视角 / 统计视角 / 时间视角)
|
||||
|
||||
4. **检索是融合,不是检索** —— recall 的价值不在"找文本相似",而在"把文本 / 图 / 时效 / 权威多个独立信号合成一个答案"
|
||||
|
||||
5. **整个心智循环可降级** —— 任一阶段失效或失准,整个系统降级而不崩;冷启动有意义;dogfooding 可演进
|
||||
|
||||
---
|
||||
|
||||
## 10. 与 auto-memory 的边界
|
||||
|
||||
auto-memory 写入的 daily event 节点也是图的一部分(承载 daily → digest 的 `derived_from::` 边)。但 daily 节点**不参与 cognition 三阶段的全部改造**:
|
||||
|
||||
| cognition 阶段 | 是否触及 daily |
|
||||
|---|---|
|
||||
| **dream** | 只读(作为入流之一) |
|
||||
| **consolidate** | 不参与 dups / community / decay(daily 是时间索引,本质不去重 / 不冷藏) |
|
||||
| **recall** | 三层并行召回时 daily 也参与命中(`structure.md` R-2 默认 `digest > daily > resource`) |
|
||||
|
||||
**关键约束**:cognition 三阶段任何子阶段都**不改写 daily**(无写回路径);daily 由 auto-memory 写完即只读。
|
||||
|
||||
---
|
||||
|
||||
## 11. 演进 / 待补
|
||||
|
||||
**当前实现状态**:
|
||||
- ✅ Stage 1 dream 已实现并跑通(`reme/steps/evolve/dream.py` + `dream.yaml`)
|
||||
- ⏳ Stage 2 consolidate split 部分将实现;dups / community / decay / archived 待实现
|
||||
- ⏳ Stage 3 recall 增强未实现(当前 search.py 已有 vector + keyword + RRF + 一跳 expand)
|
||||
|
||||
**顶层级演进议题**(不属任何单阶段):
|
||||
- ⏳ **能力成熟度路标** —— 把 15 个能力维度按 M0(必须)/ M1(期望)/ M2(演进)分级
|
||||
- ⏳ **跨阶段集成测试** —— workspace 从空到充实的端到端 dogfooding,验证三阶段配合是否符合"心智循环"预期
|
||||
- ⏳ **可观测性聚合** —— 三阶段各自的 audit / log 现在分散;是否需要统一的 cognition 健康面板
|
||||
|
||||
各阶段实现进度详见各自文档的"下一步"章节。
|
||||
|
|
@ -1,741 +0,0 @@
|
|||
# auto-consolidate 设计(Stage 2 巩固:主动解决 workspace 长期演化的实际问题)
|
||||
|
||||
> 本文档:reme 中 **auto-cognition 三阶段** 的 **Stage 2 — 巩固阶段** 实现。覆盖 workspace 长期演化中累积的实际问题(冗余 / 过载 / 稀疏 / 腐败 / 抽象缺位),通过周期 batch + 写后 inline 的方式**主动改 workspace**,让记忆系统保持健康。
|
||||
>
|
||||
> 配套阅读:
|
||||
> - `auto_cognition_design.md`:三阶段顶层心智循环
|
||||
> - `auto_dream_design.md`:Stage 1 写入 / 节点 + 边模型 / F-invariants 原始定义 / 边守恒
|
||||
> - `auto_recall_design.md`:Stage 3 检索 —— 消费本文档产出的信号
|
||||
> - `auto_memory_design.md`:auto-memory 写 daily,daily 节点不参与本文档的巩固改造
|
||||
> - `structure.md` §3.6(maintain 动作语义)
|
||||
>
|
||||
> **核心立场**:
|
||||
> - consolidate **不是产报告等人介入**,是**主动解决问题** —— 类比 NREM 慢波睡眠的 systems consolidation:跨多事件抽 schema、修剪弱连接、稳态突触强度。这些都是真实发生的改造
|
||||
> - workspace **会被 consolidate 改**,但每个动作有严格的**置信度门槛 + 守恒规则 + 审计 trail + 渐进 rollout**
|
||||
> - 灰色地带(置信度不够)才产报告等人介入;高置信度自己解决
|
||||
> - **community detection 是巩固的中枢** —— P0 基础设施,P1-P3 三个动作(abstract / merge / reinforce)都依赖它
|
||||
|
||||
---
|
||||
|
||||
## 0. 问题陈述与五大动作全景
|
||||
|
||||
dream 写入是单点视角,有三类视野局限:**写入瞬间没有跨节点视角 / 跨时间视角 / 全局拓扑视角**。这些局限会让 workspace 长期演化中累积五类实际问题:
|
||||
|
||||
| # | 问题 | 类比 | 表现 | 解决 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | **冗余** | 同事件留下重复记忆痕迹 | dream 漏判去重 / 术语演化 / 跨桶建成两份 | merge |
|
||||
| 2 | **过载** | 单一突触表征过密 | 节点 body 累积过长 / 单节点杂糅多主题 | split |
|
||||
| 3 | **稀疏** | 应有连接未建立 | dream 写入瞬间漏召回的相关节点 / 反复共现但无 wikilink | reinforce |
|
||||
| 4 | **腐败** | 长期不激活的痕迹 | 旧节点过时 / 半年没人读 / 内容已被矛盾 | archive |
|
||||
| 5 | **抽象缺位** | 跨多 instance 缺 schema | workspace 只有原子节点,没有"主题层"视角承接全局问 | abstract |
|
||||
|
||||
### 0.1 四大动作 + 优先级
|
||||
|
||||
| 优先级 | 动作 | 解决问题 | 触发节奏 | 改 workspace | 风险 | 收益 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| **P0** | **community detection** | (基础设施) | weekly batch | 否 | 0(只产 meta) | 基础(下游动作的依据)|
|
||||
| **P1** | **abstract** | 抽象缺位 | weekly batch(基于 P0) | 是(新建 summary) | 低(additive) | **最高**(GraphRAG 核心) |
|
||||
| **P2** | **merge** | 冗余 | weekly batch(基于 P0) | 是(合并 + retarget) | 高(lossy) | 中(消除可见冗余) |
|
||||
| **(独立)** | **split** | 过载 | inline 写后(D3) | 是(拆 parent + children) | 低 | 中 |
|
||||
| **(独立)** | **archive** | 腐败 | daily batch | 软(meta 标记) | 0 | 中 |
|
||||
| ~~P3 reinforce~~ | **已并入 dream synapse** | 稀疏 wikilink | 由 dream Phase 2 step 4 织突触承担 | (不在 consolidate 范围内) | — | — |
|
||||
|
||||
**关键论断**:
|
||||
- **P1 比 P2 优先** —— abstract additive 失败可逆且回报最大;merge lossy 失败要回滚 inbound,价值是消除冗余(必要但不增能力)。
|
||||
- **reinforce 已取消**(2026-06-02)—— 详 §4 标作废说明;wikilink 稀疏的解决方案是 dream Phase 2 在写入瞬间多召回 + 织突触(详 `auto_dream_design.md` §4.2.2),不再由 consolidate 周期补救。
|
||||
|
||||
### 0.2 实施路径
|
||||
|
||||
```
|
||||
M0: P0 community detection (基础设施)
|
||||
+ split (已实现)
|
||||
+ archive (软标记,完全可逆)
|
||||
|
||||
M1.1: P1 abstract (additive,最低风险开始改 workspace)
|
||||
M1.2: P2 merge (lossy,高门槛 + 多数票)
|
||||
|
||||
M2+: 多层 abstract (L2 super-community) / delete
|
||||
|
||||
reinforce: 不再排期 —— 已由 dream Phase 2 synapse 织突触承担
|
||||
```
|
||||
|
||||
### 0.3 显式排除
|
||||
|
||||
- ❌ 重做"抽象判断" —— gate 决策只在 dream(consolidate 不重新判定"该不该记")
|
||||
- ❌ 重做"语义内容" —— UPDATE 三种 flavor(CORROBORATE / REFINE / CORRECT)只在 dream;consolidate 做结构层,不做语义层
|
||||
- ❌ 改 daily / resource —— consolidate 只动 digest 节点(I-2 / I-3 仍守)
|
||||
|
||||
---
|
||||
|
||||
# Part A — community 工作群(本文档核心)
|
||||
|
||||
P0-P3 四件套围绕 community detection 协同工作:**community 提供"哪些节点同主题"的判据,abstract / merge / reinforce 各自利用这个判据做不同的解决动作**。
|
||||
|
||||
## 1. community detection(P0,基础设施)
|
||||
|
||||
**目的**:在 workspace wikilink 图上做 community detection,产出"节点 → community_id"映射。这是 P1-P3 三个动作的**唯一前置**。
|
||||
|
||||
### 1.1 算法选择:Leiden
|
||||
|
||||
| 选项 | 评估 |
|
||||
|---|---|
|
||||
| Louvain | 经典,但有 resolution limit + disconnected community 风险 |
|
||||
| **Leiden** ✅ | Louvain 改进版(2019),稳定性显著好;GraphRAG 采用;Python `igraph.community_leiden` 现成 |
|
||||
| label propagation | 实现最简,但结果不稳定(随机种子敏感) |
|
||||
|
||||
**首版决策:Leiden**,直接对齐 GraphRAG 路线,后续接它的多层抽象更顺。
|
||||
|
||||
### 1.2 图的形态
|
||||
|
||||
| 维度 | 决策 |
|
||||
|---|---|
|
||||
| **节点范围** | **只 digest 节点**;daily / resource 不参与 |
|
||||
| **边权重** | **首版 unweighted undirected**(所有 wikilink 等权)—— 加权方案(predicate 类型加权)留 M2+ 视效果 |
|
||||
| **跨桶 community** | **必须允许** —— bucket 是物理归档,community 是语义聚合,二者本就正交。"错桶节点"会被自然纳入 community,可作 audit 信号但不强制 move(F-1 守住)|
|
||||
| **resolution** | **1.0 起步**(Leiden 默认 / GraphRAG 默认)—— dogfooding 后视 community 平均规模(理想 5-15 节点)调 |
|
||||
| **更新模式** | **全量重算**;workspace 千节点级 Leiden < 1 秒,M0/M1 不引入增量复杂度 |
|
||||
|
||||
### 1.3 多层级:M1 只 L1
|
||||
|
||||
| 层数 | 适用 | reme 决策 |
|
||||
|---|---|---|
|
||||
| 单层 L1(原子 → community)| workspace < 500 节点足够 | **M1 起步** |
|
||||
| 双层 L1 + L2(community → super-community) | workspace > 500 节点 / 跨主题大类涌现 | M2+ 视规模 |
|
||||
| GraphRAG 4 层 | 大规模文档库 | M3+ 不优先 |
|
||||
|
||||
理由:GraphRAG 论文证明 L1 拿走 60-80% 效果。先把 L1 跑稳,L2 看实际是否需要。
|
||||
|
||||
### 1.4 输出
|
||||
|
||||
**`meta/communities.json`**:
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"computed_at": "2026-06-08T03:00:00Z",
|
||||
"algorithm": "leiden",
|
||||
"resolution": 1.0,
|
||||
"communities": {
|
||||
"digest/auth/jwt-rotation.md": "c_07",
|
||||
"digest/auth/oauth-flow.md": "c_07",
|
||||
"digest/api/rate-limit.md": "c_12"
|
||||
},
|
||||
"stats": {
|
||||
"n_communities": 14,
|
||||
"median_size": 7,
|
||||
"max_size": 23
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**`meta/community_changes.json`**(供 abstract 稳定度判据):
|
||||
```json
|
||||
{
|
||||
"computed_at": "...",
|
||||
"previous": "...",
|
||||
"stability_per_community": {
|
||||
"c_07": 0.92, // 1 - (Jaccard 距离与上周该 community 节点集)
|
||||
"c_12": 0.45 // 不稳定,abstract 跳过
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.5 community_id 不需要稳定
|
||||
|
||||
下游(abstract / merge / reinforce)只关心"两节点是否同 community";id 本身可重排。每周重算后 id 不需要保持与上周对齐。stability 信号通过节点集 Jaccard 距离计算,不依赖 id。
|
||||
|
||||
### 1.6 用途总览
|
||||
|
||||
| 下游 | 用法 |
|
||||
|---|---|
|
||||
| **abstract**(§2)| 判据"该 community 节点数 ≥ N + 稳定度满足 + 无 hub" → 创建 summary |
|
||||
| **merge**(§3)| 候选 pair 必须在同 community(降错率;不同 community 的相似 description 多是同名异义)|
|
||||
| **reinforce**(§4)| 候选 wikilink 必须在同 community(避免假关联)|
|
||||
| **recall**(`auto_recall_design.md` §3) | 同 community 节点 boost |
|
||||
|
||||
---
|
||||
|
||||
## 2. abstract(P1,抽象提升)
|
||||
|
||||
**类比**:NREM systems consolidation —— 跨多次睡眠把分散事件抽出共同 schema,从 episodic 升到 semantic。
|
||||
|
||||
**目的**:workspace 演化到一定规模后,某些 community 形成稳定主题群,需要一个 hub 节点统领,让 retrieve 能召回到"主题概览"而非散点。
|
||||
|
||||
### 2.1 等价处理立场(关键)
|
||||
|
||||
**summary 节点完全等同普通节点**:
|
||||
|
||||
| 维度 | 决策 |
|
||||
|---|---|
|
||||
| **路径** | LLM 选桶,正常 slug 命名(如 `digest/auth/authentication-mechanisms.md`);**无 `__community__` / `__hub__` 等结构性标识** |
|
||||
| **frontmatter** | 仅 `name + description`(reme 核心保留);**无 `kind: community_summary`、无 `auto_generated`** |
|
||||
| **summary 性质** | 完全体现在 **body 形态** —— 主题概述 + 列出 source 节点 wikilink + 跨节点 pattern;但这是内容自然形态,不是结构性宣告 |
|
||||
| **后续维护** | **无** —— 跟其它节点等价,被 dream / split / merge / archive 自然演化(参见 §2.6) |
|
||||
|
||||
这跟 dream 的核心立场对齐:"节点角色由 body 内容决定,不由 frontmatter 类型标记"。abstract 是"用一种新方式创造节点",不是"创造一种新节点类型"。
|
||||
|
||||
### 2.2 触发判据(组合门槛)
|
||||
|
||||
```
|
||||
weekly batch:
|
||||
for community in communities.json:
|
||||
if community_has_hub(community): # §2.5 结构化判据
|
||||
continue
|
||||
if len(community) < MIN_NODES (5): # 节点数门槛
|
||||
continue
|
||||
if stability(community) < 0.7: # 稳定度门槛
|
||||
continue
|
||||
if active_node_count(community, 30d) < 3: # 活跃度门槛
|
||||
continue
|
||||
if name_diversity(community) < 0.5: # 多样性门槛
|
||||
continue
|
||||
→ enqueue abstract job
|
||||
```
|
||||
|
||||
| 门槛 | 默认 | 含义 | 防的是 |
|
||||
|---|---|---|---|
|
||||
| **节点数** | ≥ 5 | community 大小 | 给 2-3 节点造 hub 不划算 |
|
||||
| **稳定度** | ≥ 0.7 | 与上周边界 Jaccard 距离 | 给短命 community 造 hub 浪费 |
|
||||
| **活跃度** | ≥ 3 节点近 30 天 hit | community 仍在用 | 给死社区造 hub(下次没人看)|
|
||||
| **多样性** | name 差异度 ≥ 0.5 | frontmatter `name` 互不相同 | 给"一组重复节点"造 summary —— 那是 merge 的事 |
|
||||
|
||||
### 2.3 创建动作 + grounding 守恒
|
||||
|
||||
```
|
||||
LLM 看 community 内所有节点 (frontmatter + body)
|
||||
↓
|
||||
产 planned summary body (三段):
|
||||
1. 主题概述 (1-2 段,跨多节点共同主题)
|
||||
2. 关键支柱 (列表,3-5 节点 + 一句话 + wikilink)
|
||||
3. 不在概览的细节 (明说哪些细节留原节点)
|
||||
↓
|
||||
长度限制: summary body < 1500 token
|
||||
(防 abstract 创建后立刻被 split 触发,§5)
|
||||
↓
|
||||
LLM 决定 path: digest/<bucket>/<slug>.md
|
||||
↓
|
||||
CAS 写入 (§9) + 双重守恒校验:
|
||||
- 机械: 出边集合 ⊇ "关键支柱"声称引用的节点 (防套话)
|
||||
- 机械: 出边集合 ⊇ source_nodes 的至少 60% (allow LLM 漏列少数)
|
||||
↓
|
||||
audit 记录: audit/<date>/consolidate_actions.md
|
||||
```
|
||||
|
||||
**grounding 守恒**:summary body 中**声称引用某节点必须真写 wikilink**。LLM 不能仅口头提及"我们在 X 中看到..."而不带 `[[X.md]]`。这是机械可校验的,LLM 跑不掉。
|
||||
|
||||
### 2.4 长度限制为什么重要
|
||||
|
||||
summary body < 1500 token 是**与 split 互锁的机制**:
|
||||
|
||||
- 不限长 → LLM 会写"完整覆盖" → 最终 body 累积接近 split 阈值(2000 token)→ 下次 D3 触发拆 → 拆出来的 children 又被 community 视为同主题 → 下次 abstract 又造一个 hub → 循环
|
||||
- 限长 1500 → summary 留出 split 阈值的 25% buffer,稳定不触发拆
|
||||
|
||||
### 2.5 "community 已有 hub"的结构化判据
|
||||
|
||||
不靠 frontmatter / 路径标识,靠**结构**:
|
||||
|
||||
```
|
||||
def community_has_hub(community):
|
||||
for node in community:
|
||||
out_targets = outbound(node) ∩ community
|
||||
if len(out_targets) / len(community) >= 0.6:
|
||||
return True # 该节点出边覆盖 community 60% 以上 → 它已是 hub
|
||||
return False
|
||||
```
|
||||
|
||||
**好处**:
|
||||
- split parent overview 自然被识别为 hub(split parent 出边覆盖大部分 children)→ abstract **复用** split 的工作,不重复创建
|
||||
- 已有 abstract 创建过的节点,只要它出边没退化,下次 batch 自然识别为 hub,不重复创建
|
||||
- 节点被 dream update 后形态变化,出边变了 → 自动重新评估
|
||||
|
||||
**M1 实施关键验证点**:跑实测验证这个涌现 —— split parent 是否真被识别为 hub。如有 corner case,调阈值 0.6 → 0.5 / 0.7。
|
||||
|
||||
### 2.6 后续维护:无 —— 完全靠 5 大动作演化
|
||||
|
||||
abstract 创建即放归 workspace,**consolidate 不再"管"它**。后续命运:
|
||||
|
||||
| 演化路径 | 结果 |
|
||||
|---|---|
|
||||
| 新材料触及该主题 | dream update 自然修正 body(走 CORROBORATE / REFINE / CORRECT)|
|
||||
| 老 summary 长期不被引用 | archive 自动归档(§6)|
|
||||
| community 边界变了 → 下次 batch 创建新 summary | 新老 summary 描述同主题 → merge 自动合并(§3)|
|
||||
| summary body 累积过长 | split 自动拆(§5)|
|
||||
|
||||
这是真正的"workspace 自我代谢"。**没有特殊维护通道**。
|
||||
|
||||
---
|
||||
|
||||
## 3. merge(P2,同概念合并)
|
||||
|
||||
**类比**:NREM 跨多次睡眠识别"同一件事" → 合一个记忆痕迹。
|
||||
|
||||
**目的**:消除 workspace 内的冗余 —— 同概念多节点。
|
||||
|
||||
### 3.1 候选挖掘(community 内三层过滤)
|
||||
|
||||
```
|
||||
weekly batch (依赖 community detection):
|
||||
for community in communities:
|
||||
pairs = all_pairs(community)
|
||||
for (A, B) in pairs:
|
||||
if description_sim(A, B) < 0.6: # 第一层: frontmatter 相似
|
||||
continue
|
||||
if body_topic_overlap(A, B) < 0.5: # 第二层: body 主题词重合
|
||||
continue
|
||||
if cooldown_active(A) or cooldown_active(B): # 第三层: cooldown 检查
|
||||
continue
|
||||
candidates.append((A, B))
|
||||
```
|
||||
|
||||
**关键约束**:候选必须在**同 community**(降错率)。
|
||||
|
||||
### 3.2 多数票决策
|
||||
|
||||
merge 是高风险动作(lossy + 改 inbound),用多数票降错:
|
||||
|
||||
```
|
||||
for (A, B) in candidates:
|
||||
votes = parallel_run(N=3, prompt="A 和 B 是否同一概念? 返回 {is_same, confidence}")
|
||||
agree = sum(v.is_same and v.confidence >= 0.8 for v in votes)
|
||||
if agree >= 2:
|
||||
→ enqueue merge job
|
||||
elif agree == 1:
|
||||
→ 写 audit/<date>/dups_uncertain.md (灰色地带,人介入)
|
||||
else:
|
||||
→ 丢弃
|
||||
```
|
||||
|
||||
### 3.3 merge 动作:body 重写归 consolidate(方案 B)
|
||||
|
||||
**关键决策**:merge 后的 body 由 **consolidate 自跑合并 prompt**,不走 dream update 路径。
|
||||
|
||||
| 方案 | 评估 | 决策 |
|
||||
|---|---|---|
|
||||
| A. 走 dream update 路径(把 loser body 作"新材料")| 优雅但跨阶段;dream 不应知道 caller 是 consolidate 还是新材料 | ❌ |
|
||||
| **B. consolidate 自跑合并 prompt** | 简单自包含;通过严格 prompt 约束化解"做语义工作"张力 | ✅ |
|
||||
| C. 不重写 body(留 redirect stub) | 完全不做语义,但 workspace 留无用节点 | ❌ |
|
||||
|
||||
**B 方案的边界守住**(避免 consolidate 真在做语义判断):
|
||||
|
||||
| 边界 | 含义 |
|
||||
|---|---|
|
||||
| **prompt 严格约束** | "只合并不精化" —— 不重写措辞、不加新内容、不做精化决策 |
|
||||
| **机械守恒** | 出边 ⊇ A.outbound ∪ B.outbound + provenance 全保留(LLM 跑不掉) |
|
||||
| **信息守恒抽样** | LLM 自检 "merged.body ⊇ A.body ∪ B.body 全部信息";audit 抽样人审 |
|
||||
| **失败拒写** | 守恒校验失败 → LLM 重试一次 → 二次失败拒写 + audit |
|
||||
|
||||
### 3.4 完整动作流
|
||||
|
||||
```
|
||||
A, B → 选择 winner (path):
|
||||
- inbound 数大者赢 (保护既有 inbound,降 retarget 量)
|
||||
- 平局取路径短者
|
||||
↓
|
||||
LLM 跑 merge prompt → planned merged_body (B 方案)
|
||||
↓
|
||||
机械 retarget 准备:
|
||||
- 扫所有 inbound(loser): [[loser.md]] → [[winner.md]]
|
||||
- alias 保留;predicate 保留
|
||||
- 这是机械算子,非 LLM
|
||||
↓
|
||||
事务式 CAS 写入:
|
||||
1. winner body 改写
|
||||
2. 所有 inbound 节点 body 改写 (retarget)
|
||||
3. 删除 loser 文件
|
||||
任一步失败 → 全部回滚
|
||||
↓
|
||||
audit 记录 + cooldown 设置 (winner 进 cooldown 2 weeks)
|
||||
```
|
||||
|
||||
### 3.5 灰色地带:报告
|
||||
|
||||
- 多数票通过(agree ≥ 2)→ 自动 merge
|
||||
- 仅 1 票通过 → 写报告 `audit/<date>/dups_uncertain.md`,人 / agent 介入
|
||||
- 0 票 → 丢弃
|
||||
|
||||
报告格式:
|
||||
```markdown
|
||||
# dups uncertain 2026-06-08
|
||||
|
||||
## pair 1 (1/3 votes)
|
||||
- A: digest/auth/jwt-rotation.md ("JWT 密钥轮换")
|
||||
- B: digest/security/key-rotation.md ("密钥轮换原则")
|
||||
- vote 1 (yes, 0.85): "同一概念,A 偏 JWT 场景"
|
||||
- vote 2 (no, 0.72): "B 是通用原则,A 是具体应用"
|
||||
- vote 3 (no, 0.68): "粒度不同,不应合并"
|
||||
|
||||
建议:走 dream update 通道把 A 内容作为 B 的实例并入。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. ~~reinforce~~(**已作废,2026-06-02**)
|
||||
|
||||
> ⚠️ **本节作废,reinforce 已并入 dream Phase 2 synapse 织突触**(详 `auto_dream_design.md` §4.2.2)。理由:
|
||||
> - reinforce 的本质 = "找语义相关但 wikilink 缺失的节点对,补 wikilink"
|
||||
> - 但 dream Phase 2 在写入新节点瞬间已经在做同样的事(多召回 + 内化判 related + 织 `[[Y.md]]`)
|
||||
> - 让 consolidate 周期事后补 wikilink = dream RECALL 不充分的兜底,与其兜底不如把 dream 召回做强
|
||||
> - F-2 自然守住:dream 只动新节点 body(自己的 subject),不需要 consolidate 改 leaf body 这种 F-2 破例
|
||||
>
|
||||
> **新立场**:wikilink 的稀疏由 dream Phase 2 在写入瞬间一次性解决,workspace 不维护"事后周期补 wikilink"的通道(`auto_cognition_design.md` §9.2 立场:关系建立在写入瞬间)。详 `hierarchical_summary.md` §13.2 Q4。
|
||||
>
|
||||
> 以下保留原 reinforce 设计内容作为历史快照,**不实施**。
|
||||
|
||||
**(以下内容已作废,仅作历史快照)**
|
||||
|
||||
**类比**:NREM 突触强化 LTP —— 反复共激活的连接被强化。
|
||||
|
||||
**目的**:workspace 演化中,某些节点对应该有 wikilink 但 dream 写入时漏召。reinforce 周期检测并 additive 补。
|
||||
|
||||
### 4.1 候选挖掘(三层过滤)
|
||||
|
||||
```
|
||||
weekly batch (依赖 community detection):
|
||||
for community in communities:
|
||||
for (A, B) in all_pairs(community):
|
||||
if has_wikilink(A, B):
|
||||
continue
|
||||
# 第一层: 字符串 mention 锚点
|
||||
if not has_mention(A.body, B.frontmatter.name):
|
||||
continue
|
||||
# 第二层: embedding 相似度验证
|
||||
if embedding_sim(A.context_around_mention, B.body) < 0.7:
|
||||
continue
|
||||
# 第三层: 同 community (已经是,但显式说明)
|
||||
candidates.append((A, mention_pos, B))
|
||||
```
|
||||
|
||||
**三层过滤的角色**:
|
||||
|
||||
| 层 | 防的是 |
|
||||
|---|---|
|
||||
| 字符串 mention | 大幅降候选数(从 O(N²) 降到 O(实际共现)) |
|
||||
| embedding 相似度 | 防同名异义("Apple" 公司 vs 水果)|
|
||||
| 同 community | 防表面术语共现但语义无关 |
|
||||
|
||||
### 4.2 决策(单票即可,门槛较高)
|
||||
|
||||
reinforce 是 additive 低风险动作,不需要多数票:
|
||||
|
||||
```
|
||||
for (A, mention_pos, B) in candidates:
|
||||
vote = LLM("A.body 在该位置提到 B 的概念。是否合理加 [[B.md]] 链接?")
|
||||
if vote.confidence >= 0.85:
|
||||
additive_wikilink(A, mention_pos, target=B.path)
|
||||
→ CAS 写入 (E-1 自动满足:additive 只增不删)
|
||||
→ audit 记录
|
||||
else:
|
||||
丢弃
|
||||
```
|
||||
|
||||
### 4.3 边界
|
||||
|
||||
| 维度 | 决策 |
|
||||
|---|---|
|
||||
| **只 additive 加 wikilink** | 不改 body 文字,不升级 typed predicate(predicate 升级是语义判断,留 dream)|
|
||||
| **alias 保留原文** | `[[B.md\|<原文 mention>]]`;原文一字不改 |
|
||||
| **写入位置** | mention 第一次出现处加;后续保持原文(防 wikilink 满文) |
|
||||
| **不动 anchor** | 与 dream 一致 |
|
||||
| **守恒** | E-1 天然满足(纯增) |
|
||||
| **rollback** | 误链发生时,人 / agent 直接编辑 body 删除 wikilink 即可;reinforce 不维护"我加过哪些"audit log(每次动作进 `audit/<date>/consolidate_actions.md`)|
|
||||
|
||||
### 4.4 reinforce 与 dream 的边界
|
||||
|
||||
dream 写入时 LLM 应已尽力召回相关节点 + 加 wikilink。reinforce 是**周期性兜底** —— 写入瞬间漏的、术语后才一致的、被 split 拆出来后才相关的,在 reinforce batch 里被检出。
|
||||
|
||||
这不违反"consolidate 不修 dream 漏的"立场 —— **dream 漏的 wikilink 在巩固阶段补,是合法工作**(它的依据是 dream 单点视角永远做不到的"周期统计 + 全局视角");**dream 漏的语义抽象在巩固阶段不补**(那是 dream 的语义判断,consolidate 不重做)。
|
||||
|
||||
---
|
||||
|
||||
# Part B — 独立工作
|
||||
|
||||
P0-P3 围绕 community,这两个动作独立运行。
|
||||
|
||||
## 5. split(过载分化:inline 写后)
|
||||
|
||||
**类比**:海马表征过密 → 分化新单元。
|
||||
|
||||
**目的**:节点 body 累积过长 / 主题离散后,拆成 parent overview + N children,保持单节点"一个原子语义单元"的粒度。
|
||||
|
||||
### 5.1 触发模型(写后立即,inline)
|
||||
|
||||
split 是 5 大动作中**唯一 inline** 的 —— 跟 dream 写入流强耦合,不走 weekly batch:
|
||||
|
||||
```
|
||||
dream / split 写 body 成功 (CAS 通过)
|
||||
└─ if len(body) > T_token (default 2000):
|
||||
└─ LLM 判离散度
|
||||
└─ if is_overloaded:
|
||||
└─ enqueue split job (FIFO, CAS-protected)
|
||||
└─ return (不阻塞 dream)
|
||||
```
|
||||
|
||||
理由:节点过载是**写入瞬间的本地信号**(token + 离散度),延后无价值;反应即时。
|
||||
|
||||
### 5.2 split 动作
|
||||
|
||||
```
|
||||
LLM 看 parent body:
|
||||
- 拆成 1 个 parent overview body + N 个 children body
|
||||
- 每个 child 自带 [[parent]] 反向链接
|
||||
- inbound 不动 (F-10)
|
||||
↓
|
||||
机械 outbound 守恒校验 (E-2):
|
||||
(parent_new ∪ ∪children_outbound) ⊇ parent_old
|
||||
失败 → LLM 重试 → 二次失败拒写 + audit
|
||||
↓
|
||||
事务式 CAS 写入: parent body 改写 + N 个新 children 文件创建
|
||||
↓
|
||||
audit + cooldown 设置 (parent + children 进 cooldown,与 merge 互锁)
|
||||
```
|
||||
|
||||
### 5.3 split 与 abstract 的协同(关键)
|
||||
|
||||
| | 起源 | 方向 | 触发 |
|
||||
|---|---|---|---|
|
||||
| split overview | 单节点过载分化 | 自上而下(一拆多)| inline 写后 D3 |
|
||||
| abstract summary | 多节点抽象凝聚 | 自下而上(多归一)| weekly batch + 稳定度阈值 |
|
||||
|
||||
**协同**:split 产出的 overview 节点会被 §2.5 的"已有 hub"判据识别,abstract 不重复创建。两者互补,不冲突。
|
||||
|
||||
---
|
||||
|
||||
## 6. archive(时效衰减:让长期不激活的节点淡出)
|
||||
|
||||
**类比**:突触代谢稳态 —— 长期不用的连接被减弱,但不删除。
|
||||
|
||||
**目的**:让 retrieve 默认排除"已不活跃"的节点,提升信噪比;不删 workspace 文件,保持可逆。
|
||||
|
||||
### 6.1 recency_score:连续衰减信号
|
||||
|
||||
```
|
||||
recency_score(node) =
|
||||
exp(-(now - last_update) / τ_update) # 时间衰减
|
||||
× (1 + log(1 + last_hit_count_30d)) # 活跃度增强
|
||||
× (1 + log(1 + inbound_count) / SCALE) # 中心性 cushion(避免 hub 被冷藏)
|
||||
```
|
||||
|
||||
| 参数 | 默认 | 含义 |
|
||||
|---|---|---|
|
||||
| τ_update | 60 days | 时间衰减常数 |
|
||||
| SCALE | 10 | 中心性 cushion 缩放 |
|
||||
|
||||
输出:`meta/recency.json`,每节点 0.0~1.0 连续值。
|
||||
|
||||
### 6.2 archived 派生快照
|
||||
|
||||
archived 是 recency_score 的二元化派生:
|
||||
|
||||
```
|
||||
archived = {node | recency_score(node) < 0.15}
|
||||
```
|
||||
|
||||
输出:`meta/archived.json`,recall 默认过滤这个列表。
|
||||
|
||||
### 6.3 解冻
|
||||
|
||||
任何动作触及节点 → 自动从 archived 移除:
|
||||
- retrieve 命中(写 access_log)
|
||||
- dream update 触及
|
||||
- merge / reinforce 触及
|
||||
|
||||
下次 batch 时 recency_score 重算自然超过阈值。
|
||||
|
||||
### 6.4 daily 节奏
|
||||
|
||||
archive 是唯一不需要 community detection 的动作 → 节奏可以更快(daily batch),让冷启动后第二天就能影响 recall。
|
||||
|
||||
```
|
||||
daily batch:
|
||||
1. 读 access_log (retrieve / dream / consolidate 钩子记录的命中事件)
|
||||
2. 重算 recency_score for all digest nodes
|
||||
3. 输出 meta/recency.json
|
||||
4. 阈值过滤 → meta/archived.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Part C — 共享基础设施
|
||||
|
||||
## 7. F-invariants 松绑与守恒规则
|
||||
|
||||
旧 F-invariants(`auto_dream_design.md` §4.3)在"workspace 只读"立场下定义,新立场要松绑。但松绑不是"自由改",是用**动作级守恒规则**换"一刀切禁令"。
|
||||
|
||||
### 7.1 F-invariants 修订
|
||||
|
||||
| # | 旧约束 | 新立场 |
|
||||
|---|---|---|
|
||||
| **F-1** | 0 文件移动 | **改为**:"非 consolidate 动作不移动文件";merge 删除 loser 文件是**合法移动**(逻辑上等价 retarget) |
|
||||
| **F-2** | 改正文限定 subject | **改为**:"dream / split / reinforce 改 subject body;merge 在受控算子内可改 inbound 节点 body";其它阶段(recall)绝不改 |
|
||||
| **F-3** | maintainer 只做 split | **作废** —— consolidate 5 大动作合法 |
|
||||
| **F-10** | inbound 不动 | **改为**:"split 时 inbound 不动";merge 必须 retarget inbound(机械算子) |
|
||||
| **F-11** | wikilink 是 body 一部分 | **保留** —— 没有"独立的边"基础设施 |
|
||||
|
||||
### 7.2 动作级守恒规则矩阵
|
||||
|
||||
| 动作 | 置信度门槛 | 守恒规则 |
|
||||
|---|---|---|
|
||||
| **abstract** | community 节点 ≥ 5 + 稳定度 ≥ 0.7 + 活跃度 ≥ 3 + 多样性 ≥ 0.5 + 无 hub | 出边 ⊇ "关键支柱"列表 + 出边 ⊇ source 节点 60%(机械)|
|
||||
| **merge** | LLM 多数票 ≥ 2/3 + similarity ≥ 0.6 + body overlap ≥ 0.5 | 信息守恒(merged.body ⊇ A ∪ B)+ 出边 ⊇ A.out ∪ B.out + inbound 全 retarget(机械)|
|
||||
| **reinforce** | LLM 单票 ≥ 0.85 + 同 community + mention 锚点存在 + embedding ≥ 0.7 | E-1 天然(additive)|
|
||||
| **archive** | recency_score < 0.15 | 软标记,无破坏性 |
|
||||
| **split** | token > T + LLM 判离散 | E-2(parent ∪ children ⊇ parent_old)+ inbound 不动 |
|
||||
|
||||
---
|
||||
|
||||
## 8. cooldown 与防循环
|
||||
|
||||
5 大动作之间的潜在循环:
|
||||
|
||||
```
|
||||
A merge B → AB body 长 → split AB 回 A' + B' → 又 merge → ...
|
||||
```
|
||||
|
||||
防御:
|
||||
|
||||
| 互锁对 | 窗口 | 实现 |
|
||||
|---|---|---|
|
||||
| **split → merge** | 2 weeks | 刚 split 出的兄弟节点不参与 merge 候选 |
|
||||
| **merge → split** | 2 weeks | 刚 merge 的节点不参与 split 评估(D3 检测时跳过)|
|
||||
| **merge → merge**(同对反复) | 12 weeks | 同一 path 12 周内被 merge 又被识别为新 merge 候选 → audit 警报,人介入 |
|
||||
| **abstract → merge**(同主题反复 abstract) | 4 weeks | 刚 abstract 出的 hub 节点 4 周内不参与 merge 候选 |
|
||||
|
||||
cooldown 状态外置 `meta/cooldowns.json`,不污染 workspace。
|
||||
|
||||
---
|
||||
|
||||
## 9. CAS 写入协议(共享基础设施)
|
||||
|
||||
CAS 是 dream(`auto_dream_design.md` §4.2)、split / merge / reinforce / abstract(本文档)**多方共用**的 workspace 写入协议。归本文档因 consolidate 是写入主战场。
|
||||
|
||||
archive 不写 workspace → 不走 CAS;它写 `meta/`,各任务的 atomic write(write-temp + rename)即可。
|
||||
|
||||
### 9.1 协议
|
||||
|
||||
```
|
||||
1. 读 + 记戳: read body → version_stamp = sha256(body) | mtime
|
||||
2. 决策: LLM / 算法 → 产 planned new_body
|
||||
3. CAS 写入: 重读 body 比 version_stamp
|
||||
- 未变: 跑动作级守恒校验 → 通过 → atomic write (write-temp + rename) → done
|
||||
- 已变: 丢弃 planned new_body, 带最新 body 重走 step 1
|
||||
4. 守恒校验失败: LLM 重试一次, 二次失败拒写 + audit
|
||||
5. 重做次数上限: 3 次 → 跳过候选 + audit log
|
||||
```
|
||||
|
||||
### 9.2 事务式 merge / split 写入
|
||||
|
||||
merge 涉及多文件写入(winner body + N 个 inbound retarget + loser 删除);split 涉及多文件创建(parent body + N children)。需要事务语义:
|
||||
|
||||
- 准备阶段:全部 planned new_body 写到 temp 区(带 version_stamp)
|
||||
- 提交阶段:逐个 CAS 检查 + atomic write(write-temp + rename)
|
||||
- 任一 CAS 失败 → 全部回滚(temp 区清理,已 rename 的恢复)
|
||||
|
||||
实现细节:可借 fs-level 事务库(如 `pyrsistent` 模式)或自实现 journal。M0 起步用最简的"先全部检查 → 再全部写入"两阶段,接受窗口期(检查到写入间)的极小并发风险。
|
||||
|
||||
### 9.3 create 路径 race
|
||||
|
||||
merge / abstract 都可能并发 create 同一 path → atomic create(`O_CREAT | O_EXCL`)只让一个赢;输者 EEXIST → 重走 step 1(此时大概率改判 update 或丢弃)。
|
||||
|
||||
### 9.4 不解决
|
||||
|
||||
- 跨进程并发(多 reme 实例同 workspace)→ 不在 M0,需 fs lock(M1+)
|
||||
- 高冲突 workload(同候选反复触发)→ 重做上限触发后 audit
|
||||
|
||||
---
|
||||
|
||||
## 10. D 健康检查(D1 / D10)
|
||||
|
||||
不属"巩固"主语义,但跟 consolidate 同节奏(周期 batch 顺手跑),归本文档:
|
||||
|
||||
| # | 信号 | 节奏 | 修复策略 |
|
||||
|---|---|---|---|
|
||||
| **D1** | 断链(wikilink → 不存在 path) | 写时 inline + weekly batch 巡检(双重保险)| 就地删 wikilink 或保留 alias 文本 → audit |
|
||||
| **D10** | provenance 断裂(digest 反指的 daily/resource 不可达)| 同上 | I-不变量违反 → 严重告警 + 人介入 |
|
||||
|
||||
D1 / D10 不算 5 大动作之一(它们不解决"workspace 演化问题",只检测异常)。但它们的修复(就地删 wikilink)需要走 CAS,所以协议共享。
|
||||
|
||||
---
|
||||
|
||||
# Part D — 契约与实施
|
||||
|
||||
## 11. 维护 → 检索契约
|
||||
|
||||
5 大动作产物给 retrieve 消费(详细 retrieve 逻辑见 `auto_recall_design.md`):
|
||||
|
||||
| 产物 | 路径 | 写入者 | 读取者 | 缺失行为 |
|
||||
|---|---|---|---|---|
|
||||
| **workspace 节点变化** | `digest/**.md` | merge / split / reinforce / abstract | recall(图遍历 / 命中) | — |
|
||||
| **communities** | `meta/communities.json` | community detection | recall + abstract / merge / reinforce | 不做同社区 boost / 三个动作跳过 |
|
||||
| **community changes** | `meta/community_changes.json` | community detection | abstract 决策 | abstract 跳过(无稳定度判据)|
|
||||
| **recency** | `meta/recency.json` | archive daily batch | recall | recency_factor = 1.0 |
|
||||
| **archived** | `meta/archived.json` | archive daily batch | recall(默认过滤)| 不过滤 |
|
||||
| **cooldowns** | `meta/cooldowns.json` | split / merge | consolidate 内部 | 无防御循环 |
|
||||
| **access_log** | `meta/access_log.json` | recall(写命中) + archive(聚合) | archive(读 recency) | recency 不衰减 |
|
||||
| **dups uncertain** | `audit/<date>/dups_uncertain.md` | merge | 人 / agent | — |
|
||||
| **consolidate actions** | `audit/<date>/consolidate_actions.md` | 全部 5 动作 | 审计 | — |
|
||||
| **D1 / D10 健康** | `audit/<date>/health_*.md` | inline check + weekly | 人 / agent | — |
|
||||
|
||||
**契约稳定性**:`meta/*.json` 都带 `version` + `computed_at`;recall 启动时校验 version,不兼容则降级。
|
||||
|
||||
---
|
||||
|
||||
## 12. 与 dream 模型的引用关系
|
||||
|
||||
本文档松绑了部分 F-invariants(§7),但仍在 dream 定义的底层模型上工作:
|
||||
|
||||
| 引用 | 来源 |
|
||||
|---|---|
|
||||
| wikilink 基础语法 | `auto_dream_design.md` §3 |
|
||||
| 节点 / 边模型 | `auto_dream_design.md` §4 / §2 / §3 |
|
||||
| F-invariants 原始定义 | `auto_dream_design.md` §4.3(本文档 §7 修订)|
|
||||
| 边守恒 E-1 / E-2 / E-3 | `auto_dream_design.md` §4.4 |
|
||||
| 路径即 ID / rename | `auto_dream_design.md` §2 |
|
||||
| anchor 不引入 | `auto_dream_design.md` §3 |
|
||||
| provenance 载体形态 | `auto_dream_design.md` §4.2 |
|
||||
| dream 写入路径 | `auto_dream_design.md` §4.2 |
|
||||
|
||||
---
|
||||
|
||||
## 13. 下一步(M0 → M1.1 → M1.2 → M1.3 → M2)
|
||||
|
||||
实现进入 `reme/steps/consolidate/` 时,本文档与 `auto_dream_design.md` / `auto_cognition_design.md`(顶层)/ `auto_recall_design.md` 共同作为契约依据。
|
||||
|
||||
### M0:基础设施 + 完全可逆动作
|
||||
|
||||
- ✅ split inline 触发 + LLM 离散度判 + E-2 守恒(基础部分)
|
||||
- ⏳ **community detection weekly batch**(Leiden via `igraph`)+ `meta/communities.json` + `meta/community_changes.json`
|
||||
- ⏳ **archive daily batch** + recency_score + access_log 收集
|
||||
- ⏳ CAS 写入框架 + version_stamp + EEXIST race + 重做上限 + audit
|
||||
- ⏳ D1 / D10 写时 inline 检测 + weekly 巡检
|
||||
|
||||
### M1.1:abstract(P1,additive 最低风险)
|
||||
|
||||
- ⏳ abstract 候选挖掘(community 大小 + 稳定度 + 活跃度 + 多样性 + 无 hub 五重判据)
|
||||
- ⏳ abstract LLM prompt(三段输出 + 长度限制 1500 token)
|
||||
- ⏳ grounding 守恒校验(出边 ⊇ 关键支柱 + 出边 ⊇ source 60%)
|
||||
- ⏳ "已有 hub" 结构化判据(outbound 覆盖度 ≥ 60%)
|
||||
- ⏳ **关键验证点**:实测 split parent 是否被识别为 hub
|
||||
|
||||
### M1.2:merge(P2,lossy 高门槛)
|
||||
|
||||
- ⏳ 候选挖掘(community 内 description 相似 + body 重合 + cooldown 检查)
|
||||
- ⏳ 多数票框架(N=3 LLM,2/3 通过)
|
||||
- ⏳ merge prompt(B 方案:"只合并不精化")
|
||||
- ⏳ inbound retarget 机械算子(扫所有 `[[loser.md]]` → `[[winner.md]]`,alias / predicate 保留)
|
||||
- ⏳ 事务式多文件 CAS 写入
|
||||
- ⏳ 灰色地带报告(`audit/<date>/dups_uncertain.md`)
|
||||
- ⏳ cooldown 框架(`meta/cooldowns.json` + 各动作互锁)
|
||||
|
||||
### M1.3:reinforce(P3,价值最低,可缓做)
|
||||
|
||||
- ⏳ 候选挖掘(三层过滤:mention + embedding + 同 community)
|
||||
- ⏳ 单票决策(门槛 0.85)
|
||||
- ⏳ additive wikilink 写入(alias 保留原文)
|
||||
|
||||
### M2+:演进
|
||||
|
||||
- ⏳ 多层级 community(L2 super-community)+ L2 abstract
|
||||
- ⏳ delete(永久删除 workspace 文件)—— 视 dogfooding 效果决定是否开启
|
||||
- ⏳ predicate upgrade(typed link reinforce —— 当前 reinforce 只 additive 加无谓词)
|
||||
- ⏳ PageRank 替代 simple inbound count(若 retrieve 质量瓶颈在中心性)
|
||||
- ⏳ 跨进程并发(fs lock 支持多 reme 实例同 workspace)
|
||||
- ⏳ Leiden 边权重(按 predicate 类型加权)
|
||||
|
|
@ -1,352 +0,0 @@
|
|||
# auto-dream 设计(桶 / 节点 / 边 / 演化)
|
||||
|
||||
> 本文档:digest 沉淀层的**桶**(物理布局)/ **节点**(原子单元)/ **边**(wikilink)/ **演化**(dream create_or_update;split 归 maintain)。
|
||||
>
|
||||
> 配套阅读:
|
||||
> - `structure.md` §1.2(数据视角)/ §2(三层存储)/ §3.5(digest 动作)
|
||||
> - `auto_memory_design.md`:daily 实时事件 = dream 的入流之一
|
||||
> - `auto_consolidate_design.md`:M split / D 检测 / CAS 写入协议(dream 模型的运行时实现)
|
||||
> - `auto_cognition_design.md`:auto-cognition 三阶段顶层思想 —— dream 是其 Stage 1(写入阶段)的实现
|
||||
>
|
||||
> **核心**:digest = **浅桶(shallow bucket)+ flat .md** + **一张图(节点 + 边)**;dream 定义模型与主流程(create_or_update),maintain 负责 split / 写入运行时。
|
||||
>
|
||||
> **关键收敛**:digest 不分"逻辑层"。所有 .md 文件都是同一种节点,内容决定它扮演什么角色(主题概览 / 概念定义 / 方法描述 / 实体记录 ...)。"主题"从图中涌现,不是结构性宣告。
|
||||
|
||||
---
|
||||
|
||||
## 0. 问题陈述
|
||||
|
||||
digest 是 agent 长期记忆的"组织化沉淀"层,与三层架构的另两层职责互补:
|
||||
|
||||
| 层 | 组织主轴 | 形态 |
|
||||
|---|---|---|
|
||||
| resource/ | 时间(`<date>/<name>`) | 外部原始资料,不可变 |
|
||||
| daily/ | 时间 + 任务(`<date>/<event-slug>/`) | agent 任务过程,半可变 |
|
||||
| **digest/** | **语义** | **跨任务知识,可重组** |
|
||||
|
||||
dream 设计回答四个问题:**桶**怎么布局 / **节点**长什么样 / **边**怎么连 / **演化**谁负责怎么做。
|
||||
|
||||
---
|
||||
|
||||
## 1. 桶(物理布局)
|
||||
|
||||
| 维度 | 决策 |
|
||||
|---|---|
|
||||
| **物理几何** | `digest/<bucket>/<slug>.md`;**浅桶一层**(顶多两层),桶内 flat |
|
||||
| **bucket 角色** | **仅承担物理归档 + OS-level 浏览锚点**;不承担语义本体角色 —— 主题由图中节点表达 |
|
||||
| **bucket 集合** | **代码内 hard-coded**(`reme/steps/evolve/dream.py` 的 `BUCKETS` 常量),不通过配置外置,不由 dreamer / maintainer 动态生成 —— 三桶设定是 dream 模型本身的一部分(Phase 2 prompt 按 bucket 专化),不是可调参数 |
|
||||
| **集合视图** | 桶名内嵌在 prompt 中(extract 阶段三桶判别启发 + 三份独立 integrate prompt);不再生成独立 `_buckets.md` 视图 |
|
||||
| **初始化** | opinionated **三桶**,按"答什么问 + 谁在问"划分:`procedure`(答"怎么做 X" —— 步骤 / 方法 / runbook)/ `personal`(答"X 是谁 / 喜欢什么 / 不要做什么" —— 用户 / 团队 specific 身份 + 偏好)/ `wiki`(答"X 是什么 / 发生了什么 / 决策依据是什么" —— 通用知识 / 定义 / 原则 / 观察 / 决策先例;**也是默认兜底**) |
|
||||
| **bucket 主页** | 不强制存在;split 累积出层级时 parent 节点天然成为浏览主页(中心性涌现,非架构必需) |
|
||||
| **新节点归属** | bucket 由 **Phase 1** 在 unit 级别分配(写进 `MemoryUnit.bucket`),Phase 2 据此分发到对应 bucket 的专用 prompt;LLM 不能造新桶 |
|
||||
| **未归类节点** | Phase 1 找不到更明确归属时强制归入 `wiki` —— 它就是默认兜底,不是失败状态 |
|
||||
| **跨桶 move** | F-1 已禁止;若必须做(人工介入修错桶),走一次 `wikilink_handler.retarget_links(old, new)` |
|
||||
|
||||
**`wiki` 兜底桶**:
|
||||
|
||||
| 维度 | 内容 |
|
||||
|---|---|
|
||||
| **语义** | "通用知识 / 默认归属" —— `wiki` 在三桶中 scope 最广(定义 / 原则 / 观察 / 决策先例),Phase 1 没有更明确归属(不属于 `procedure` 的可执行流程,也不属于 `personal` 的用户 specific 偏好)时归入此桶;**是合法常态,不是故障状态** |
|
||||
| **路径** | `digest/wiki/<slug>.md`,与其它 bucket 完全等同;节点演化与其它桶一致 |
|
||||
| **错桶后续** | 不主动跨桶 move;若严重,人工 mv + `retarget_links(old, new)` |
|
||||
|
||||
**为什么 `wiki` 兜底,而不是另设 `unknown`**:三桶设计中 `procedure` / `personal` 都有明确语义边界,剩下的"X 是什么 / 决策依据 / 一般原则"自然落在通用知识那一边 —— 这恰好就是 `wiki` 的本职。再设独立 `unknown` 会出现两类语义重叠的兜底(`wiki` 的"通用知识" vs `unknown` 的"分类未定"),反倒让 LLM 在 Phase 1 多一道无意义的犹豫。`wiki` 节点本身就是合法常态,不需要后续清理。
|
||||
|
||||
**为什么是浅桶而不是深树**:
|
||||
- 物理浏览有"主题轮廓"(打开 `digest/wiki/` 能看到这一族节点),不像纯 flat 那样毫无锚点
|
||||
- 节点不被深路径绑死("属 wiki/auth 还是 wiki/session"这种归属焦虑被消解 —— 一个节点可以同时被多个主题通过 wikilink 引用)
|
||||
- F-1(0 文件移动)+ 平铺后,深树的核心收益(子树重组)消失,只剩深路径维护负担
|
||||
- **固定三桶的关键意义**:LLM 在 dream 桶决定时只做"分类"(三选一),不做"造类" —— 决策面坍缩,跨任务跨时间稳定;不会出现 "knowledge" / "wiki" / "concepts" 三个语义重叠的桶共存。三桶覆盖 personal-knowledge 的核心切片(做什么 / 谁喜欢什么 / 知识本身),进一步细分由桶内 wikilink 图自然涌现
|
||||
|
||||
**已排除**:动态扩桶 / 拒绝写入(候选丢失)/ 强行选最近似专属桶(本体污染) / 把 bucket 数推回 6+(决策面失控)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 节点
|
||||
|
||||
| 维度 | 决策 |
|
||||
|---|---|
|
||||
| **粒度** | atomic;一个 .md 文件 = 一个原子单元(概念 / 方法 / 实体 / 案例 / 原则 / 主题概览)|
|
||||
| **节点角色** | **由 body 内容决定,不由 frontmatter 类型标记**;同一节点扮演"主题概览"还是"具体方法",看它的 body 写了什么 |
|
||||
| **身份(ID)** | **workspace-relative 路径(含 `.md`)即节点身份** —— `digest/auth/jwt-rotation.md` |
|
||||
| **`name` frontmatter** | 文件名 basename(不含扩展名),与文件名同步 —— 检索 hint / 人读标签,**不当 ID 用** |
|
||||
| **frontmatter 保留字段** | 只有 `name` + `description`(reme 核心保留)|
|
||||
| **可选 `kind` 字段** | 例:concept / procedure / preference / observation / ...;**消费层 schema 提示**,reme 核心透明,不读它做结构决策。与 bucket 是不同概念 —— bucket 决定物理归档(三桶)+ Phase 2 prompt 走哪份;`kind` 是更细粒度的 frontmatter 标签,留给消费层自由使用 |
|
||||
| **文件名冲突** | 同 bucket 内文件名冲突 → 文件系统层断言(写入即拒);不需要独立检测信号 |
|
||||
| **rename** | 一次 `wikilink_handler.retarget_links(old_path, new_path)`(机制现成);无 alias 表,无透明展开 |
|
||||
|
||||
**为什么 atomic + 路径即 ID**:
|
||||
- **节点粒度 = retrieve 精度上限** —— semantic 检索召回 "一个原子单元" 远比召回 "一个 5000 字的主题文档" 信噪比高
|
||||
- **wikilink 在 atomic 粒度才真有意义** —— `[[digest/auth/jwt-rotation.md]]` 指向"一个具体方法"比指向"auth 主题文档"精确一个数量级
|
||||
- F-1 + 平铺 + 下层 immutable 后,slug abstraction 的核心价值(移动鲁棒性)蒸发;路径作 ID 与 `wikilink_handler.py` 默认形态完全对齐(*Recommended form: full path relative to the workspace with extension*)
|
||||
- provenance wikilink 反指 daily/resource 本来就用路径,统一后整个 workspace 一种 wikilink 形态
|
||||
|
||||
**"主题概览节点"靠内容识别,不靠前缀 / kind**:`hub__` / `topic__` 前缀**不存在**;文件名自然命名(`auth-fundamentals.md` / `jwt-rotation.md`)。主题概览身份是图位置(中心性 / split parent)+ body 形态共同涌现。
|
||||
|
||||
---
|
||||
|
||||
## 3. 边
|
||||
|
||||
参考实现:`reme/utils/wikilink_handler.py` + `reme/schema/file_link.py`。
|
||||
|
||||
| 形态 | 写法 | 说明 |
|
||||
|---|---|---|
|
||||
| **基础** | `[[<workspace-path>.md]]` | literal,不隐含 `.md`,不自动短链补全 |
|
||||
| **alias** | `[[path.md\|display-text]]` | rewrite 时 alias 保持 |
|
||||
| **image** | `![[image.png]]` | 资源引用,不是知识边 |
|
||||
| **可选谓词** | `predicate:: [[path.md]]`(行级)/ `[predicate:: [[path.md]]]`(内联) | Dataview 风格;谓词在 `[[]]` 外,`[[]]` 内只保留纯目标 |
|
||||
| **谓词标识符** | `[A-Za-z][A-Za-z0-9_]*`(`is_a` / `extends` / `causes` / `references` ...) | 词表**开放**,任意标识符 |
|
||||
| **未类型化合法** | 绝大多数 wikilink 不加 predicate;`predicate=None` 是默认 / 常态 | |
|
||||
| **边唯一性键** | `(target_path, predicate)` 二元组 | 同源同标不同 predicate = 不同边 |
|
||||
| **不引入 anchor** | digest 设计层不使用 `[[path.md#section]]` | `FileLink.target_anchor` schema 保留(供其它消费层),digest 层永远写 `None` |
|
||||
|
||||
**reme 核心对 predicate 的"透明"边界**(关键):
|
||||
- 横向 link/ retrieve 中心性 —— 都**聚合所有 predicate** 算,不分桶
|
||||
- 只有 edge 唯一性 / 反向索引会用到 predicate(否则 `[[A]]` 和 `is_a:: [[A]]` 会被当作同一条边互相覆盖)
|
||||
- 消费层若要按 predicate 做更精细的推理(如"taxonomic 路径只走 `is_a` 边"),自己读 `FileLink.predicate` 即可
|
||||
|
||||
**与 `kind` 一致的立场**(与 [[reme_schema_layering]] 对齐):reme 核心**只有节点 + 边两种结构类型**;`kind` / `predicate` 都是内容标签,绝不参与"hub / topic / leaf"这类结构角色判断。
|
||||
|
||||
**为什么不引入 anchor**:LLM 想"指向具体子主题"时,**正确做法是让那个子主题升级为独立节点**(必要时通过 split),不在过载 parent 内部用 anchor 凑合。anchor 在 digest 层无语义;prompt 必须明确告知 LLM 写 wikilink 时不带 `#section`。
|
||||
|
||||
---
|
||||
|
||||
## 4. 演化
|
||||
|
||||
### 4.1 演化只做两件事
|
||||
|
||||
| op | 谁 | 何时 | 改什么 |
|
||||
|---|---|---|---|
|
||||
| **dream**(create_or_update) | dreamer(本文档 §4.2) | 入流(新材料进入) | 创建新节点 / update 已有节点 body(语义守恒重写;UPDATE 内分 **CORROBORATE / REFINE / CORRECT** 三种 flavor,详 §4.2.3) |
|
||||
| **M split** | maintainer(`auto_consolidate_design.md` §1) | 节点过载(token / 主题离散度超阈值) | 把 parent body 拆成 parent overview + N children;parent 文件原地 |
|
||||
|
||||
> **关键观察**:"主题概览节点"不是一种 kind,也不是 maintainer 主动涌现的产物 —— 它是 split 的副产品(parent 节点天然成为该 cluster 的 overview,中心性自然高)。
|
||||
|
||||
显式排除:
|
||||
- ❌ merge / dissolve / re-edge / unify —— 跨节点重组不做(同概念二次进入靠 dream update;错桶节点不主动 move)
|
||||
- ❌ 完美归簇 —— F-5 留白,不确定就不动
|
||||
- ❌ 实时一致 —— 异步 / eventual
|
||||
|
||||
### 4.2 dream(create_or_update)流程
|
||||
|
||||
**dream = dreamer 入流唯一改 body 的操作,且只改 subject node。**
|
||||
|
||||
#### 4.2.0 digest 是抽象记忆层
|
||||
|
||||
Digest 是 agent 长期记忆的**抽象层** —— 类比前额叶对认知的聚合。原始细节(数字、流程文本、谁说了什么)留在材料(daily / resource),digest 只承载细节淡忘后仍想调取的那一层:原则、模式、可作为先例的决策、认知要点。这一立场决定了 dream 流程的形态:**Phase 1 识别抽象,Phase 2 把抽象登记到 digest 节点**。
|
||||
|
||||
#### 4.2.1 两阶段流程
|
||||
|
||||
```
|
||||
material 进入(daily / resource 选定 scope)
|
||||
│
|
||||
▼
|
||||
Phase 1 — extract (轻量)
|
||||
LLM 读材料 → 识别其中教导的"抽象"(原则 / 模式 / 先例)
|
||||
→ 为每个 unit **分配 bucket**(procedure / personal / wiki)
|
||||
→ 发出 ExtractedUnits 结构化输出 = K 个 sub-unit
|
||||
(每个: {name, bucket, summary})
|
||||
说明:多个支撑事实说明同一抽象 → 合并为同一 sub-unit
|
||||
(倾向少而精);Phase 1 是 gate ——
|
||||
无新抽象时发空列表,Phase 2 跳过整轮;
|
||||
bucket 由 Phase 1 一次性决定,Phase 2 不再回选
|
||||
│
|
||||
▼ (Python 外循环,K 次)
|
||||
Phase 2 — integrate (per sub-unit,**按 bucket 分发到独立 prompt**)
|
||||
│ system prompt = integrate_system_prompt_<unit.bucket>
|
||||
│ procedure / personal / wiki 三份独立 prompt,**不共用一套**
|
||||
│ sub-unit ↔ digest 节点 1:1;Phase 2 必写,无 SKIP 出口
|
||||
│
|
||||
├─ RECALL: search(关键词 + 向量 + RRF) + traverse(对 top hit
|
||||
│ 做图扩展,**跨 bucket**) → 候选路径集
|
||||
│
|
||||
├─ HIT: frontmatter_read 廉价 triage → read 完整 body
|
||||
│ 确认候选是否承载同一抽象 → hit 集合
|
||||
│
|
||||
├─ 决策:
|
||||
│ ├─ hit 空 → CREATE 在 digest/<unit.bucket>/<slug>.md
|
||||
│ └─ hit 非空 → UPDATE 路径 (CORROBORATE / REFINE / CORRECT;
|
||||
│ 目标可在任意桶 —— 召回是跨桶的)
|
||||
│
|
||||
▼
|
||||
写入(canonical write 创建 / canonical edit 改正文)
|
||||
│
|
||||
▼
|
||||
agent 上报 IntegrateOutcome {action, target_path}
|
||||
```
|
||||
|
||||
**两阶段 trade-off**:Phase 2 把完整材料发 LLM K 次(一次一 sub-unit),不做 summary loss;代价是 K 倍 prompt token。换来的是 Phase 1 只做"识别抽象 + 分类 bucket"两件事(粒度集中在一个 prompt),Phase 2 每次会话上下文干净、bucket-specific prompt 让推理聚焦于"这一桶要怎么写 / 怎么改"。
|
||||
|
||||
**Phase 2 的 bucket 专化**:三桶各有独立 system prompt,因为各桶的 body 形态、决策偏置不同 —— `procedure` 节点是 runbook 风(触发 / 步骤 / 前置 / 失败模式),`personal` 节点是规则风(rule + Why + How to apply),`wiki` 节点是百科风(定义 + 性质 + 关系)。共用一份通用 prompt 会让"应该写成什么样"的指导被稀释,bucket 信号靠一段 if-this-then-that 散文承载,效果劣于让每桶自带专属 prompt。
|
||||
|
||||
#### 4.2.2 召回 → 内化分类 → 决策 → 织突触(ReAct agent 一体完成)
|
||||
|
||||
Phase 2 是单个 ReAct agent 在一个 loop 内完成 4 件事 —— **不拆 stage,不引入外部机械步骤**,只通过 prompt 引导 agent 把 dedup 与 synapse 这两类判断都做透。当前默认 `search(limit=5)` 不够,prompt 已显式引导更深召回。
|
||||
|
||||
**4 步流程**(整段由 ReAct agent 自主组织调用):
|
||||
|
||||
| # | 步 | 关键动作 |
|
||||
|---|---|---|
|
||||
| 1 | **召回 —— 多角度宽召** | 显式 `limit=20-30` × 两轮 search(一次 hybrid,一次 `vector_weight=1.0` 纯语义)+ `traverse depth=2` 拓扑补充 |
|
||||
| 2 | **内化分类** | `frontmatter_read` triage + 必要时 `read` body;对每个候选**内化打 label**(只在思考中分类,不输出):`same_abstraction` / `related` / `unrelated` |
|
||||
| 3 | **决策** | 0 个 `same_abstraction` → CREATE;1 个 → UPDATE(选 flavor) |
|
||||
| 4 | **织突触** | CREATE 或 UPDATE 都把所有 `related` 候选织入 body 作 `[[Y.md]]`;CREATE 一次性织全;UPDATE additive 加 wikilink |
|
||||
|
||||
**两类内化判断的本质**:
|
||||
|
||||
| 判断 | 服务 | 输出形态 |
|
||||
|---|---|---|
|
||||
| **同抽象?**(dedup)| 决定 CREATE / UPDATE | 0/1 个 target(决策面排他) |
|
||||
| **相关?**(synapse)| 决定织哪些 wikilink | N 个 related 候选(决策面累加) |
|
||||
|
||||
两者是同一个 ReAct agent 在看完 candidates 后的**两层独立判断**,共享同一批召回结果,**不需要分两轮 LLM 调用**。
|
||||
|
||||
**召回**(对应 prompt step 1):dream 用专属的 `node_search`(`reme/steps/index/node_search.py`),**不**用通用 `search`,**也不用 `traverse`** —— 详 §4.2.2.1(traverse 是 retrieve-time 子图挖掘工具,跟 dream 写入场景错位)。
|
||||
|
||||
| 调用 | 找什么 |
|
||||
|---|---|
|
||||
| `node_search(query=<...>, limit=20-30)` | digest 内节点级 hybrid 召回(vector + BM25 RRF),返回 path + frontmatter |
|
||||
|
||||
**召回结果服务两类判断**:dedup(`same_abstraction` label,是否同抽象 → CREATE / UPDATE)和 synapse(`related` label,是否相关 → 织 wikilink)是 LLM 在**同一批 candidates** 上的两类内化 label。原"两轮 search(hybrid + vector_only)"是设计冗余 —— 同一批候选 LLM 自己能判 same/related/unrelated,模式切换无意义。**调用次数由 agent 自决**:一次通常够;若 unit 跨多个概念维度,agent 可发起多次不同 query 的召回,prompt 不强约束。
|
||||
|
||||
**HIT = `node_search` 返回 + read**:`node_search` 已内嵌返回每个 hit 的 frontmatter(`name + description`),agent 直接据此 triage,**不需要额外调 `frontmatter_read` 批量取 metadata**;仅对需要看 body 的少数候选用 `read`。**不可仅凭 frontmatter 决定 UPDATE**,body 才是判定依据。
|
||||
|
||||
##### 4.2.2.1 node_search vs 通用 search 的差别 + 为什么 dream 不用 traverse
|
||||
|
||||
**node_search vs 通用 search**:dream 的召回需求跟外部 agent 的 RAG 检索**结构性不同**,因此用专属 step 而非复用 `search`:
|
||||
|
||||
| 维度 | 通用 `search`(外部 agent)| `node_search`(dream Phase 2) |
|
||||
|---|---|---|
|
||||
| 用户 | 用户/外部 agent 的自然语言 query | dream 内部生成的 unit.summary |
|
||||
| 结果粒度 | **chunk 级**(可能同一 node 多个 chunk)| **node 级**(同 path 聚合 max score)|
|
||||
| 返回信息 | 完整 chunk text + scores | **path + name + description**(frontmatter 内嵌,无 body)|
|
||||
| 范围 | 全 workspace(daily / resource / digest) | **digest-only**(dream 永远只在 digest 找候选)|
|
||||
| expand_links | 默认 `True`(给 agent 更多上下文)| **永远 `False`**(synapse 找的就是未 link 的)|
|
||||
| 默认 limit | 5 | **20**(dream 需要宽召覆盖 synapse)|
|
||||
|
||||
复用通用 `search` 会让 dream 拿到的候选**既粒度不对**(chunk 级,同 node 多次出现)**又信息冗余**(chunk text 不必要)**又被噪声污染**(daily / resource hits 永远不是 dream 的 UPDATE 候选)**又召回偏窄**(expand_links 把已 link 的拖回来,挤掉真正未 link 的 synapse 候选)。所以 dream 需要自己的 `node_search`。
|
||||
|
||||
**为什么 dream toolkit 不包含 traverse(或 dream_traverse)** —— traverse 是 **retrieve-time 子图挖掘工具**,跟 dream 写入场景**结构性错位**:
|
||||
|
||||
| 维度 | traverse 的本性(retrieve / RAG)| dream 的真实需求(写入)|
|
||||
|---|---|---|
|
||||
| 方向 | 从已知中心向外扩散 | 从外部新材料找 workspace 内相关候选 |
|
||||
| 输入 | 已知种子节点 | 新材料的 unit.summary |
|
||||
| 输出语义 | "X 的子图"(给读者上下文) | "X 应该 link 到哪些 Y" |
|
||||
| 图遍历的角色 | 主操作 | 召回兜底(可有可无) |
|
||||
|
||||
dream 写新节点要回答"workspace 中谁跟我相关",这是**召回**问题(给 query 找相关),不是**遍历**问题(给中心找邻居)。**召回工具 = node_search;遍历工具 = traverse(留给 retrieve / 外部 agent 用)。dream 不需要遍历**。
|
||||
|
||||
(早期曾实现 `dream_traverse` 准备作为 dream toolkit 一员,后撤销 —— 实测拓扑遍历 vs vector 召回重叠率 ~95%,真正独特贡献 < 2%,且引入 LLM 调用 / 上下文 / 复杂度成本。详 git log。)
|
||||
|
||||
**node_search 参数极简**(`query / limit` 两个):**mode 不需要**(同一批候选服务双判断);**exclude_paths 不需要**(self 由 LLM 自己识别,frontmatter 内嵌让 agent 一眼看出"这就是我");**min_score 不需要**(RRF 分数范围 0~0.025,跟 cosine 0~1 量纲完全不同,召回深度由 `limit` 控制就够)。**调用次数 agent 自决**:prompt 不约束"必须一次",unit 跨多个概念维度时 agent 可多次召回。
|
||||
|
||||
**node_search 召回算法:weighted node-level RRF**(vector + BM25 hybrid):
|
||||
|
||||
- vector + BM25 各自独立召回 → 各自得到 chunk list(按各自 score 排序)
|
||||
- 同 path 多 chunk 合并:取该 path 在两个 list 中的 max chunk score 位置作为 node rank
|
||||
- RRF 融合:`score(path) = vector_weight × 1/(60 + rank_v) + (1-vector_weight) × 1/(60 + rank_k)`
|
||||
- `vector_weight=0.7`(默认),vector 主导,BM25 作为兜底(覆盖专有名词 / 缩写等 embedding 可能 struggle 的字面 case)
|
||||
- 输出 score 是 RRF 分(0~0.025 量级,不是 cosine);LLM 不依赖具体分数,内化判 same/related/unrelated
|
||||
|
||||
**reinforce 并入立场**(对照 `auto_consolidate_design.md` §4 标作废):reinforce 不再是独立的 consolidate 动作 —— 它就是 step 4 的"织突触"。新节点写入瞬间一次性建立关系,workspace 不维护"事后周期 batch 补 wikilink"的通道。F-2 自然守住 —— dream 只动新节点 body,不动其它节点。
|
||||
|
||||
**关键约束**(诚实承认):
|
||||
- **写入即定型** —— 今天没织的 wikilink 以后没机会再织;workspace 单调演化
|
||||
- **一次性 commit,无事后兜底** —— prompt 明示"宁可多织"(false positive 一眼能否决;false negative 永远沉默)
|
||||
- **召回深度取决于 prompt 引导 + agent 配合** —— 不引入外部机械召回 step;prompt 已明示 `limit=20-30 × 两轮`,但仍是 ReAct agent 的开放执行
|
||||
- **dedup 与 synapse 在一次 LLM 调用内完成** —— 不拆独立 stage,共享召回结果,内化分类是免费的
|
||||
|
||||
#### 4.2.3 UPDATE 三种 flavor
|
||||
|
||||
| flavor | 何时 | body 怎么动 |
|
||||
|---|---|---|
|
||||
| **CORROBORATE**(最常见)| 已有节点已覆盖此抽象,材料是又一个实例 | body 实质不变 —— 追加 `derived_from::` 溯源,可选强化措辞("似乎"→"确实") |
|
||||
| **REFINE**(常见)| 已有节点覆盖了核心,但材料揭示新的范围 / 边界 / 维度 | 改相关片段使更精确,加新维度,加 `derived_from::`。正文在**精度**上长,不在**细节**上膨胀 |
|
||||
| **CORRECT**(少见)| 材料与已有抽象矛盾 / 表明它被夸大 | 收紧到新旧证据都支持的窄形式,或内联标注 `> note: contradicted by [[...]]` 不仲裁。仍加溯源 |
|
||||
|
||||
三种都受 §4.4 E-1 强守恒约束(出边集合不能缩)。
|
||||
|
||||
#### 4.2.4 关键边界
|
||||
|
||||
- **Phase 1 是 gate + 分类器** —— "不值得记忆"在 Phase 1 过滤(空列表);此外 Phase 1 还为每个进入 Phase 2 的 unit 分配 bucket(procedure / personal / wiki),决定 Phase 2 走哪份专用 prompt;Phase 2 必然写,sub-unit 与 digest 节点 1:1
|
||||
- **Phase 2 prompt 按 bucket 分发** —— `integrate_system_prompt_procedure` / `_personal` / `_wiki` 三份独立 system prompt,各自承载该桶的 body 形态指南与决策偏置,**不共用一份通用 prompt**
|
||||
- **CREATE 写入桶 = Phase 1 分配的桶**;**UPDATE 目标可在任意桶**(召回跨桶,UPDATE 命中谁就写谁)
|
||||
- **dream update 必须语义守恒** —— LLM 重写 body 时只能"融入"新内容,不能删除已有信息(只增不删 / 不改原意;冲突标注 `> 注:不同来源记载...`,不擅自仲裁);**当前实现下 E-1 强守恒是 prompt-only 自律**(canonical edit 不做机械 outbound diff;早期 `digest_edit` 子类的机械校验已在切到 canonical 工具时移除,详 §4.4)
|
||||
- **Phase 2 用 canonical write / edit** —— 不再有 `digest_write_step` / `digest_edit_step` 子类;桶归位与边守恒都是 prompt-level 纪律
|
||||
- **dream 不改其它节点正文**(F-2) —— 只动 subject
|
||||
- **dreamer 不做事件级伞节点** —— 材料本身(daily / resource 文件)就是 fan-out 点,每个 sub-unit 的 `derived_from::` 让材料天然聚合到所有派生节点
|
||||
- **0 出边节点合法**(没识别到合适邻居),后续 dream 进入时其它节点可以反向链回来 —— 不强求 LLM 一次性给全
|
||||
- **dream 漏判去重**(同概念建成新节点)→ 不主动兜底,接受重复;若 workspace 累积明显重复,由 auto-consolidate 的 dups 检测周期 batch 产报告(`auto_consolidate_design.md` §3)
|
||||
- **召回不做 bucket 粗筛** —— LLM 拥有完整跨桶视野,可识别"概念跨桶同抽象"(例如同一原则在 wiki 已有节点而 Phase 1 把新材料归入 personal,此时 UPDATE wiki 节点而非新建 personal 节点)
|
||||
- **reinforce 已并入 dream synapse recall** —— 不存在独立的 reinforce 动作或周期 batch;突触构建(原 `auto_consolidate_design.md` §4 reinforce 的职责)在 dream Phase 2 synapse recall 阶段完成,新节点写入瞬间织全(详 §4.2.2)
|
||||
- **workspace 不维护事后补 wikilink 通道** —— 上一条的直接推论;cognition §9.2 立场("关系建立在写入瞬间")在此自然守住
|
||||
|
||||
**provenance 写出**:
|
||||
- 行文中自然带:"... 该模式最早出现在 [[daily/2026/05/15.md]] 的实践中"
|
||||
- **强制 typed predicate `derived_from::`** —— body 必须织入至少一条 `derived_from:: [[daily/...]]` 或 `[[resource/...]]`,纯散文形式不会被未来的 update / 守恒比对识别为边,下次 update 时会消失
|
||||
- LLM 直接做语义守恒重写(只增不删) —— 不走"首版 append 起步"的过渡路径
|
||||
|
||||
### 4.3 F-invariants(演化的硬约束)
|
||||
|
||||
| # | 约束 | 含义 |
|
||||
|---|---|---|
|
||||
| **F-1** | **0 文件移动** | dream / split 都不移动现有文件;split 创建的是**新文件**,parent 原地 |
|
||||
| **F-2** | **改正文限定 subject** | dream update 改 subject body;M split 改 parent body + 创建 children body;**没有任何操作改"其它节点正文"** |
|
||||
| **F-3** | **maintainer 只做 split** | 没有 summarize / merge / re-edge / link / unify / dissolve |
|
||||
| **F-4** | **一次一个候选** | M split 一次拆一个;dream 一次处理一个原子单元(N 候选 = N 次 dream) |
|
||||
| **F-5** | **不确定时不动** | dream 拿不准 create 还是 update → 倾向 create;split 拿不准 cluster → 不拆 |
|
||||
| **F-7** | **多归属合法** | 一个节点可被多个引用,也可指向多个;**没有"单父"约束** |
|
||||
| **F-10** | **inbound 目标节点不动** | 所有 inbound 是裸链 `[[<parent-path>.md]]`(digest 不引入 anchor);split 时全部保持,parent 路径未变即天然有效 |
|
||||
| **F-11** | **wikilink 是 body 的一部分** | 不存在"独立的边";reme 核心机械算子只感知字符层,语义责任在 LLM(prompt 自律);split 写入路径仍带机械 outbound 校验,dream update 当前是 prompt-only(详 §4.4) |
|
||||
|
||||
### 4.4 边守恒(E-1 / E-2 / E-3)
|
||||
|
||||
**前提**:wikilink 是 body 的一部分(F-11)。"边"不是独立抽象 —— body 一变,边就跟着变。reme 核心**没有"修边"算子**;边的所有变化都是 body 文本编辑的副作用。语义层守恒由两条腿承担:**prompt 自律**(LLM 在 update 时被反复要求 only-add, not-delete)+ **必要时的机械校验**(下文区分了哪些保留、哪些已移除)。
|
||||
|
||||
| # | 类别 | 规则 | 谁负责 |
|
||||
|---|---|---|---|
|
||||
| **E-1** | dream update 节点出边(subject 自身) | **强守恒**:新 body 出边 ⊇ 原 body 出边(`(target, predicate)` 二元组,predicate 一并守住) | **当前实现:LLM(prompt)自律** —— canonical `edit` 不做机械 outbound diff,prompt 反复强调"never drop wikilinks the old span contained" |
|
||||
| **E-2** | split parent 出边(parent 拆解) | `(parent_new ∪ ∪children_outbound) ⊇ parent_old` | LLM(split prompt)+ 机械(由 maintainer 在 split 写入路径上实施,见 `auto_consolidate_design.md`) |
|
||||
| **E-3** | inbound wikilink `[[<parent-path>.md]]` | split 时**不动** —— 仍指 parent;后续 dream 进入若 LLM 觉得 child 粒度更合适,直接加新边到 child(F-10) | 不动 |
|
||||
|
||||
**E-1 实现取舍**:早期版本有专用 `digest_edit_step` 子类,在写入前对 body 做 outbound diff 比较,违反守恒时返回 `REJECT_CONSERVATION` 让 LLM 重试。在切到 canonical `edit` 工具(放弃 digest 子类)后,这道机械校验被移除 —— 守恒退化为 prompt-only 自律。trade-off:
|
||||
- **失**:LLM 偶尔会在 REFINE / CORRECT 时无意丢弃 `derived_from::` 链;系统不再自动拒写
|
||||
- **得**:Phase 2 工具与系统其它写入路径完全一致(write / edit 是 canonical job),没有 dream-private 写入语义;prompt 复杂度下降,工具表面更小
|
||||
- **后续**:若 prompt-only 守恒在生产中被证伪(掉链率高),可在 canonical `edit` 上挂一个可选的 conservation 校验 hook(不再走子类化路径),由 dreamer 在调用前后各 read 一次做 diff;但当前不做
|
||||
|
||||
**强守恒(集合包含)而非等价**:`new ⊇ old` = 允许加新边(新关联),不允许减边(老内容不能丢);`new == old` 会拒绝任何新出边 → update 失去意义。
|
||||
|
||||
**predicate 守住** —— `[[A]]` ↔ `is_a:: [[A]]` 视为不同 key,升降级走显式 audit 路径,不走默认。重排 / 改 alias / 加新边都不被拦下(集合相同或只增)。
|
||||
|
||||
**provenance 不单列** —— 节点反指上游 daily/resource 的 wikilink 是 body 正文的一部分,跟其它 wikilink 走同一套 E-1 / E-2;reme 核心没有 provenance 专用算子。
|
||||
|
||||
**inbound anchor 这一类不存在** —— digest 不引入 anchor,所有 inbound 都是裸链,走 E-3 即可,无需机械 retarget 子流程。
|
||||
|
||||
---
|
||||
|
||||
## 5. 与其它层
|
||||
|
||||
| 上下游 | 关系 |
|
||||
|---|---|
|
||||
| ← **auto-memory**(daily) | dream 读 daily 作为入流;daily 写完即对 dream 可见 |
|
||||
| ← **resource** | dream 读 resource 作为入流(只读,不写) |
|
||||
|
||||
**关键边界**:dream 不写 daily / resource(I-2 / I-3);只写 digest 节点 body(自身 subject)。dream 不感知下游 —— split / 链接增强 / 索引刷新 / rename 等由 `auto_consolidate_design.md` / `auto_cognition_design.md` / `update_store_index_loop` 各自负责。
|
||||
|
||||
---
|
||||
|
||||
## 6. 下一步
|
||||
|
||||
本文档覆盖 dream 模型(桶 / 节点 / 边 / 演化)。组织端实现清单(M split / D 检测 / CAS 框架)见 `auto_consolidate_design.md` §10。
|
||||
|
||||
- ✅ **dream step 实现** —— Phase 1 extract(识别抽象 + 分配 bucket)+ Phase 2 integrate(per sub-unit,**bucket-specific prompt 分发**;`reme/steps/evolve/dream.py` + `dream.yaml`,与 `auto_memory` 同级同形)
|
||||
- ✅ **三桶 hard-coded** —— `procedure / personal / wiki`,`BUCKETS` 常量在 `dream.py` 顶部,Phase 1 通过 `MemoryUnit.bucket: Literal[...]` 由 Pydantic 强制约束
|
||||
- ✅ **provenance prompt 规范** —— `derived_from:: [[daily/...]]` / `[[resource/...]]` 强制(三桶 prompt 各自重申)
|
||||
- ❌ ~~**边守恒校验工具**~~ —— 早期 `digest_edit` 子类的 outbound diff 校验已随子类一并移除(切到 canonical `edit`);E-1 现由 prompt 自律,详 §4.4
|
||||
- ❌ ~~**bucket 集合配置外置**~~ —— 撤销:三桶是 dream 模型本身的一部分,不做配置参数(`workspace.yaml` 不再承载 `digest.buckets`,`_buckets.md` 视图也不再生成)
|
||||
- 🆕 **Phase 2 召回拆 dedup / synapse**(2026-06-02 沉淀,详 §4.2.2)—— 当前 prompt 共用一次 `search(limit=5)`,既不够 dedup 精度也不够 synapse 覆盖;落地:`dream.yaml` 6 处(en + zh × 3 buckets)Recall 段改写,加 synapse 模式说明 + 写入即定型纪律
|
||||
- 🆕 **`file_store.default.embedding_model` 启用**(blocker)—— `default.yaml` 当前 `""`,synapse recall 用 vector_weight=1.0 模式必须开启;否则 `search` 退化为纯 BM25,dedup 也劣化
|
||||
- 🆕 **reinforce 并入立场写入**(详 §4.2.2)—— 与 `auto_consolidate_design.md` §4 标作废同步;`hierarchical_summary.md` §13.2 Q4 标解决
|
||||
|
||||
实现进入 `reme/steps/evolve/` 时,本文档与 `auto_memory_design.md` / `auto_consolidate_design.md` / `auto_cognition_design.md` 共同作为契约依据。
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
# auto-memory 设计(实时事件拆分 / 写入 daily)
|
||||
|
||||
> 本文档记录 reme 中 **auto-memory** 的设计讨论 —— 把 agent 连续的对话 / 任务流切成离散的 daily 事件原子,inline 落到 `daily/` 层。
|
||||
>
|
||||
> 配套阅读:
|
||||
> - `structure.md` §2.1-2.2(daily 层定位)/ §3.4(sync 动作语义)/ §7.1(synchronizer 模块)
|
||||
> - `auto_dream_design.md`:auto-memory 产物如何被 dream 消化(dream 读 daily 作为入流之一)
|
||||
> - `auto_consolidate_design.md`:digest 的组织端 / CAS 写入协议;auto-memory 不直接复用,但事件级"拆"与节点级 split 在概念上同构(都把过载粒度切小)
|
||||
> - `auto_cognition_design.md`:auto-cognition 三阶段顶层思想(写入 / 巩固 / 检索);daily 节点是 cognition 图视图的一部分(承载 `derived_from::` 反指),但不参与 Stage 2 巩固改造
|
||||
>
|
||||
> **服务全景**:reme 服务两条主线 —— **auto-memory**(本文档,入流端 / daily 写入)与 **auto-cognition**(顶层思想:写入 = auto-dream,巩固 = auto-consolidate,检索 = auto-recall)。auto-memory 把 agent 实时事件流切成 daily 事件原子;它的产物是 dream(cognition Stage 1)消化的两路输入之一(另一路是 resource)。
|
||||
>
|
||||
> **核心立场**:auto-memory 是 `structure.md` §3.4 `sync` 动作的实现侧 —— 强调 **inline 实时**与**事件边界检测**。是不是改名 sync → auto-memory 留给上层文档对齐,本文档聚焦机制。
|
||||
|
||||
---
|
||||
|
||||
## 0. 问题陈述
|
||||
|
||||
agent 的对话与任务过程是连续事件流(用户回合、工具调用、上下文切换、中断恢复),但记忆系统需要离散的、可独立检索的事件单元。auto-memory 解决这个切分问题。
|
||||
|
||||
| 输入 | 输出 |
|
||||
|---|---|
|
||||
| agent 当前事件流(对话回合 / 工具调用 / 任务切换信号);可选 `notify` 候选作为 cue | `daily/<date>/<event-slug>/<note>.md` 事件原子;`daily/<date>.md` 主索引 |
|
||||
|
||||
**设计目标**:
|
||||
1. **事件边界尽量与 agent 语义意图一致** —— 同一个意图(同一个任务 / 同一段思路)→ 同一个事件;意图切换 → 新事件
|
||||
2. **inline 实时写入** —— 不滞后,不批处理;agent 一边工作,记忆一边落地
|
||||
3. **保持 daily 写权契约** —— I-2 单作者(同 folder 不并发改);folder 名 = summary note 名(I-3 可移动单元)
|
||||
|
||||
**显式排除**(不属于 auto-memory 职责):
|
||||
- ❌ 蒸馏 / 沉淀:那是 auto-dream(`auto_dream_design.md`)的事
|
||||
- ❌ 实体识别 / wikilink 自动补全:cognition 三阶段不在写入后做"事后补 wikilink"(详 `auto_cognition_design.md` §1.1);所有 wikilink 由 dream 在写入瞬间产出
|
||||
- ❌ 改写 resource / digest:auto-memory 只写 daily(I-1 / I-3)
|
||||
|
||||
---
|
||||
|
||||
## 1. 已对齐决策
|
||||
|
||||
### 1.1 物理布局:与 `structure.md` §2.2 对齐
|
||||
|
||||
| 项 | 决策 |
|
||||
|---|---|
|
||||
| **主轴** | 时间 + 任务:`daily/<date>/<event-slug>/` |
|
||||
| **event-slug** | LLM 抽取的事件短名(snake_case / dash-case;不强制 schema),同 `<date>` 下唯一 |
|
||||
| **folder 内** | 一个事件可有 N 个 note(`progress.md` / `decision.md` / `references.md` 等),由消费层 schema 决定;最少含一个 summary note,与 folder 同名 |
|
||||
| **主索引** | `daily/<date>.md`:当天事件列表(机械写入,wikilink 指向各 event folder)|
|
||||
| **跨日索引** | 不强制;dream 消费时按 `<date>` 范围拉取即可 |
|
||||
|
||||
**为什么不是单文件 event**(report §5.1 一种简化方向):
|
||||
- 单文件 event = `daily/<date>/<event-slug>.md` 比 folder 模型简单,但失去"一个事件可包含多个视角 note"的灵活度
|
||||
- 现行 `structure.md` 已定 folder 单位模型;auto-memory 沿用,不破坏既有 I-2 / I-3
|
||||
- 若后续 dogfooding 验证单事件普遍只有一份 note,可演进为 folder 内只放一份 summary,机械上等价于单文件方案 —— 演进路径平滑,不需要现在选
|
||||
|
||||
### 1.2 事件边界:语义意图切换驱动
|
||||
|
||||
事件边界由 LLM 在 inline 写入时判:**当前回合的意图是否仍属上一个 event**。
|
||||
|
||||
| 维度 | 决策 |
|
||||
|---|---|
|
||||
| **决策时机** | 每个 agent 回合写入前 inline 判 |
|
||||
| **决策依据** | 上一个 active event 的 summary + 当前回合内容;LLM 输出 `{continue: bool, new_event_slug?: str, summary_patch?: str}` |
|
||||
| **continue=true** | append 当前回合到 active event(append-only 或 LLM 重写 summary,详 §1.3) |
|
||||
| **continue=false** | 关闭 active event(写最终 summary)+ 开新 event folder(slug 由 LLM 给)|
|
||||
| **同时 active 多事件** | 不允许(I-2 单作者)—— 一时刻只一个 active event;真要并行任务,agent 自己 sync 切换 |
|
||||
|
||||
**已排除**:
|
||||
- 时间窗口切分(N 分钟无活动则切)—— 对话节奏因任务而异,时间窗口噪声大
|
||||
- 关键词切分(出现"切换 / 现在做 X"等触发词)—— 假阳性高,且不所有切换都明显说出
|
||||
- 后置 batch 切分 —— inline 写入要求 event 必须当下可决定归属,不能等
|
||||
|
||||
### 1.3 事件内写入模型
|
||||
|
||||
active event 内,每个回合的内容写到 event folder 下,有两种模式可选(消费层 schema 决定):
|
||||
|
||||
| 模式 | 形态 | 适用 |
|
||||
|---|---|---|
|
||||
| **append-only** | 一份 `<event-slug>.md`,新回合 append 到末尾(章节 / 时间戳 / 等)| 实现最简;事件短(< 几十回合)时可读性 OK |
|
||||
| **多 note 重写** | summary note(folder 同名)+ 各视角 note(`progress.md` / `decision.md`);LLM 把新内容融到对应 note,summary note 重写为当下概览 | 事件长 / 多视角时可读性高;LLM 成本高 |
|
||||
|
||||
**默认 opinionated default**:append-only(最简启动)。消费层可改 prompt + schema 走多 note。
|
||||
|
||||
**与 E-1 守恒的关系**:daily 不强制 E-1 守恒(它是工作记录,允许 LLM 删旧加新);只在 multi-note 重写模式下,可选启用类似守恒(保留所有 wikilink),具体由消费层决定。
|
||||
|
||||
### 1.4 主索引 `daily/<date>.md`
|
||||
|
||||
当天事件 list 视图,机械维护(无需 LLM):
|
||||
|
||||
| 触发 | 操作 |
|
||||
|---|---|
|
||||
| 新建 event folder | 主索引 append 一行 `[[daily/<date>/<event-slug>/<event-slug>.md|<event-slug>]]` |
|
||||
| 事件关闭(被切下一个 event) | 主索引该行 append 最终 summary 摘要(可选,LLM 写最终 summary 时附带写入) |
|
||||
| 索引文件不存在 | 写入第一个 event 时创建 |
|
||||
|
||||
主索引**仅承担当天浏览锚点**:文件系统 `ls daily/<date>/` 也能看见,但有主索引人/agent 可直接 `read daily/2026/05/28.md` 拿到 list 视图 + summary 一览。
|
||||
|
||||
不维护跨日索引(`daily/2026/05.md` 或 `daily.md`):dream 消费时按时间范围拉取即可;`list daily/<date>/` 已经覆盖浏览需求。
|
||||
|
||||
### 1.5 与 notify 的协作
|
||||
|
||||
`notify` 是 reme → agent 的虚边推送(`structure.md` §3.3),把"有新 resource 值得看"传递给 agent。auto-memory 在以下两点与 notify 协作:
|
||||
|
||||
| 维度 | 协作方式 |
|
||||
|---|---|
|
||||
| **新事件 cue** | agent 收到 notify 后,如果决定响应(开始处理这个候选),通常会触发**新 event** —— auto-memory 把 notify payload 作为 hint(候选 resource 路径)写入新 event 的 summary,顺手用 wikilink 引上 |
|
||||
| **acknowledge 派生** | event note 里出现指向 `[[resource/...]]` 的 wikilink → L1 watcher 将该 resource 推送状态置 `acknowledged`(`structure.md` §3.3 / §6.2);auto-memory 自身不调任何 ack API |
|
||||
|
||||
**关键约束**:auto-memory **不强制** agent 用 wikilink 引 notify 候选 —— agent 可能略过、也可能不通过 wikilink 而是直接读 resource。ack 是 daily → resource wikilink 的副产品,不是 auto-memory 显式负责的事。
|
||||
|
||||
---
|
||||
|
||||
## 2. 待对齐边界点
|
||||
|
||||
### 2.1 LLM 决策频率与成本
|
||||
|
||||
inline 边界检测的最朴素形态是每回合调一次 LLM。在长对话 + 高频回合下成本可观。可选优化:
|
||||
- **continue 假设默认**:大多数回合是 continue(同一意图内),LLM 可能只在"看似切换"启发(token 跨度大 / 工具种类突变 / 用户显式说"接下来")时跑;否则默认 continue 不调 LLM
|
||||
- **批回合**:每 N 回合批一次,延迟切分(代价:active event 边界滞后,首版可接受)
|
||||
|
||||
首版默认每回合调一次(最简,正确率高),M1+ 视成本优化。
|
||||
|
||||
### 2.2 中断恢复 / 跨进程 active event
|
||||
|
||||
agent 进程重启 / Service 重启后,如何识别"还有 active event"?
|
||||
|
||||
候选方案:
|
||||
- **L2 自治状态**:L1 watcher 派生 `daily/<date>/<event-slug>/` 中最新 mtime 的 event 为 active(默认 N 分钟内有写入)
|
||||
- **状态文件**:`.daily-active` 维护 active event slug,Service 启动时读
|
||||
- **每次重建**:agent 进程重启视为新 event,旧的关闭(切到 §1.2 continue=false 路径)—— 最简但会增加事件数
|
||||
|
||||
倾向 §1.2 自然路径(进程重启 = LLM 下次判 continue=false 概率高)+ 不维护状态文件,详细 worker recovery 留给 Service 实现。
|
||||
|
||||
### 2.3 多 agent 同 workspace 的 active event 隔离
|
||||
|
||||
I-2 daily 单作者契约在多 agent 场景下需细化。候选:
|
||||
- per-agent date subfolder:`daily/<date>/<agent-id>/<event-slug>/`
|
||||
- 单 agent 模式 + agent ID 进 event-slug:`<date>/<agent-id>_<event-slug>/`
|
||||
|
||||
第二种破坏 slug 短名习惯;第一种引入额外层级。倾向后者作为消费层契约,reme 核心不固化。
|
||||
|
||||
### 2.4 事件粒度的 prompt 引导
|
||||
|
||||
边界检测的 prompt 决定切分粒度。粗 = event 大 / dream 看每个 event 时容易 overflow;细 = event 数爆炸 / 主索引拥挤。
|
||||
|
||||
**opinionated default prompt 倾向**:
|
||||
- 一个意图 = 一个事件(用户提了 X 问题 / agent 开了 Y 任务 → 直到这个意图收尾)
|
||||
- 跨意图的"附带工作"(查资料 / 算个数)归入当前意图,不开新 event
|
||||
- 真新意图("好,现在我们做下一件事")才切
|
||||
|
||||
详细 prompt 落 `reme/steps/jobs/protocol.md` 或 synchronizer 的 prompt 模板。
|
||||
|
||||
### 2.5 与 resource ingest 的时序
|
||||
|
||||
如果 ingest 与 auto-memory 同时活跃(External push 推 resource 进来 + agent 在 sync),且 agent 想响应这个新 resource:
|
||||
- ingest 写完 resource → L1 watcher 派生 L2 → notifier 决策推送(`structure.md` §5.3)→ Service MCP 推给 agent
|
||||
- agent 在当前回合或下一回合响应 → auto-memory 判 continue=false 开新 event,wikilink 引上 resource
|
||||
|
||||
整条链 sub-second 到 seconds(notify 节奏);auto-memory 不直接知道 ingest,只在 agent 决定响应时被动接收 notify payload。
|
||||
|
||||
### 2.6 跨日任务延续
|
||||
|
||||
event 物理路径含日期(`daily/<date>/<event-slug>/`),同一意图跨日的任务无法用同一 event folder 承载。候选模型:
|
||||
|
||||
| 模式 | 形态 | 适用 |
|
||||
|---|---|---|
|
||||
| **每日新 event,wikilink 反指前日** | new day 起新 folder;summary note frontmatter 加 `inherits: [[daily/<prev-date>/<prev-slug>/<prev-slug>.md]]`;新 event body 不复制旧内容,仅引用 | event-slug 短,日切口干净;查 backlinks 拼出整条任务链 |
|
||||
| **同 event 重复写不同日** | 不允许(I-2 single author + event folder date 在路径上,跨日写违反路径不可变) | × |
|
||||
| **任务 ID 跨 daily 抽象** | 引入 `task-id` 维度,daily event 只是某 task 的某一日切片;额外维护 task index | 复杂度高,M0 不引入 |
|
||||
|
||||
**倾向**:第一种(`inherits` frontmatter wikilink)—— 与 §1.4 主索引一致(机械维护),实现侧 LLM 在 §1.2 boundary 判定时若发现意图与最近 N 天某个 active 任务一致,直接写入 inherits 即可。详细 boundary prompt 落 §2.4。
|
||||
|
||||
INHERIT 行为细节(扫描窗口、predecessor 是否关闭、Plan/Objective 是否拷贝)归消费层 schema 决定;reme 核心只承认 `inherits:` frontmatter wikilink 作为跨日链路载体。
|
||||
|
||||
---
|
||||
|
||||
## 3. 与其它层的协作
|
||||
|
||||
| 上下游 | 关系 |
|
||||
|---|---|
|
||||
| ← **notify** | 接收 notify payload 作为新 event cue;不强制响应,不强制 wikilink 引 |
|
||||
| ← **resource** | 只读(通过 wikilink 引);不写 |
|
||||
| → **daily** | **唯一写者**(I-2);写 event folder + 主索引 |
|
||||
| → **auto-dream** | dream 读 daily 作为入流(`auto_dream_design.md` §4.2 dream scope);auto-memory 写完即对 dream 可见(走 L2 索引,有 eventual 窗口) |
|
||||
| → **auto-cognition (三阶段)** | daily 节点是 cognition 图视图的一部分;dream(Stage 1)读 daily 作为入流;consolidate(Stage 2)只对 digest 节点跑 dups / community / decay,**不改 daily**;recall(Stage 3)三层并行召回时 daily 也参与命中 |
|
||||
|
||||
**关键边界**:auto-memory 是 daily 写入端的**唯一**入口;cognition 三阶段没有任何子阶段会**事后改写 daily**(无写回路径)。daily 一旦由 auto-memory 写完,就只被读不被改(I-2 / I-3 仍守);后续 dream / consolidate / recall 都是只读消费。
|
||||
|
||||
---
|
||||
|
||||
## 4. 下一步
|
||||
|
||||
1. **synchronizer step 实现**:event 边界检测 prompt + active event 状态管理 + inline 写入(append-only 默认)
|
||||
2. **主索引维护**:`daily/<date>.md` 机械维护(新 event 时 append、关闭时附 summary)—— 走 crud/daily 基础工具
|
||||
3. **notify ack 派生验证**:L1 watcher 派生 acknowledged 状态(`structure.md` §6.2),与 auto-memory 的 wikilink 写入端到端跑通
|
||||
4. **多 agent 隔离 schema**(M1+):若实际有并发 agent,确定 daily 子目录 / slug 命名约定
|
||||
5. **粗 / 细粒度 prompt 调参**:dogfooding 后看实际 event 数 / dream 消化效率,调 boundary prompt
|
||||
|
||||
实现进入 `reme/steps/jobs/` 与 `reme/file_graph/` 时,本文档与 `auto_dream_design.md` / `auto_cognition_design.md` 共同作为契约依据。
|
||||
|
|
@ -1,323 +0,0 @@
|
|||
# auto-recall 设计(Stage 3 检索:信号融合 + 召回增强)
|
||||
|
||||
> 本文档:reme 中 **auto-cognition 三阶段** 的 **Stage 3 — 检索阶段** 实现。覆盖 query 到来时如何把 workspace 一等公民信号(wikilink 图 / frontmatter)与维护阶段产出信号(centrality / community / recency / archived)融合,生成最终召回。
|
||||
>
|
||||
> 配套阅读:
|
||||
> - `auto_cognition_design.md`:三阶段顶层思想(本文档是 Stage 3)
|
||||
> - `auto_dream_design.md`:Stage 1 写入 / 节点 + 边模型
|
||||
> - `auto_consolidate_design.md`:Stage 2 维护 —— **本文档消费它产出的所有 `meta/*.json`**
|
||||
> - `structure.md` §4(retrieve 三种问法)/ §7.4(为什么没有 retriever 模块)
|
||||
> - `reme/steps/index/search.py` / `traverse.py`:现有原子实现
|
||||
>
|
||||
> **核心立场**:
|
||||
> - retrieve **不引入新 L4 模块**(`structure.md` ✗-15)—— 三种问法各自由 L3 原子工具(`list_step` / `search_step` / `traverse_step`)直接覆盖
|
||||
> - 本文档增强**集中在 `search_step` 内部**:把维护信号融入打分 / 排序 / 过滤;`traverse_step` 仅做小幅参数扩展
|
||||
> - retrieve **只读 workspace,不写 body / 不写 frontmatter**;唯一写入是 `meta/access_log.json`(命中计数,供下次 recency 计算)
|
||||
|
||||
---
|
||||
|
||||
## 0. 问题陈述
|
||||
|
||||
`structure.md` §4 已规定 retrieve 三种问法(state / semantic / topological)正交分立(R-1)。本文档**只增强 semantic 问法**;state 问法已被 `list_step` 覆盖,topological 问法已被 `traverse_step` 覆盖。
|
||||
|
||||
semantic 问法当前在 `reme/steps/index/search.py` 实现:
|
||||
|
||||
| 已就绪 | 缺口 |
|
||||
|---|---|
|
||||
| ✅ vector + keyword 并行召回 | ❌ 节点中心性加权(高权威节点不被 boost) |
|
||||
| ✅ RRF fusion(vector_weight=0.7) | ❌ 同社区 boost(`meta/communities.json` 未消费) |
|
||||
| ✅ 一跳 expand_links(向前向后,max=10) | ❌ 时效衰减 / 冷藏过滤(`meta/access_log.json`、`meta/archived.json` 未消费) |
|
||||
| ✅ min_score 过滤 + limit 截断 | ❌ 同 file 多 chunk 冗余(top-K 可全来自同节点) |
|
||||
| ✅ chunk-level 命中(start_line / end_line) | ❌ 节点级 surface(frontmatter `name + description` 未与 chunk 命中合并展示) |
|
||||
| ✅ 二跳 traverse 作为独立工具 | ❌ search 内 multi-hop expand(只一跳,跨术语关系到不了) |
|
||||
| | ❌ query rewrite / multi-query(单一表达式漏召) |
|
||||
|
||||
**本文档的工作 = 设计这些缺口怎么填**,在 `search_step` / `traverse_step` 现有形态上增量。
|
||||
|
||||
---
|
||||
|
||||
## 1. 三种问法分立(继承 R-1)
|
||||
|
||||
```
|
||||
┌─────────────┐ state 问 ──────► list_step + frontmatter filter
|
||||
│ agent │ semantic 问 ──► search_step (本文档主要增强)
|
||||
└─────────────┘ topological 问 ► traverse_step (小幅参数扩展)
|
||||
```
|
||||
|
||||
| 问法 | 原子工具 | 本文档涉及 | 备注 |
|
||||
|---|---|---|---|
|
||||
| **state** | `list_step` / `daily_list_step` / `frontmatter_read_step` | 不涉及 | frontmatter 过滤无需维护信号 |
|
||||
| **semantic** | `search_step` | **主战场**(§3-§7) | RRF fusion + 信号加权 + multi-hop + query rewrite |
|
||||
| **topological** | `traverse_step` | 小幅(§8) | 起点选择可借助维护信号 |
|
||||
|
||||
**关键约束**(继承 `structure.md` ✗-8):**绝不合并三种问法成单一 read verb**。本文档增强 search_step,但不把 list / traverse 揉进 search;agent 按需各自调用。
|
||||
|
||||
---
|
||||
|
||||
## 2. 维护信号契约消费总览
|
||||
|
||||
`auto_consolidate_design.md` §11 列出维护产出。retrieve 端按以下方式读:
|
||||
|
||||
| 信号 | 来源 | 加载时机 | 缺失行为(降级) |
|
||||
|---|---|---|---|
|
||||
| **centrality** | `file_graph` 反向索引(实时) | search_step init 时引用 file_store | 总在线(file_graph 是核心组件) |
|
||||
| **community** | `meta/communities.json` | search_step 启动 lazy load(LRU 缓存,文件 mtime 失效) | 缺失 → 不做同社区 boost |
|
||||
| **recency** | `meta/access_log.json` | 同上 | 缺失 → recency_factor = 1.0 |
|
||||
| **archived** | `meta/archived.json` | 同上 | 缺失 → 不过滤,所有节点参与 |
|
||||
| **wikilink 图** | workspace 自身(file_graph) | 实时 | 总在线 |
|
||||
| **frontmatter** | workspace 自身(`name` / `description`) | chunk 已带 metadata | 总在线 |
|
||||
|
||||
**version 校验**:`meta/*.json` 加载时检查 `version` 字段,与本文档约定的 schema 版本不匹配 → 走"该信号缺失"降级,日志告警(不崩)。
|
||||
|
||||
**新鲜度**:每个信号文件的 `computed_at` 暴露给调用者(metadata 中带 `signals_freshness`),调用方知道当前权重基于多久前的快照。超过阈值(默认 14 days)→ logger.warning + 仍使用(避免维护偶尔失效就拒绝服务)。
|
||||
|
||||
---
|
||||
|
||||
## 3. semantic 问法增强:打分公式
|
||||
|
||||
**目标**:把维护信号融入 fused chunk 的最终 score,让排序兼顾"文本相关 + 节点权威 + 同社区 + 时效"。
|
||||
|
||||
### 3.1 当前打分(基线)
|
||||
|
||||
```
|
||||
score = RRF_fused(vector_rank, keyword_rank, vector_weight=0.7)
|
||||
```
|
||||
|
||||
仅文本相似度。
|
||||
|
||||
### 3.2 新打分公式
|
||||
|
||||
```
|
||||
final_score = base_score
|
||||
× centrality_factor(path)
|
||||
× community_factor(path, query_seed_paths)
|
||||
× recency_factor(path)
|
||||
```
|
||||
|
||||
| 因子 | 公式 | 默认参数 | 来源 |
|
||||
|---|---|---|---|
|
||||
| **base_score** | RRF 融合分(现状) | vector_weight=0.7 | search.py |
|
||||
| **centrality_factor** | `1 + α · log(1 + inbound_count)` | α = 0.15 | file_graph 实时 |
|
||||
| **community_factor** | 同 community 命中节点 → ×β,否则 1.0 | β = 1.20 | `meta/communities.json` |
|
||||
| **recency_factor** | `exp(-Δt / τ)`,Δt = 距 last_hit_or_update | τ = 60 days | `meta/access_log.json` |
|
||||
|
||||
**为什么乘法而非加法**:
|
||||
- 各因子量级不同(base_score ≤ 0.02,centrality 与 query 无关),加法需大量 normalization;乘法天然处理量级差
|
||||
- 任一因子接近 0(极冷藏 / 极孤立)→ 整体压低,符合"弱信号一票否决"直觉
|
||||
- 默认 α/β/τ 让 factor 落在 [0.5, 2.0] 区间,不会让 base_score 完全失声
|
||||
|
||||
**已排除**:LLM rerank。它是 query-time 多调一次 LLM,成本高,M0 不引入;留 M1+ 视 dogfooding 决定。
|
||||
|
||||
### 3.3 query_seed_paths 的角色
|
||||
|
||||
community_factor 需要"query 主关注的节点是哪些"才能判断同/异社区。做法:
|
||||
1. RRF 融合后取 top-N(N=3)的 fused chunk 的 path 作 seed
|
||||
2. 后续每个候选 chunk 的 path → 查它和任一 seed 是否同社区 → boost
|
||||
3. 不需要 query 自身被映射到 community(query 是字符串,不在图里)
|
||||
|
||||
**边界**:N=3 是经验起点;N 太大会让"同社区"几乎等于"全召回"失去区分度。dogfooding 后调。
|
||||
|
||||
---
|
||||
|
||||
## 4. semantic 增强:节点级合并(unique_paths)
|
||||
|
||||
**问题(gap 5)**:fused 列表里 top-5 可能是同 file 的 5 个 chunk,信噪比退化。
|
||||
|
||||
**当前**:`expand_links` 已用 `unique_paths = list(dict.fromkeys(c.path for c in fused))`,但 fused 本身没去重,limit=5 仍可全是同节点。
|
||||
|
||||
**新方案**(节点级 dedupe + 节点级 surface):
|
||||
|
||||
```
|
||||
fused (chunk-level) → group by path → 每组保留 top_chunks_per_path 个
|
||||
→ 每组追加节点 frontmatter (name + description) 作"节点级 surface"
|
||||
→ 再按节点 best_score 排序 → limit
|
||||
```
|
||||
|
||||
| 参数 | 默认 | 含义 |
|
||||
|---|---|---|
|
||||
| `top_chunks_per_path` | 2 | 同节点最多保留多少 chunk |
|
||||
| `surface_node` | true | 是否在每组前追加 frontmatter `name + description` |
|
||||
|
||||
**为什么**:
|
||||
- 节点是 retrieve 的语义单位(`auto_dream_design.md` §2 路径即 ID),chunk 只是"展示窗口"
|
||||
- frontmatter 是节点级摘要(name + description)—— 已是 dream 写入时认证过的信号,不召它浪费
|
||||
- 同节点多 chunk 时,frontmatter + top-2 chunk 比 5 个 chunk 信息密度高
|
||||
|
||||
### 4.1 答案展示形态
|
||||
|
||||
```
|
||||
========== digest/auth/jwt-rotation.md ==========
|
||||
[node] JWT Key Rotation
|
||||
Process for rotating JWT signing keys without downtime.
|
||||
[score=0.0241 centrality=2.1 community=1.2 recency=0.91]
|
||||
|
||||
---------- chunk @5-23 ----------
|
||||
<chunk text>
|
||||
|
||||
---------- chunk @45-60 ----------
|
||||
<chunk text>
|
||||
|
||||
[expansion] 1 inbound, 2 outbound (...)
|
||||
```
|
||||
|
||||
**对照旧形态**:每个 chunk 独立成块,无节点级 surface,scores 散在 chunk 头。新形态以**节点为视觉单位**,人 / agent 看到的第一眼是"哪个节点中了",而非"哪段文字中了"。
|
||||
|
||||
---
|
||||
|
||||
## 5. semantic 增强:multi-hop expand
|
||||
|
||||
**问题(gap 4)**:当前 expand_links 只展一跳,跨术语关系("分布式锁" → 一跳到"租约机制",再一跳才到"心跳协议")到不了。
|
||||
|
||||
**新方案**:expand_links 支持 `depth` 参数;默认仍 1(保守),agent / 配置可调到 2。
|
||||
|
||||
| 参数 | 默认 | 限制 |
|
||||
|---|---|---|
|
||||
| `expand_depth` | 1 | 最大 3(避免组合爆炸) |
|
||||
| `max_links_per_direction` | 10(现状)| 每跳每方向上限,深度不展开时限到当跳总数 |
|
||||
| `expand_path_budget` | 30 | 总扩展节点数硬上限,优先深度优先(深度浅但条数少) |
|
||||
|
||||
**为什么默认仍 1**:
|
||||
- 二跳延迟不可忽略(N × 10 × 10 = 100 候选 IO)
|
||||
- agent 需要"再深一层"时显式调 `traverse_step(depth=2)` —— 三种问法分立(R-1)
|
||||
- 默认深拉会让"语义召回"变成"图召回",违背 R-1
|
||||
|
||||
**何时调 2**:dogfooding 发现 workspace 节点平均出度低 / 跨术语关系频繁 → 调到 2(改 search_step 配置,不改协议)。
|
||||
|
||||
---
|
||||
|
||||
## 6. semantic 增强:query rewrite / multi-query
|
||||
|
||||
**问题(gap 6)**:用户 query "JWT 怎么轮换" 可能错过 body 写"密钥定期更换"的节点(术语不同)。
|
||||
|
||||
**方案矩阵**:
|
||||
|
||||
| 方案 | 成本 | 效果 |
|
||||
|---|---|---|
|
||||
| **(a) 不做** | 0 | 漏召部分跨术语 |
|
||||
| **(b) embedding 多 query**(用同 LLM 生成 N 个表述) | LLM 调用 1 次(query → N 表述)+ N 次 vector_search | 中等 |
|
||||
| **(c) BM25 同义词扩展**(用静态词表 / 嵌入式词表) | 0(若有词表) | 弱(中文场景词表缺) |
|
||||
| **(d) HyDE**(LLM 生成假设答案 → 嵌入这个答案而非 query) | LLM 1 次 | 高,文献证实 |
|
||||
|
||||
**首版决策**:**(a) 不做**。理由:
|
||||
- workspace 本身规模 M0 不大,推断增加召回但增 LLM cost 不划算
|
||||
- 维护阶段的 community 聚类已部分弥补"跨术语关系"(同社区 boost)
|
||||
- 真要做,优先 (d) HyDE,延 M1+ 再启,实施只需加一层 query 预处理
|
||||
|
||||
**契约预留**:search_step kwargs 加 `query_rewrite: str | None`(默认 None;非 None 则用此重写代替原 query 做 vector_search,keyword_search 仍用原 query)。SDK 层可调用 LLM 生成重写后传入,reme 核心不强加 LLM 依赖。
|
||||
|
||||
---
|
||||
|
||||
## 7. semantic 增强:archived 过滤
|
||||
|
||||
**问题**:长期未访问的旧节点应该默认排除。
|
||||
|
||||
**方案**:search_step kwargs 加 `include_archived: bool`,默认 false。
|
||||
|
||||
```
|
||||
fused → drop where path in archived_set → 后续打分 / unique_paths
|
||||
```
|
||||
|
||||
**何时绕过**:
|
||||
- agent 显式 `include_archived=true`(找历史 / debug)
|
||||
- query 命中节点本身在 archived → boost 推回(冷节点突然被命中,说明不是真冷)
|
||||
- **首版不做**,过滤即过滤;如有需要,M1+ 加"intent override"机制
|
||||
|
||||
**冷启动**(`meta/archived.json` 缺失)→ 不过滤,等同 `include_archived=true`。
|
||||
|
||||
---
|
||||
|
||||
## 8. topological 问法的小增强
|
||||
|
||||
`traverse_step` 当前完整:BFS / 多 seed / direction / depth / per-edge 输出。本文档不重构,仅:
|
||||
|
||||
### 8.1 起点选择借助维护信号(可选 hint)
|
||||
|
||||
agent 调用 traverse 时往往不知道"哪个节点是该主题的中心";维护阶段产出的 centrality 可作 hint:
|
||||
|
||||
| 用例 | 做法 |
|
||||
|---|---|
|
||||
| traverse 给定 seed | 不变,直接 BFS |
|
||||
| traverse 给定主题字符串(SDK 上层语法糖) | 先 search_step 找 top-1 → 用其作 seed → traverse depth=2 |
|
||||
|
||||
**位置**:这个组合在 SDK 上层做,不进 traverse_step;reme 核心保留 traverse 原子形态。
|
||||
|
||||
### 8.2 traverse 输出消费 archived
|
||||
|
||||
traverse_step 当前不知道 archived 信号。改造:加 `exclude_archived: bool` kwarg 默认 false(traverse 默认不过滤,因为它是图问法,过滤会破坏图视角)。SDK / agent 可显式开启。
|
||||
|
||||
---
|
||||
|
||||
## 9. retrieve 写访问日志(唯一对外写入)
|
||||
|
||||
**问题**:`meta/access_log.json` 的 `last_read` / `last_hit_count_30d` 谁写?
|
||||
|
||||
**约定**:retrieve 命中节点 → 异步 append 到访问日志缓冲区;由 maintain daily batch 聚合写入 `meta/access_log.json`。
|
||||
|
||||
| 路径 | 实现 |
|
||||
|---|---|
|
||||
| **同步写**(每 query) | retrieve 把命中 path 写入内存 ring buffer(进程级)|
|
||||
| **异步落盘** | 进程退出 / 维护 daily batch / 周期 flush(默认 10 min)|
|
||||
| **聚合** | maintain 在 daily access_log 重算时:读 ring buffer + 上一份 access_log → 合并写新版 |
|
||||
|
||||
**幂等**:同 query 多次重读同节点不应放大 last_hit_count;ring buffer 按 (path, day) 去重,每天每节点最多记一次"被读"。
|
||||
|
||||
**降级**:ring buffer 写失败 / flush 失败 → 不影响 retrieve 返回,只是日志少一条;recency 信号略迟。
|
||||
|
||||
---
|
||||
|
||||
## 10. 不变量 / 边界
|
||||
|
||||
| # | 约束 | 含义 |
|
||||
|---|---|---|
|
||||
| **R-1**(继承)| 三种问法分立 | 不合并 list / search / traverse 成单一 verb |
|
||||
| **R-2**(继承)| 默认 `digest > daily > resource`,可覆盖 | search_step 通过 `search_filter` 支持限层 |
|
||||
| **R-3**(继承)| 拓扑问与层无关 | traverse 跨三层(I-4) |
|
||||
| **R-4**(继承)| Provenance 默认 lazy | retrieve 不自动 traverse(R-4);expand_links 是性能优化非语义展开 |
|
||||
| **Re-1**(本文档)| retrieve 不引入 L4 模块 | 增强限定在原子 step 内部 |
|
||||
| **Re-2**(本文档)| retrieve 只读 workspace | 不改 body / frontmatter / 文件位置 |
|
||||
| **Re-3**(本文档)| retrieve 唯一对外写入是 `meta/access_log.json` | 通过 ring buffer + maintain 聚合,不直接写 |
|
||||
| **Re-4**(本文档)| 任一维护信号缺失 → 降级不崩 | `meta/*.json` 缺 → 跳过对应因子,系统始终可用 |
|
||||
| **Re-5**(本文档)| version 不兼容 → 降级 + warning | 不阻断 retrieve |
|
||||
|
||||
---
|
||||
|
||||
## 11. 与其它文档的引用关系
|
||||
|
||||
| 引用 | 来源 |
|
||||
|---|---|
|
||||
| 三种问法 / R-1..R-5 | `structure.md` §4 |
|
||||
| 没有 retriever 模块 | `structure.md` §7.4 |
|
||||
| 节点 / 边 / wikilink 模型 | `auto_dream_design.md` §2 / §3 |
|
||||
| 维护信号契约 | `auto_consolidate_design.md` §11 |
|
||||
| centrality / community / recency / archived 输出 | `auto_consolidate_design.md` §3-§5 |
|
||||
| 路径即 ID | `auto_dream_design.md` §2 |
|
||||
|
||||
---
|
||||
|
||||
## 12. 下一步
|
||||
|
||||
实现进入 `reme/steps/index/` 时,本文档与 `auto_cognition_design.md`(顶层)/ `auto_dream_design.md` / `auto_consolidate_design.md` 共同作为契约依据。
|
||||
|
||||
**search_step 增强(§3-§7)**:
|
||||
- ⏳ **打分公式**:加 centrality_factor / community_factor / recency_factor;config 化 α / β / τ(§3)
|
||||
- ⏳ **节点级合并 + surface**:group-by-path + frontmatter surface + top_chunks_per_path(§4)
|
||||
- ⏳ **multi-hop expand**:`expand_links` 支持 depth 参数,加 `expand_path_budget` 硬上限(§5)
|
||||
- ⏳ **query_rewrite kwarg**:契约预留,reme 核心不强加 LLM(§6)
|
||||
- ⏳ **archived 过滤**:`include_archived` kwarg,默认 false(§7)
|
||||
|
||||
**traverse_step 增强(§8)**:
|
||||
- ⏳ **`exclude_archived` kwarg**(默认 false)
|
||||
|
||||
**信号加载基础设施(§2)**:
|
||||
- ⏳ **`meta/*.json` lazy loader + LRU 缓存 + mtime 失效**
|
||||
- ⏳ **version 校验 + 降级路径 + warning logger**
|
||||
- ⏳ **signals_freshness metadata 暴露**
|
||||
|
||||
**access log 写入路径(§9)**:
|
||||
- ⏳ **进程级 ring buffer**(命中 path 异步 append)
|
||||
- ⏳ **周期 flush + (path, day) 幂等**
|
||||
- ⏳ **maintain daily 聚合接口**(读 ring → 合并旧 access_log → 写新版)
|
||||
|
||||
**性能与回归**:
|
||||
- ⏳ **基准测试**:打分公式启用前后的 召回 P@5 / MRR(用合成 workspace + ground-truth query)
|
||||
- ⏳ **延迟监控**:维护信号读取 + multi-hop expand 的 p50 / p95
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
ReMe新版本V4
|
||||
|
||||
@jinli
|
||||
新版reme是一个自管理的个人知识库。
|
||||
- **记忆分层:** → 记忆按"原始 → 加工"两层组织:`resource/`(原始素材)、`daily/`(日记事件)是只增不删的流水帐;`digest/` 是加工层,下分 `personal/`(个性化)、`knowledge/`(主题知识)、`procedural/`(Agent 任务经验)、`proactive/`(主动洞察)四个固定子目录,写入策略和检索权重各有差异。
|
||||
- **记忆的载体还是 Markdown:** → 所有记忆都是 Obsidian 兼容的 .md 文件——YAML front matter、四种 wikilink(`[[X]]` / `[[X#anchor]]` / `[[X|alias]]` / `![[X]]`)、Dataview 风格 `predicate:: [[X]]` 语义关系全部沿用社区约定。用户可读、可备份、可迁移,对抗黑盒。
|
||||
- **自我管理进化** → 不需要用户手工整理,Agent 在后台自动对于原始素材进行整理和融合,按照记忆的类型(个性化、程序化、知识类)进行分类整理并更新现有逻辑,同时自动Build link让笔记自己长出结构。这一点把 ReMe 同时与"手动建图的 Obsidian"和"扁平存储的Mem0"拉开。
|
||||
- **渐进式检索** → 自我管理进化的产出物不是一堆扁平笔记,而是一张可被**渐进式检索**消费的图:向量 + 关键词 + 图谱三路 RRF 融合,返回时通过 1-hop 邻居 meta 让 Agent"先看目录、再决定要不要展开正文",不像传统 RAG 那样一次性把 top-K 切片塞进上下文。
|
||||
- **被集成而非内置(分发形态)** → ReMe 不做独立 Agent 产品,而是作为**能力**被任意 Harness 调用:SDK 深度集成(qwenpaw / AgentScope)、MCP Tool + skill.md、CLI + skill.md 三条路径并行,记忆跟着用户走,不绑定任何上层框架。
|
||||
|
||||
1. 目标: 构建个人知识库,集成qwenpaw等harness框架中,实现知识/记忆的自进化和自管理,结合graph高效搜索。
|
||||
2. 新的特性:
|
||||
- 支持多种记忆类型,包括个性化记忆、程序化记忆、知识类记忆
|
||||
- [❌ 待补充] 当前 reme 中只有统一的 `FileNode/FileChunk` 抽象(`reme/schema/file_node.py`、`reme/schema/file_chunk.py`),尚未在代码层区分"个性化/程序化/知识类"三类记忆,需要在 schema 与 store 中扩展类型字段或子类。
|
||||
- 支持memory-self-evolving
|
||||
- [❌ 待补充] 没有发现自进化相关的 step/job 实现,目前只有基础的 search/reindex 等 common steps(`reme/steps/common/`)。需要新增 auto-memory/auto-dream 等 step。
|
||||
- 支持markdown之间的链接,构建graph,更好的渐进式展开
|
||||
- [✅ 已实现 → `reme/components/file_chunker/markdown_file_chunker.py`(wikilink 解析 + Dataview 谓词)、`reme/components/file_graph/`(local/nx/neo4j 三种 graph 后端)]
|
||||
- [✅ 已实现 → `reme/steps/common/search.py:109` `_expand_links`、`reme/config/default.yaml:86` `expand_links` 参数(搜索结果可附 outlinks/inlinks 邻居元数据)]
|
||||
3. 工程实现:
|
||||
1. components
|
||||
- 支持backend切换
|
||||
- [✅ 已实现 → `reme/components/component_registry.py`(`R.register(name)` 装饰器 + `R.get(ctype, backend)` 查找);`reme/application.py:51` 通过 `config.components` 中的 `backend` 字段动态构造]
|
||||
- 生命周期管理 start/close
|
||||
- [✅ 已实现 → `reme/components/base_component.py:151` `start()` / `:162` `close()` / `:172` `restart()`,含 `is_started` 幂等保护与 `asyncio.Lock`]
|
||||
- components之间相互调用,支持前序依赖还是啥
|
||||
- [✅ 已实现 → `reme/components/base_component.py:78` `BaseComponent.bind()` 声明依赖(含 `optional`、`default_factory`),`:98` `_resolve_bindings()` 自动注入;`reme/application.py:80` `_topological_order()` Kahn 算法做拓扑排序、检测循环依赖]
|
||||
2. job/step,借鉴自github action
|
||||
- step是最小的执行单元,可以自由使用components,不需要管理生命周期
|
||||
- [✅ 已实现 → `reme/steps/base_step.py`、`reme/steps/common/`(demo/health_check/help/reindex/search/version/stream_demo 等内置 step)]
|
||||
- job是steps的集合,可以自由组合,支持step复用
|
||||
- [✅ 已实现 → `reme/components/job/base_job.py`(顺序执行 step)、`reme/components/job/stream_job.py`(流式 job);`reme/config/default.yaml:5` 通过 yaml 声明 job→steps 组合]
|
||||
- 对外job可以封装cli命令,mcp_tool,http服务接口等
|
||||
- [✅ 已实现(HTTP / MCP / CLI client) → `reme/components/service/http_service.py:35` `_add_job` 把 job 注册成 POST 端点;`reme/components/service/mcp_service.py`;`reme/components/client/http_client.py`、`reme/components/client/mcp_client.py`;`reme/reme.py:27` `main()` 通过 CLI 子命令 `start` / `find_reme` / `<job_name>` 调用 client]
|
||||
3. application:
|
||||
- components的生命周期管理,通过前序依赖构建拓扑图启动应用
|
||||
- [✅ 已实现 → `reme/application.py:115` `_start()` 按拓扑顺序启动所有 component;`:133` `_close()` 反序关闭]
|
||||
- 集成run_job
|
||||
- [✅ 已实现 → `reme/application.py:148` `run_job()` / `:154` `run_stream_job()`]
|
||||
- 集成Service能力对外提供能力
|
||||
- [✅ 已实现 → `reme/application.py:170` `run_app()` 调用 `service.run_app(app=self)`;`reme/components/service/base_service.py`]
|
||||
4. 对外接口:
|
||||
- skill.md + cli方案,通用方案,支持集成到各种harness框架中
|
||||
- [⚠️ 部分实现] CLI 调用通道已具备(`reme/reme.py:17` `call_server()` 通过 `http_client` / `mcp_client` 调任意已注册 job),但 [❌ 待补充] 仓库内未发现 `skill.md` 文件,需要为 Claude Code / 其它 harness 编写 skill 描述文件。
|
||||
- 可以选择agent来启动reme服务(后台)
|
||||
- [❌ 待补充] 未见"由 agent 自动拉起后台 reme 服务"的脚本/约定,需要补充进程托管或 launchctl/systemd 集成方案。
|
||||
- skill.md + mcp-tool方案,通用方案
|
||||
- [⚠️ 部分实现] MCP 服务通道存在(`reme/components/service/mcp_service.py`、`reme/components/client/mcp_client.py`),但 [❌ 待补充] 同样缺 `skill.md` 模板。
|
||||
- 需要手动启动mcp服务
|
||||
- [✅ 已实现 → `reme service` 模式可通过 `reme/config/default.yaml:1` `service.backend: mcp` 切换,`reme/reme.py` `start` 子命令拉起]
|
||||
- sdk集成(qwenpaw集成)
|
||||
- [❌ 待补充] reme 内未见 qwenpaw / agentscope 相关适配代码(仅 `reme/`、`reme_ai/` 旧版有部分逻辑,但已废弃,按记忆 [[feedback_deprecated_directories]] 不应改动)。需要新建 `reme/integrations/qwenpaw/` 之类的模块。
|
||||
- str + 封装AgentscopeTools
|
||||
- [❌ 待补充] 没有 `AgentscopeTools` 包装层。
|
||||
- 集成auto-memory、auto-dream、auto-memory-search的能力
|
||||
- [❌ 待补充] 三个能力均未实现。
|
||||
4. 记忆存储方案:
|
||||
- resource:原始对话日志,上传的文件,原始的html文件等
|
||||
- [❌ 待补充] `reme/application.py:20` 仅创建 `metadata_dir` / `daily_dir` / `knowledge_dir`,未见 `resource_dir` 概念;需要在 `ApplicationConfig` 中加入并落地相应目录与抓取/上传逻辑。
|
||||
- daily:
|
||||
- daily/YYYYMMDD.md:主Agent调用write/edit工具修改,兼容上一版,同时承担了当天其他md的索引
|
||||
- [⚠️ 部分实现] `reme/application.py:23` 已建 `daily_dir`,但目录内 markdown 的"主索引"约定与 write/edit 兼容协议无显式实现,主要靠主 agent 自身行为。需要文档化 + 校验。
|
||||
- daily/YYYYMMDD/{event}.md auto-memory 针对上下文对话,拆分成不同的事件存储,同时在YYYYMMDD.md构建好索引可以链接过来
|
||||
- [❌ 待补充] auto-memory 拆事件的 step / job 不存在。
|
||||
- knowledge:
|
||||
- knowledge/{topic:-personal/agent/financial/work...}/{xxx}.md 在空闲时间整理记忆,按照主题和事件进行分类存储
|
||||
- [⚠️ 部分实现] `knowledge_dir` 已建(`reme/application.py:24`),但"按主题/事件整理"的后台任务、topic 枚举均缺失。
|
||||
- proactive:
|
||||
- proactive/YYYYMMDD.md 待定。如果存在给用户主动推送的能力,这里可以记录每一天agent给用户推荐的分析和心路历程。
|
||||
- [❌ 待补充] 主动推送/proactive 目录与逻辑均未实现。
|
||||
5. Markdown 格式 & Build Graph
|
||||
1. obsidian格式的Markdown文件格式
|
||||
- front matter格式
|
||||
- [✅ 已实现 → `reme/components/file_chunker/markdown_file_chunker.py:313` `frontmatter.loads(...)`;`reme/schema/file_front_matter.py`]
|
||||
- file link格式 4种格式
|
||||
- [⚠️ 部分实现] `markdown_file_chunker.py:88` `_WIKILINK_RE` 已支持 `[[target]]` / `[[target#anchor]]` / `[[target|alias]]` / `![[target]]`(嵌入),并支持 Dataview `predicate:: [[X]]` 与 inline `[predicate:: [[X]]]`。但 [❌ 待补充] 标准 Markdown `[text](url.md)` 链接尚未被解析为 graph 边。
|
||||
2. 更好的文件chunking机制
|
||||
- 旧版 类似rag 带overlap的chunking机制
|
||||
- [📌 历史] V3 旧逻辑,对照说明用,无需在 reme 中实现。
|
||||
- 解析 Markdown Ast
|
||||
- [✅ 已实现 → `markdown_file_chunker.py:308` 使用 `mistletoe` 的 `Document`/`MarkdownRenderer`;`:335` `_build_tree` 把扁平 children 折叠成 section 嵌套树(`MdNode`)]
|
||||
- 每一个chunk都带全部标题
|
||||
- [✅ 已实现 → `markdown_file_chunker.py:381` `_chunk_node`(`before` 累积已经过的标题、`after` 拼剩余 desc_toc);`:712` `_make_chunk` 用 `_toc_join(before, content, after)` 把全文目录骨架前后包裹]
|
||||
3. 通过link构建graph索引,同时构建反向link索引
|
||||
- [✅ 已实现 → `reme/components/file_graph/base_file_graph.py`、`reme/components/file_graph/local_file_graph.py`(含 `get_outlinks`、`get_inlinks` 双向索引);nx/neo4j 后端同 API;`reme/steps/common/search.py:114-129` 使用双向 link]
|
||||
4. link的生成有两种,一种是主agent在生成link;另一种是通过后台任务,自动构建文档之间的link
|
||||
- 介绍如何auto-link
|
||||
- [⚠️ 部分实现] 主 agent 显式写 `[[link]]` 已经会被 parser 抓为边(`markdown_file_chunker.py:152` `_extract_links`)。但 [❌ 待补充] "后台任务自动补 link" 的实现(实体抽取 / 候选文档相似度匹配 / link 写回 markdown)尚不存在,需要单独的 step/job。
|
||||
6. 如何做memory自进化
|
||||
Auto-memory
|
||||
auto-dream
|
||||
- [❌ 待补充] reme 没有 auto-memory / auto-dream 任何代码。需要:
|
||||
- 新增 step(如 `reme/steps/auto_memory.py`、`reme/steps/auto_dream.py`),基于现有 `BaseStep` + LLM component;
|
||||
- 设计触发机制(job 调度、空闲检测);
|
||||
- 与上面的 daily/knowledge 目录约定打通。
|
||||
7. 更好的检索:
|
||||
- 渐进式展开的检索
|
||||
- [⚠️ 部分实现] `reme/steps/common/search.py:14` `SearchStep` 已做 vector + keyword 的 RRF 融合,并支持 `expand_links` 一跳展开(outlinks/inlinks + 邻居 meta)。但 [❌ 待补充] "多跳渐进展开"、"按需要由 agent 主动展开下一层"的交互式 API 尚未实现。
|
||||
8. 结合外部的Agent工具:
|
||||
ReMe更加专注于知识加工,而不是知识获取
|
||||
- 结合qwenpaw
|
||||
- sdk集成(qwenpaw集成)
|
||||
- [❌ 待补充] 见 §3.4。
|
||||
- str + 封装AgentscopeTools
|
||||
- [❌ 待补充] 同上。
|
||||
- 集成auto-memory、auto-dream、auto-memory-search的能力
|
||||
- [❌ 待补充] 同上。
|
||||
- 结合其他的Agent框架
|
||||
- skill.md + cli方案,通用方案,支持集成到各种harness框架中
|
||||
- 可以选择agent来启动reme服务(后台)
|
||||
- [❌ 待补充] 同 §3.4。
|
||||
- skill.md + mcp-tool方案,通用方案
|
||||
- 需要手动启动mcp服务
|
||||
- [⚠️ 部分实现] MCP 服务可启动,但 skill.md 缺失。
|
||||
|
||||
## 更好的性能,更稳定和兼容
|
||||
V4更加高效的底层记忆索引
|
||||
- V3版本基于sqlite/chroma等本地数据库
|
||||
- 在qwenpaw等低版本linux & win系统存在兼容性问题,会存在core dump等问题
|
||||
- 不支持关键词检索,这里需要Keyword倒排索引,对中文的支持较差
|
||||
- [📌 历史] 描述 V3 痛点,不需要代码。
|
||||
- V4版本我们重写了file parser,file store,file graph,file watcher,手写了支持增量更新倒排索引
|
||||
- file parser → [✅ `reme/components/file_chunker/`(base/default/chunked/linked 四种)]
|
||||
- file store → [✅ `reme/components/file_store/local_file_store.py`]
|
||||
- file graph → [✅ `reme/components/file_graph/`(local/nx/neo4j)]
|
||||
- file watcher → [✅ `reme/components/file_watcher/lite_file_watcher.py` 基于 watchfiles awatch;`base_file_watcher.py` 抽象接口]
|
||||
- 增量倒排索引 → [✅ `reme/components/keyword_index/bm25_index.py`(增量 BM25);`reme/components/tokenizer/`(regex / jieba 两种 tokenizer,jieba 含 stopwords 子目录)]
|
||||
- 未来可以使用rust/c++重写,高性能本地知识引擎
|
||||
- [📌 规划]
|
||||
|
||||
## 知识库应用场景(重点)
|
||||
|
||||
### 金融
|
||||
产业链
|
||||
- [❌ 待补充] 没有领域 schema / 产业链知识图谱样例,需要写 demo 数据集 + topic 配置。
|
||||
|
||||
### 自己的工作&生活
|
||||
xxxx
|
||||
- [❌ 待补充] 文档本身就是占位,需要补充具体场景描述与对应的 daily/knowledge 目录样例。
|
||||
|
||||
---
|
||||
|
||||
## 标注小结
|
||||
|
||||
### ✅ 已经在 `reme/` 中实现的能力
|
||||
1. **组件框架**:backend 注册(`component_registry.py`)、生命周期(`base_component.py`)、依赖声明 + 拓扑启动(`application.py:80`)。
|
||||
2. **Job/Step 体系**:`components/job/base_job.py`、`components/job/stream_job.py`、`steps/base_step.py` 与 `steps/common/*`。
|
||||
3. **服务/客户端**:HTTP(`service/http_service.py` + `client/http_client.py`)、MCP(`service/mcp_service.py` + `client/mcp_client.py`),CLI 入口 `reme.py:main`。
|
||||
4. **Markdown 解析**:`file_chunker/markdown_file_chunker.py`,含 frontmatter、wikilink + Dataview 谓词、AST 树、带全标题骨架的 chunking。
|
||||
5. **Graph**:`file_graph/{local,nx,neo4j}_file_graph.py`,双向链接索引。
|
||||
6. **存储 / 索引**:`file_store/local_file_store.py` + `keyword_index/bm25_index.py`(增量 BM25)+ `tokenizer/{regex,jieba}_tokenizer.py`。
|
||||
7. **文件监听**:`file_watcher/lite_file_watcher.py`(watchfiles 轮询)。
|
||||
8. **混合检索 + 一跳展开**:`steps/common/search.py`(vector + keyword RRF 融合,可附 outlinks/inlinks)。
|
||||
9. **Embedding / LLM 适配壳**:`components/embedding/openai_embedding_model.py`、`components/as_llm/`、`components/as_llm_formatter/`、`components/as_token_counter/`。
|
||||
|
||||
### ❌ 需要额外补充的能力
|
||||
1. **记忆类型分层**:个性化 / 程序化 / 知识类的 schema 与路由。
|
||||
2. **memory-self-evolving**:auto-memory(拆事件→ daily/YYYYMMDD/{event}.md)、auto-dream(空闲整理→ knowledge/{topic}/)、对应触发器与调度。
|
||||
3. **存储目录约定**:`resource/`、`proactive/` 目录、daily 主索引协议、knowledge topic 枚举均未落地。
|
||||
4. **auto-link 后台任务**:自动从正文挖出实体并写回 wikilink。
|
||||
5. **多跳渐进展开检索 API**:当前只能一跳。
|
||||
6. **标准 Markdown `[text](url.md)` 链接**:尚未纳入 graph 边解析。
|
||||
7. **skill.md 模板**:CLI 与 MCP 两种集成方式都缺 skill 描述文件。
|
||||
8. **Agent 拉起后台 reme 服务**:缺脚本/约定。
|
||||
9. **qwenpaw / AgentScope SDK 集成**:包括 `AgentscopeTools` 包装层与 auto-memory/dream/search 暴露。
|
||||
10. **应用场景样例**:金融产业链、个人工作&生活的 demo 数据 + topic 配置。
|
||||