Skip to content

feat(cli): materialize ~/.agentmemory/config/iii-config.yaml with absolute paths#699

Open
m1dm4n wants to merge 1 commit into
rohitg00:mainfrom
m1dm4n:feat/global-iii-config
Open

feat(cli): materialize ~/.agentmemory/config/iii-config.yaml with absolute paths#699
m1dm4n wants to merge 1 commit into
rohitg00:mainfrom
m1dm4n:feat/global-iii-config

Conversation

@m1dm4n
Copy link
Copy Markdown

@m1dm4n m1dm4n commented May 28, 2026

Problem

Two distinct failures, same root cause: agentmemory's iii-engine config layout is anchored to the engine's working directory instead of to ~/.agentmemory/.

1. Scattered data directories. Bundled iii-config.yaml (top of repo) declares:

file_path: ./data/state_store.db
file_path: ./data/stream_store

When iii --config <path> runs with a different cwd — common under systemd or any launcher that doesn't cd into ~/.agentmemory first — the KV state and stream store land wherever cwd points. A user running agentmemory globally under systemd ended up with three independent data/ dirs (~/Tara/data, ~/data, ~/.agentmemory/data) and lost session history to the unused ones until manual migration.

2. Reload storm → blank viewer. iii's config watcher (Rust notify crate, NonRecursive) watches the PARENT directory of the config file to catch atomic-rename writes. With the config at ~/.agentmemory/iii-config.yaml, every write to sibling files (preferences.json, iii.pid, worker.pid, engine-state.json) triggers a config reload, which drops the worker's HTTP function registrations → 404 on every /agentmemory/* route → blank viewer until restart.

Fix

On first start, materialize ~/.agentmemory/config/iii-config.yaml from the bundled template:

  • Two known relative ./data/ paths are rewritten to absolute paths under ~/.agentmemory/data/ so the engine is location-independent.
  • The config/ subdir is quiet (no sibling pidfiles, no JSON state files written during normal operation), so iii's parent-dir watcher doesn't fire on every routine write.

Helper wired into both call sites:

  • runInit() (explicit agentmemory init flow) — emits a p.log.success line.
  • startEngine() (catches users who skip init; the existsSync short-circuit makes it ~free on every subsequent invocation).

Backwards compatibility

findIiiConfig() resolution order (post-change):

  1. AGENTMEMORY_III_CONFIG env (highest precedence)
  2. <cwd>/iii-config.yaml
  3. ~/.agentmemory/config/iii-config.yamlnew materialized location
  4. ~/.agentmemory/iii-config.yaml ← legacy, kept
  5. __dirname/iii-config.yaml ← bundled
  6. __dirname/../iii-config.yaml

Existing users with hand-rolled ~/.agentmemory/iii-config.yaml keep working unchanged; the legacy candidate is still consulted, just below the new subdir target.

Design notes

  • Idempotent. Helper short-circuits via existsSync(userPath); user edits are preserved on subsequent starts.
  • Shape guard. The relative-path regex only fires when both recognizable bundled markers (file_path: ./data/state_store.db and file_path: ./data/stream_store) are present. Unknown shapes are copied verbatim — we never silently corrupt a user-supplied template.
  • Best-effort. Any failure (read-only $HOME, missing bundled, permission denied) is logged via vlog and returns null. The caller falls through to legacy candidates so the engine still starts.
  • Atomic write. tmp + fsync + rename, mirroring writePrefs() in src/cli/preferences.ts. Parallel callers on first start produce identical content, so the loser of the rename race leaves no half-written file behind.
  • No new dependency. Regex over YAML text rather than adding js-yaml for a two-substring rewrite. A js-yaml round-trip would also strip the substantive bundled comments (the sampling_ratio: 0.1 block documents the 137 GB log feedback loop fix from Runaway daemon.log.new growth from observability subscriber-lagged WARN feedback loop #519).
  • Pure helper isolated for testing. materializeUserIiiConfig() lives in src/cli/iii-config.ts and takes targetDir, dataDir, findBundled, onWarn as args — no implicit dependencies on prefsDir() / __dirname. The thin wrapper in src/cli.ts wires production values; tests inject their own.

Tests

6 new cases in test/cli-iii-config.test.ts:

Case What it asserts
materializes on first call absolute data paths written; header + source comment present; bundled comments survive
idempotent on second call same path returned, mtime unchanged, content identical even if findBundled now returns something different
unrecognized bundled shape copied verbatim, no rewrite, onWarn invoked
no bundled found returns null, no file written
read-only target directory returns null, onWarn invoked, no crash
parallel callers (Promise.all) both return same path, content valid

Full suite: Test Files 4 failed | 114 passed (118) / Tests 8 failed | 1266 passed (1274). The 8 failures (test/auto-compress.test.ts, test/embedding-provider.test.ts, test/mcp-standalone*.test.ts) are present on main at HEAD before this branch is applied — they are pre-existing and unrelated.

How to verify

rm -rf /tmp/am-fixture && mkdir -p /tmp/am-fixture
HOME=/tmp/am-fixture node dist/cli.mjs init
grep file_path /tmp/am-fixture/.agentmemory/config/iii-config.yaml
# →   file_path: /tmp/am-fixture/.agentmemory/data/state_store.db
# →   file_path: /tmp/am-fixture/.agentmemory/data/stream_store

Re-run node dist/cli.mjs init and confirm the materialized file's mtime is unchanged.

Out of scope (possible follow-ups)

  • paths.ts consolidation. ~/.agentmemory/... is joined ad-hoc in ~10 sites (src/cli.ts, src/config.ts, src/cli/preferences.ts, src/index.ts, etc.). Centralizing them into one accessor would touch too many files for this PR; happy to do it as a follow-up if a maintainer wants.
  • iii engine inotify behavior. The parent-dir NonRecursive watch is by design (to catch atomic-rename writes); a fix would need to change either the watch scope or the reload-on-event filter. That belongs in iii-hq/iii, not here. The subdir placement in this PR sidesteps the symptom on the agentmemory side.

Summary by CodeRabbit

Release Notes

  • Documentation

    • Added documentation explaining automatic engine config generation on first start, which creates a user-anchored config file with absolute data paths for consistent KV/streams storage across working directories.
  • New Features

    • Engine now automatically generates configuration file on startup with proper data path handling.
  • Tests

    • Added comprehensive test suite for config generation ensuring idempotency, path rewriting, and error handling.

Review Change Stack

…olute paths

The bundled iii-config.yaml declares `file_path: ./data/state_store.db`
and `file_path: ./data/stream_store` as relative paths. When the
engine spawns with a different working directory — common under
systemd or any launcher that doesn't `cd` into ~/.agentmemory first —
the KV state and stream store land wherever cwd points. A user
running agentmemory globally under systemd ended up with three
independent data dirs (~/Tara/data, ~/data, ~/.agentmemory/data) and
lost session history to the unused ones until manual migration.

On first start, agentmemory now materializes the bundled template to
~/.agentmemory/config/iii-config.yaml with the two known relative
`./data/` paths rewritten to absolute paths under ~/.agentmemory/data/,
making the engine location-independent. The helper is wired into
both `runInit()` (explicit user-facing init flow) and `startEngine()`
(catches users who skip init).

The subdir placement also sidesteps a separate failure mode: iii's
config watcher (Rust `notify` crate, NonRecursive) watches the PARENT
directory of the config file to catch atomic-rename writes. With the
config at ~/.agentmemory/iii-config.yaml, every write to sibling
files (preferences.json, iii.pid, worker.pid, engine-state.json)
triggers a config reload, which drops the worker's HTTP function
registrations and 404s every /agentmemory/* route. The quiet config/
subdir has no such siblings.

Design notes:

- Idempotent: the helper short-circuits if the target file already
  exists. User edits are preserved.
- Shape-guarded: the relative-path regex only fires when both
  recognizable bundled markers are present. Unknown shapes are
  copied verbatim — never silently corrupted.
- Best-effort: any failure (read-only $HOME, missing bundled,
  permission denied) is logged via vlog and returns null. The
  caller falls through to legacy candidates so the engine still
  starts.
- Atomic write: tmp + fsync + rename, mirroring writePrefs() in
  src/cli/preferences.ts.
- No new dependency: uses a regex over the YAML text rather than
  adding js-yaml for a two-substring rewrite. YAML round-trip
  would also strip the substantive bundled comments (the
  `sampling_ratio: 0.1` block documents the 137 GB log feedback
  loop fix from rohitg00#519).

The legacy ~/.agentmemory/iii-config.yaml candidate is kept in the
findIiiConfig() resolution order (just below the new subdir target)
so existing users with hand-rolled top-level configs keep working
unchanged. AGENTMEMORY_III_CONFIG env and cwd-local config still
take precedence.

Tests: 6 new cases in test/cli-iii-config.test.ts cover
materialization, idempotency, unrecognized shape, missing bundled,
read-only target, and parallel callers.

Signed-off-by: m1dm4n <92845822+m1dm4n@users.noreply.github.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 28, 2026

@m1dm4n is attempting to deploy a commit to the rohitg00's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

📝 Walkthrough

Walkthrough

This PR implements first-start generation of a user-anchored iii-config.yaml file that materializes with absolute data paths under ~/.agentmemory/data/, ensuring consistent KV and stream storage across working directories. A new materializeUserIiiConfig() function handles atomic writes with shape-aware path rewriting, and the CLI wires materialization into startup and init flows while updating config search order.

Changes

User-anchored engine config materialization

Layer / File(s) Summary
Config materialization contract and implementation
src/cli/iii-config.ts
MaterializeOptions interface and materializeUserIiiConfig() function materialize user config from bundled template, rewrite relative ./data/... paths to absolute dataDir when bundled shape is recognized, guarantee idempotency and no overwrite of existing user config, and perform atomic temp-file writes with best-effort durability via fsyncSync.
CLI wiring and search-order update
src/cli.ts
Imports materialization utilities; adds findBundledIiiConfig() and ensureUserIiiConfig() wrappers; updates findIiiConfig() to prioritize materialized ~/.agentmemory/config/iii-config.yaml and remove legacy candidates; calls ensureUserIiiConfig() on engine startup and during agentmemory init to ensure user config exists before config resolution.
Tests and documentation
README.md, test/cli-iii-config.test.ts
Test suite validates path rewriting, comment preservation, idempotency, handling of unrecognized shapes, fallback when bundled unavailable, read-only directory error handling, and concurrent-caller safety. README documents first-start materialization, absolute path rewriting under ~/.agentmemory/data/, and config resolution precedence order.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • rohitg00/agentmemory#686: Both PRs modify src/cli.ts's findIiiConfig() behavior by changing how iii-config.yaml is discovered and resolved; this PR additionally introduces config materialization with rewritten absolute data paths.

Poem

🐰 A config materializes with care,
Paths rewritten, data placed just right,
No more drifting through working directories,
One anchored home beneath the light!
Atomic writes keep engines working tight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: materializing the iii-config.yaml file with absolute paths in the ~/.agentmemory/config/ directory.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/cli/iii-config.ts (1)

1-37: ⚡ Quick win

Trim descriptive WHAT-comments in implementation files.

This header is mostly narrative/behavior documentation; keep that in README/ADR and leave only minimal intent comments inline.

As per coding guidelines, "Do not use code comments explaining WHAT — use clear naming instead".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli/iii-config.ts` around lines 1 - 37, Remove the long narrative header
block at the top of src/cli/iii-config.ts and replace it with a very short
intent comment (one or two lines) that states the purpose (e.g., "Materialize
user-anchored iii-engine config to an absolute, engine-independent location").
Keep any necessary mentions of the key behaviors only as succinct notes if
needed, and do not duplicate WHAT-level documentation — leave detailed
rationale/behavior to README/ADR; ensure references to unique symbols like
findBundled, prefsDir(), and writePrefs() remain in code or tests but are not
explained in-line.
src/cli.ts (1)

795-800: ⚡ Quick win

Prefer self-documenting naming over WHAT-comments here.

This added block explains mechanics already reflected by ensureUserIiiConfig() + findIiiConfig() flow; consider removing or shrinking to a short intent note.

As per coding guidelines, "Do not use code comments explaining WHAT — use clear naming instead".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli.ts` around lines 795 - 800, Remove the long WHAT-style comment block
above ensureUserIiiConfig(); if you need any comment at all replace it with a
short intent note (one line) describing why we call ensureUserIiiConfig (e.g.,
"Ensure per-user III config is materialized so findIiiConfig() can locate it")
or rely on the clear function name; do not restate mechanics already expressed
by ensureUserIiiConfig() and findIiiConfig().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/cli-iii-config.test.ts`:
- Around line 151-152: This test uses require("node:fs") to get chmodSync inside
the test body which breaks in ESM; replace the runtime require with a top-level
ESM import (e.g., import { chmodSync } from "node:fs") and remove the inline
require calls, and update any other occurrences (the second require around the
later chmodSync use) so both uses reference the imported chmodSync; keep the
existing calls to chmodSync(targetDir, 0o500) and chmodSync(targetDir, 0o700)
unchanged.

---

Nitpick comments:
In `@src/cli.ts`:
- Around line 795-800: Remove the long WHAT-style comment block above
ensureUserIiiConfig(); if you need any comment at all replace it with a short
intent note (one line) describing why we call ensureUserIiiConfig (e.g., "Ensure
per-user III config is materialized so findIiiConfig() can locate it") or rely
on the clear function name; do not restate mechanics already expressed by
ensureUserIiiConfig() and findIiiConfig().

In `@src/cli/iii-config.ts`:
- Around line 1-37: Remove the long narrative header block at the top of
src/cli/iii-config.ts and replace it with a very short intent comment (one or
two lines) that states the purpose (e.g., "Materialize user-anchored iii-engine
config to an absolute, engine-independent location"). Keep any necessary
mentions of the key behaviors only as succinct notes if needed, and do not
duplicate WHAT-level documentation — leave detailed rationale/behavior to
README/ADR; ensure references to unique symbols like findBundled, prefsDir(),
and writePrefs() remain in code or tests but are not explained in-line.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c9db2a1e-c076-402e-943b-9c4ac2956764

📥 Commits

Reviewing files that changed from the base of the PR and between bfde288 and 378cefd.

📒 Files selected for processing (4)
  • README.md
  • src/cli.ts
  • src/cli/iii-config.ts
  • test/cli-iii-config.test.ts

Comment on lines +151 to +152
const { chmodSync } = require("node:fs");
chmodSync(targetDir, 0o500);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify ESM mode + CJS require usage in this test file.

set -euo pipefail

echo "== package.json module type =="
fd -a '^package\.json$' | while read -r f; do
  echo "-- $f"
  jq -r '.type // "(no type field)"' "$f"
done

echo
echo "== require(...) occurrences in test/cli-iii-config.test.ts =="
rg -n 'require\(' test/cli-iii-config.test.ts

echo
echo "== tsconfig module settings (if present) =="
fd -a '^tsconfig.*\.json$' | while read -r f; do
  echo "-- $f"
  jq -r '.compilerOptions.module // "(no module setting)"' "$f" 2>/dev/null || true
done

Repository: rohitg00/agentmemory

Length of output: 753


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== File header/imports (top 80 lines) =="
sed -n '1,80p' test/cli-iii-config.test.ts | cat -n

echo
echo "== Context around lines 130-190 =="
sed -n '120,190p' test/cli-iii-config.test.ts | cat -n

Repository: rohitg00/agentmemory

Length of output: 5935


Replace require("node:fs") with ESM chmodSync import in test/cli-iii-config.test.ts.

test/cli-iii-config.test.ts is an ESM test (top-level ESM imports) and the repo sets "type": "module", but it uses require("node:fs") inside the test body (lines 151-152 and 165-166). This can fail at runtime in Node ESM (require undefined) and makes the suite sensitive to runner/module mode.

💡 Proposed fix
 import {
   existsSync,
+  chmodSync,
   mkdirSync,
   mkdtempSync,
   readFileSync,
   rmSync,
   statSync,
   writeFileSync,
 } from "node:fs";
@@
-      const { chmodSync } = require("node:fs");
       chmodSync(targetDir, 0o500);
@@
-      const { chmodSync } = require("node:fs");
       chmodSync(targetDir, 0o700);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { chmodSync } = require("node:fs");
chmodSync(targetDir, 0o500);
chmodSync(targetDir, 0o500);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/cli-iii-config.test.ts` around lines 151 - 152, This test uses
require("node:fs") to get chmodSync inside the test body which breaks in ESM;
replace the runtime require with a top-level ESM import (e.g., import {
chmodSync } from "node:fs") and remove the inline require calls, and update any
other occurrences (the second require around the later chmodSync use) so both
uses reference the imported chmodSync; keep the existing calls to
chmodSync(targetDir, 0o500) and chmodSync(targetDir, 0o700) unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant