docs: add comprehensive documentation for auto-dream, auto-link, and auto-resource flows (#343)

This commit is contained in:
Sen Huang 2026-07-14 15:34:15 +08:00 committed by GitHub
parent b5e0ec2d8d
commit 8042f74b6f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 2495 additions and 33 deletions

View file

@ -33,6 +33,8 @@ the code, schema, tests, configuration, and concise documentation together as ne
- `reme/reme.py`: CLI entry point and client/server dispatch.
- `reme/application.py`: application assembly, dependency ordering, and lifecycle.
- `reme/components/application_context.py`: application-wide wiring and shared in-memory metadata.
- `reme/components/runtime_context.py`: scratch state shared by steps within one execution.
- `reme/config/default.yaml`: built-in jobs, components, and defaults.
- `reme/schema/`: public and runtime Pydantic contracts.
- `reme/components/`: services, stores, clients, jobs, and component registration.
@ -79,6 +81,34 @@ Do not silently change stable CLI flags, configuration keys, workspace layouts,
schemas, or service interfaces. When such a change is required, preserve compatibility
where practical and make the migration explicit.
## Step State Model
Treat every Step as stateless. `BaseJob` stores Step specifications and builds fresh Step
instances for each Job invocation. A Step instance must not use `self` or class variables to
retain mutable runtime state between calls.
Place state according to its lifetime:
- Constructor fields on `self`: immutable Step configuration and resolved dependencies only.
- `self.context` (`RuntimeContext`): request data and intermediate results for one Job
execution; sequential Steps share this context.
- `self.app_context.metadata`: in-memory state that must be shared across Step or Job
invocations for the lifetime of the Application.
- Workspace files or a dedicated Component/store: durable state that must survive an
Application restart.
Use narrow, namespaced keys in `app_context.metadata`, following existing patterns such as
`tool_contexts` and `channel_sink`. The ApplicationContext is shared, so account for
concurrent access when values are mutable. New Step code must not fall back to `self.kwargs`
or another Step field to emulate shared state when `app_context` is absent; tests of shared
state should construct an `ApplicationContext`. If shared state grows into a stable
service-level contract or needs its own lifecycle, locking, or persistence, promote it to a
typed ApplicationContext field or a dedicated Component instead of expanding an ad hoc
metadata bucket.
Do not use `Response.metadata` as a state store. It is request-scoped output for callers and
diagnostics, distinct from `ApplicationContext.metadata`.
## Validation
Use the narrowest useful check while iterating, then broaden it according to risk.

View file

@ -1,7 +1,209 @@
# Auto Dream
```{note}
📖 The English version of this page is in progress.
`auto_dream` is ReMe's long-term memory distillation flow from daily to digest. It scans daily inputs for a specified date,
processes only files that changed since the previous dream, extracts content worth retaining as memory units, integrates those
units into `digest/`, and generates the day's `interests.yaml` for proactive use.
In the meantime, please read the <a href="../zh/auto_dream.html">中文版本</a>.
<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/`, `derived_from::`, 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: ""
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
- 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. |
| `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 for the specified date:
```text
daily/<date>.md
daily/<date>/**/*.md
```
`daily/<date>/interests.yaml` is excluded from extraction input so topics from the previous run do not feed back into the
next extraction.
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 the day's index page at `daily/<date>.md`.
2. Scan `daily/<date>.md` and `daily/<date>/**/*.md` and compare their mtimes with `file_catalog: dream`.
3. Send only changed files 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`.
`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, the flow ends early with success and skips later extraction work. 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.
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/<previous-date>/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 `daily/<date>/interests.yaml` and `daily/<date>.md` 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"
```
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 details point back to sources
through `derived_from:: [[daily/<date>/...]]`. 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.

View file

@ -1,7 +1,144 @@
# Auto Link
```{note}
📖 The English version of this page is in progress.
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.
In the meantime, please read the <a href="../zh/auto_link.html">中文版本</a>.
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 | Point back to daily/resource source material with `derived_from:: [[...]]`. |
| 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 a new `derived_from:: [[...]]` 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 `derived_from` entries. This prevents
later graph indexing and retrieval from losing edges.
### 3. Write source edges
Source edges use Markdown wikilinks:
```markdown
derived_from:: [[daily/2026-06-20/session.md]]
derived_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. For the complete parsing rules, see
[Memory as File](./memory_as_file.md#wikilink).
### 4. Write relationships between digest nodes
Relationships between digest nodes also use complete workspace-relative paths:
```markdown
relates_to:: [[digest/wiki/hybrid-search.md]]
depends_on:: [[digest/procedure/rebuild-index.md]]
blocks_on:: [[digest/personal/team-review-preference.md]]
```
Predicates are open-ended. Common forms include `relates_to::`, `depends_on::`, and `blocks_on::`. The predicate sits outside
the brackets, while the target path goes inside `[[...]]` and should include the `.md` suffix.
## 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.

View file

@ -1,7 +1,109 @@
# Auto Memory
```{note}
📖 The English version of this page is in progress.
Auto Memory is ReMe's entry point for conversational memory. Each conversation is first distilled into a daily memory card
identified by `session_id`, and the day's `YYYY-MM-DD.md` page then indexes all of those cards. It turns "we talked about it"
into "it was remembered" while preserving the original conversation as evidence.
In the meantime, please read the <a href="../zh/auto_memory.html">中文版本</a>.
<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/<session_id>.md # one card per conversation
├─ step 2: daily/YYYY-MM-DD.md # daily index linking the cards
└─ source: session/dialog/<session_id>.jsonl # original conversation
```
## 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/
session-a.md
session-b.md
```
`daily/2026-06-20/session-a.md` and `daily/2026-06-20/session-b.md` are memory 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 records that conversation separately under the given ID:
```text
daily/2026-06-20/session-a.md
```
This keeps different conversations separate. A requirements discussion, a debugging session, and a documentation update can
each have their own memory card. To see what happened on a particular day, start with `YYYY-MM-DD.md`. To inspect what was
distilled from one conversation, open the corresponding `<session_id>.md`.
## Preserving the Original Information
The distilled daily note is optimized for readability; the original conversation is retained for trust and verification.
While generating memory cards, Auto Memory also saves the raw sessions:
```text
session/
dialog/
session-a.jsonl
session-b.jsonl
```
Each daily note points to its corresponding original conversation. When a memory needs verification, follow that link back to
the complete context in which it was created.
## Message Timestamps
Auto Memory preserves each message's `created_at` in both the prompt and the raw session 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 date of the earliest valid `created_at` value 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).

View file

@ -1,7 +1,101 @@
# Auto Resource
# Auto Resource `Beta`
```{note}
📖 The English version of this page is in progress.
Auto Resource is ReMe's entry point for interpreting resources and is currently in **Beta**. Resource files first enter
`resource/` by date 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.
In the meantime, please read the <a href="../zh/auto_resource.html">中文版本</a>.
<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. Resources must be placed under a date, which determines
the day whose daily memory layer receives the interpreted card.
Example directory:
```text
workspace/
resource/
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 under `resource/YYYY-MM-DD/`. 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). To search original resources, daily cards, and digest nodes, use
[Memory Search](./memory_search.md).

View file

@ -1,7 +1,221 @@
# Contributing
# Open Source and Contributing
```{note}
📖 The English version of this page is in progress.
ReMe is open source and hosted on GitHub:
In the meantime, please read the <a href="../zh/contributing.html">中文版本</a>.
**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 ".[dev,full]"
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.

View file

@ -1,7 +1,786 @@
# Framework
# ReMe Framework
```{note}
📖 The English version of this page is in progress.
## 1. Overview
In the meantime, please read the <a href="../zh/framework.html">中文版本</a>.
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
config/
default.yaml # default service / jobs / components
config_parser.py # config=, dot notation, and env placeholder parsing
components/
component_registry.py # global registry R
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 / default text chunking
file_catalog/ # change checkpoints
as_llm/, as_embedding/ # model wrappers
agent_wrapper/ # AgentScope / Claude Code wrappers
steps/
base_step.py # BaseStep, Ref, dispatch_steps
common/ # version, help, health_check, demo
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/ingest
channel/ # MCP channel tools
```
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/ # agent sessions and original conversations
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:
```mermaid
flowchart LR
A["Service.build_service(app)"] --> B["read app.context.jobs"]
B --> C{"job.enable_serve == true?"}
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. |
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. |
## 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.
### 4.2 Registration Through Module Imports
Registration happens when a module is imported. `reme/components/__init__.py` imports component packages, while
`reme/steps/__init__.py` imports `channel/common/evolve/file_io/index/transfer`. Each package's `__init__.py` then imports
its concrete modules, causing `@R.register(...)` to execute.
After adding a Step file, make sure the package's `__init__.py` imports it. Otherwise, the backend will not appear in the
registry.
### 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 --> Base["base<br/>version / help / health_check<br/>search / node_search / traverse / reindex<br/>read / write / edit / delete / move / list / stat<br/>daily_list / daily_reindex / daily_write<br/>auto_memory / 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` | `${EMBEDDING_BACKEND:-openai}` | Embedding model wrapper. |
| `embedding_store` | `default` | `local` | Embedding store depending on `as_embedding: default`. |
| `as_llm` | `default` | `${LLM_BACKEND:-openai}` | LLM model wrapper. |
| `agent_wrapper` | `default` | `agentscope` | AgentScope wrapper. |
| `agent_wrapper` | `claude_code` | `claude_code` | Claude Code wrapper. |
| `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` | `default` | `default` | Default text chunking, currently supporting `jsonl`. |
| `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`.

View file

@ -1,7 +1,373 @@
# Memory as File
```{note}
📖 The English version of this page is in progress.
ReMe's core idea is **Memory as File, File as Memory**.
In the meantime, please read the <a href="../zh/memory_as_file.html">中文版本</a>.
<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. It lives in Markdown files, resource files, and
index snapshots under the workspace. Users and agents can directly read, write, move, and delete those files.
**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 sources through `derived_from:: [[...]]`. |
| 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
raw input -> session/ + resource/
working memory -> daily/
long memory -> digest/
system state -> metadata/
```
Each layer solves a different problem.
`session/` and `resource/` preserve raw input. Their purpose is to retain the original situation: conversations, agent
sessions, uploaded material, web pages, and reports remain intact as evidence for later verification.
`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/ # raw input layer; original conversations and agent sessions
│ ├── dialog/
│ │ └── <session_id>.jsonl # conversation messages saved by auto_memory
│ ├── agentscope/
│ │ └── <session_id>.jsonl
│ └── claude_code/
│ └── <session_id>.jsonl
├── resource/ # raw input layer; original external material
│ └── YYYY-MM-DD/
│ └── <resource>.<ext>
├── daily/ # lightly processed layer; facts, conversation summaries, and resource interpretations by date
│ ├── YYYY-MM-DD.md # index page for the day
│ └── YYYY-MM-DD/
│ ├── <session_id>.md # daily note distilled from a conversation
│ ├── <resource_stem>.md # daily note distilled from a resource
│ └── 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/<session_id>.md
-> digest/personal | digest/procedure | digest/wiki
external resource
-> resource/YYYY-MM-DD/<resource>.<ext>
-> daily/YYYY-MM-DD/<resource_stem>.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 raw input, and `auto_dream` extracts and integrates digest nodes from daily.
## 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.
upstream:: [[digest/wiki/polysilicon.md]]
[company:: [[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.
derived_from:: [[daily/2026-06-20/session-a.md]]
related:: [[digest/procedure/technical-documentation.md]]
```
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. Typed wikilinks such as `derived_from::` and `related::` 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
[[digest/wiki/solar.md]]
[[digest/wiki/solar.md#supply-chain]]
[[digest/wiki/solar.md|solar]]
![[resource/2026-06-01/report.md]]
```
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.
Wikilinks support these behaviors:
```text
body link -> create a FileLink
predicate:: link -> create a FileLink with a relationship name
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
```
Supported relationship forms:
```markdown
industry:: [[digest/wiki/new-energy.md]]
[competitor:: [[digest/wiki/byd.md]]]
```
Parsed result:
```text
FileLink
source_path = current file
target_path = digest/wiki/new-energy.md
predicate = industry
```
### Sources and Relationships
The two most important link types in ReMe are source links and conceptual relationship links.
A source link explains where a long-term memory came from:
```markdown
derived_from:: [[daily/2026-06-20/session-a.md]]
derived_from:: [[resource/2026-06-20/report.pdf]]
```
A conceptual relationship link explains which other long-term memories relate to the node:
```markdown
related:: [[digest/wiki/solar-supply-chain.md]]
depends_on:: [[digest/procedure/research-report-analysis.md]]
contrasts_with:: [[digest/wiki/central-inverter.md]]
```
Ordinary body wikilinks also create graph edges, but when the relationship itself has semantic value, prefer
`predicate:: [[path]]`. This makes the meaning of links clearer to search, graph traversal, and later agent integration.
## 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,
`derived_from:: [[...]]` 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/session-a.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.

View file

@ -1,7 +1,207 @@
# Memory Search
```{note}
📖 The English version of this page is in progress.
Memory Search is ReMe's memory retrieval entry point. It continuously builds files under `daily/`, `digest/`, and `resource/`
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.
In the meantime, please read the <a href="../zh/memory_search.html">中文版本</a>.
<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 three memory directories:
- `daily_dir`: daily working memory and session memory cards generated by Auto Memory.
- `digest_dir`: long-term distilled digest nodes.
- `resource_dir`: external resources or imported material.
The default suffixes are `md` and `jsonl`. Markdown uses the `markdown` chunker, which parses frontmatter, heading structure,
and `[[wikilinks]]`. JSONL uses the `default` chunker and creates overlapping chunks by byte size.
## How the Index Is Built
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, resource_dir ]
watch_suffixes: [ md, jsonl ]
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 `[[...]]` into `FileNode`, `FileChunk`, and `FileLink`
objects. For detailed chunking rules, see [Memory as File](./memory_as_file.md#memory-chunking).
## 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.
## 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
steps:
- backend: search_step
vector_weight: 0.7
candidate_multiplier: 3.0
expand_links: true
max_links_per_direction: 10
```
Call it with:
```bash
reme search query="recent discussions about indexing" limit=5
```
`search_step` executes in this order:
```mermaid
flowchart LR
A["query + limit"] --> B["candidates = 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`, allowing the caller to read the source precisely with `read`.
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, predicate, 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/session-a.md:12-28 [score=0.0317 keyword=4.8120] ==========
...matched memory fragment...
outlinks (2):
-> digest/indexing.md name="Indexing" description="..."
via predicate=related
inlinks (1):
<- daily/2026-06-19.md name="..."
via plain
```
`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`.

View file

@ -1,7 +1,138 @@
# Proactive
```{note}
📖 The English version of this page is in progress.
`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`:
In the meantime, please read the <a href="../zh/proactive.html">中文版本</a>.
```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 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` writes these values to 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 looks like:
```text
Read 3 proactive topic(s) from daily/2026-06-20/interests.yaml
```
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.

View file

@ -1,7 +1,214 @@
# Quick Start
```{note}
📖 The English version of this page is in progress.
## Installation
In the meantime, please read the <a href="../zh/quick_start.html">中文版本</a>.
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 ".[core]"
```
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 list
```
`reme list` lists server actions. Ordinary commands invoke server Jobs over HTTP.
---
## 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/ # agent sessions and original conversations
├── 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
ReMe indexes Markdown under the daily, digest, and resource 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 name `list` is used by the CLI to list actions, so the file-listing Job must be called over HTTP:
```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/`, 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
```