app_name: reme-expert enable_logo: false log_to_console: true log_to_file: false # Expert-exposure profile: every memory_* read + write primitive plus # `sync` (event log) are surfaced as MCP tools. The agent picks whatever # it needs — finest granularity, no opinion enforced beyond the # wikilink-uniqueness gate. # # **No `ingest` tool.** Cold-path R-M-W in expert mode is owned by the # host agent (Claude Code, with the `reme-expert` SKILL loaded), not by # reme2's internal ReActAgent. The reme-expert plugin spawns a # `reme-distiller` subagent at SessionEnd to handle distillation in its # own context window, using the same memory_* primitives. # # Use when you want the agent to manage memory directly with full # control. For an opinionated minimal surface (where reme2's internal # Ingestor owns R-M-W), see ./service.yaml. service: backend: mcp transport: stdio sidecar_http: true sidecar_http_host: "127.0.0.1" sidecar_http_port: 8765 sidecar_info_path: "./vault/.reme/sidecar.json" jobs: # -- Write entry points ------------------------------------------------ - backend: base name: sync description: | Hot-path event sync: idempotent upsert of an event FOLDER under `events/{date}/{name}/`. The folder contains the index `{name}.md` (Event schema, frontmatter + narrative + Materials footer) plus any raw materials you pass — conversation snippets, tool outputs, data dumps. The watcher indexes everything inside. CONTINUITY MODEL: pick a stable `name` per logical thread and call `sync` repeatedly through the task. Each call extends the same folder: * new `content` → appended under a `## Update — {iso}` section * new `materials` → siblings (auto-suffix on filename collision) * `topics` + `tags` merged (union) into frontmatter * Materials footer regenerated to list every artifact in the folder First call (folder doesn't exist) → CREATE; subsequent calls with the same `name` while the event is `status: active` → APPEND. If the event is `status: distilled` / `archived`, `sync` REFUSES and returns a `suggested_name` so you start a fresh thread instead of mutating prior cognition. Zero LLM cost. Call CONTINUOUSLY through a task as facts land, and especially at PreCompact to dump verbose raw text into `materials` before context truncation loses it. The folder is the unit `ingest` later reads from. parameters: type: object required: [name] properties: name: type: string description: "kebab-case event identifier (folder + index stem). Reuse the same name across calls in one thread to keep extending the same folder." description: { type: string, description: "one-line summary for index frontmatter (set on initial create only)" } content: { type: string, description: "markdown body. Initial create: the body. Subsequent calls: appended as a `## Update — {iso}` section." } topics: type: array items: { type: string } description: "related topic wikilinks ('[[X]]' or '[[topics/X/X]]'). Unioned into frontmatter on append." tags: { type: array, items: { type: string }, description: "free-form tags; unioned on append." } materials: type: array description: "Raw artifacts written as siblings of the index inside the event folder. Filenames must be safe (letters/digits/dot/underscore/dash). On filename collision with an existing artifact, auto-suffixes (foo.txt → foo-2.txt)." items: type: object required: [filename, content] properties: filename: { type: string, description: "e.g. 'raw-prompt.md', 'tool-output.txt', 'snapshot.json'" } content: { type: string } on_date: { type: string, description: "ISO date for events/{date}/ bucket; defaults to today" } origin_session_id: { type: string, description: "set on initial create only" } steps: - backend: sync # -- Read tools -------------------------------------------------------- - backend: base name: memory_search description: "Hybrid (vector + keyword) search over chunks." parameters: type: object required: [query] properties: query: { type: string } max_results: { type: integer, default: 5 } min_score: { type: number, default: 0.1 } paths: { type: array, items: { type: string } } tags: { type: array, items: { type: string } } exclude_paths: { type: array, items: { type: string } } steps: - backend: memory_search - backend: base name: memory_graph_search description: | Three-way fusion search: vector + keyword + graph (BFS over wikilinks). Pulls in chunks reachable through linked topics/events that pure relevance search would miss. parameters: type: object properties: query: { type: string } max_results: { type: integer, default: 5 } min_score: { type: number, default: 0.0 } graph_depth: { type: integer, default: 1 } seeds: { type: array, items: { type: string } } paths: { type: array, items: { type: string } } tags: { type: array, items: { type: string } } exclude_paths: { type: array, items: { type: string } } steps: - backend: memory_graph_search - backend: base name: memory_get description: "Read frontmatter + body of a single file." parameters: type: object required: [path] properties: path: { type: string } include_chunks: { type: boolean, default: false } steps: - backend: memory_get - backend: base name: memory_list description: | List indexed files filtered by frontmatter exact-match, tags, or path prefix. Returns {items: [{path, metadata}], count}. parameters: type: object properties: metadata: { type: object } tags: { type: array, items: { type: string } } path_prefix: { type: string } limit: { type: integer, default: 100 } steps: - backend: memory_list - backend: base name: memory_backlinks description: "Files linking TO the given path (with edge predicates)." parameters: type: object required: [path] properties: path: { type: string } steps: - backend: memory_backlinks - backend: base name: memory_links description: "Files the given path links to (resolved, with edge predicates)." parameters: type: object required: [path] properties: path: { type: string } steps: - backend: memory_links - backend: base name: memory_resolve_wikilink description: | Resolve a `[[target]]` wikilink to a vault path. Stem-form (`X`) consults the file_store's stem index; path-form (`a/b` or `a/b.md`) is anchored at the vault root. parameters: type: object required: [wikilink] properties: wikilink: { type: string } steps: - backend: memory_resolve_wikilink - backend: base name: memory_count_tokens description: "Estimate token count for a file body or raw text." parameters: type: object properties: path: { type: string } text: { type: string } steps: - backend: memory_count_tokens - backend: base name: memory_lint description: | Read-only projection of the Maintainer's lint findings. Walks the indexed files (optionally restricted to a path prefix) and returns structural issues — broken wikilinks, schema violations, stem collisions. Never mutates. The agent decides what to do with each finding using existing memory_* primitives (rename, update, property_update, archive, delete). parameters: type: object properties: target_prefix: type: string description: "restrict scan to relpaths starting with this prefix (e.g. 'events/2026-05-09/'). Empty string scans the whole vault." steps: - backend: memory_lint # -- Write primitives (raw building blocks) ---------------------------- - backend: base name: memory_create description: | Create a new file (raw primitive — no LLM reasoning). Prefer `ingest` when you want an LLM curator. Two gates run unless `force=true`: (1) path template — must be `topics/{folder}/{name}.md`, `events/{date}/{name}/...`, or `Archive/...`; (2) wikilink uniqueness — `[[stem]]` must resolve to ≤1 path post-create. parameters: type: object required: [path] properties: path: { type: string } metadata: { type: object } content: { type: string } overwrite: { type: boolean, default: false } force: type: boolean default: false description: "bypass BOTH the path-template gate and the wikilink-uniqueness gate. Use only when you intentionally need a non-template path or accept the ambiguity." steps: - backend: memory_create - backend: base name: memory_update description: | Edit-style content update: replace `old_string` with `new_string` in the file body. For frontmatter changes use `memory_property_update`. parameters: type: object required: [path, old_string, new_string] properties: path: { type: string } old_string: { type: string } new_string: { type: string } replace_all: { type: boolean, default: false } steps: - backend: memory_update - backend: base name: memory_property_update description: | Update a single YAML frontmatter key. value=null deletes the key. When key='status', enforces the active → distilled → archived single-direction state machine (skip / reverse refused). Pass `force=true` to bypass. parameters: type: object required: [path, key] properties: path: { type: string } key: { type: string } value: {} force: type: boolean default: false description: "bypass the status state machine (only meaningful when key='status')." steps: - backend: memory_property_update - backend: base name: memory_rename description: | Rename a file and rewrite all incoming `[[wikilink]]` references across the vault. Refuses on destination conflict or stem ambiguity. parameters: type: object required: [old_path, new_path] properties: old_path: { type: string } new_path: { type: string } steps: - backend: memory_rename - backend: base name: memory_delete description: "Delete a file." parameters: type: object required: [path] properties: path: { type: string } steps: - backend: memory_delete - backend: base name: memory_archive description: | Archive a file: flip `status: archived` then move under `//`. parameters: type: object required: [path] properties: path: { type: string } archive_dir: { type: string, default: "Archive" } steps: - backend: memory_archive components: # Ingestor LLM (opt-in). Without this the Ingestor degrades to a # direct create from explicit `target_path`; edits/renames/deletes # require the LLM. Uncomment + provide LLM_API_KEY to enable. # # as_llm: # default: # backend: openai # model_name: ${LLM_MODEL_NAME:-gpt-4o-mini} # api_key: ${LLM_API_KEY} # client_kwargs: # base_url: ${LLM_BASE_URL:-https://api.openai.com/v1} # stream: false # # as_llm_formatter: # default: # backend: openai as_token_counter: default: backend: estimated # Embedding is opt-in: leave embedding_model="" on file_store to run # keyword-only; uncomment + flip to "default" to enable hybrid search. # # embedding_model: # default: # backend: openai # model_name: ${EMBEDDING_MODEL_NAME:-text-embedding-3-small} # dimensions: 1536 # pass_dimensions: false # enable_cache: true # max_batch_size: 10 # max_cache_size: 2000 # max_input_length: 8192 file_parser: md: backend: md default: backend: text file_store: default: backend: local embedding_model: "" store_name: "reme" db_path: "./vault/.reme" working_dir: "./vault" file_watcher: default: backend: full file_store: default default_parser: md recursive: true # Retriever (`hybrid`) is a Step, not a pre-instantiated component — # the `memory_search` / `memory_graph_search` shells build it on # demand. To tune defaults, pass knobs (`vector_weight`, `graph_*`, # …) on the step config under each job below.