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.
To run and use ReMe first, see Quick Start. For workspace file semantics, see Memory as File. User-facing guides for retrieval, automatic memory, and proactive reading are Memory Search, Auto Memory, Auto Resource, Auto Dream, and Proactive.
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.
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. |
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 / 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/ # optional research workflow 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
The default workspace directories are defined by ApplicationConfig:
<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.
The entry point is reme/reme.py::main():
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:
reme start
reme start service.port=8181
reme version
reme search query="memory" limit=5
reme search query="memory" backend=mcpConfiguration 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. |
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.
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-ai-studio package installed by the web and core extras, and source-tree locations such as
website/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.
ReMe uses the process-wide singleton R = ComponentRegistry(). Every component, Job, and Step is registered with
@R.register("name").
from ...components import R
@R.register("version_step")
class VersionStep(BaseStep):
...The registry key is:
(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.
Registration happens when a module is imported. reme/components/__init__.py imports component packages, while
reme/steps/__init__.py imports benchmark/common/cookbook/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.
Dependencies between components are declared with BaseComponent.bind(). At startup,
Application._topological_order() reads every component's dependencies and starts them in topological order.
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. |
Steps do not participate in component topological startup. They are created temporarily for each Job invocation. Steps
access components primarily through BaseStep.Ref:
file_store: BaseFileStore = Ref(BaseFileStore, ComponentEnum.FILE_STORE)
agent_wrapper: BaseAgentWrapper = Ref(BaseAgentWrapper, ComponentEnum.AGENT_WRAPPER, optional=True)Resolution priority:
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:
steps:
- backend: update_catalog_step
file_catalog: resourceHere, file_catalog: resource means to resolve the file_catalog component named resource.
The Application converts configuration into runtime objects and starts and closes them in order.
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():
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.
A Job is the orchestration unit for an externally callable capability or background task. Jobs are configured under
jobs:
in reme/config/default.yaml.
BaseJob is the most common request-oriented Job:
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). |
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. |
BackgroundJob runs long-lived loops such as file watchers. Its constructor forces enable_serve=False.
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.
CronJob extends BackgroundJob with a cron expression:
jobs:
nightly_dream:
backend: cron
cron: "0 3 * * *"
steps:
- backend: dream_extract_step
- backend: dream_integrate_step
- backend: dream_topics_step
- backend: dream_finish_stepThe current implementation uses croniter to calculate the next trigger time. The timezone comes from
app_config.timezone.
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"]
A Step is a concrete business action. Every Step extends BaseStep and implements execute().
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"]
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:
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.responseBaseStep.__call__() invokes RuntimeContext.apply_mapping() before and after execution:
steps:
- backend: some_step
input_mapping:
user_query: query
output_mapping:
result: final_resultThe semantics are to copy context.data[source] to context.data[target].
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:
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:
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"]
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.
Suppose you want to add a Step that converts input text to uppercase.
Create a file such as reme/steps/common/uppercase.py:
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.responseMake sure reme/steps/common/__init__.py imports the new module. Add:
from . import uppercaseThe reason is that @R.register("uppercase_step") only executes after the module is imported.
If a Step needs an existing component, prefer the Refs provided by BaseStep:
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:
steps:
- backend: my_step
file_catalog: dream| 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. |
A Step can be instantiated directly and passed a RuntimeContext:
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"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.
Add the Job under jobs: in a YAML configuration:
jobs:
uppercase:
backend: base
description: "Convert text to uppercase."
parameters:
type: object
properties:
text:
type: string
description: "input text"
required:
- text
steps:
- backend: uppercase_stepStart and call it:
reme start
reme uppercase text="hello"Call chain:
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"]
A Job can chain multiple Steps:
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_step2The first Step writes:
context["processed_query"]
context["adjusted_min_score"]
The second Step reads those fields and writes the final response.
Use backend: stream in configuration:
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_stepExample streaming Step:
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.responseUse backend: background in configuration:
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. |
Use backend: cron in configuration:
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: dreamAn invalid cron expression fails at startup.
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:
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.