Skip to content

fix(deploy): distroless volume perms + viewer proxy + budget loop (#299, #301)#304

Merged
rohitg00 merged 1 commit into
mainfrom
fix/299-301-deployment-shape
May 12, 2026
Merged

fix(deploy): distroless volume perms + viewer proxy + budget loop (#299, #301)#304
rohitg00 merged 1 commit into
mainfrom
fix/299-301-deployment-shape

Conversation

@rohitg00
Copy link
Copy Markdown
Owner

@rohitg00 rohitg00 commented May 12, 2026

Summary

Three deployment-shape fixes reported live by @flamerged on a real production deployment, all closed in this PR. Plus a small budget-loop hardening I noticed while reviewing #288's pinned-slot priority semantic.

#301 — distroless engine can't write to root-owned named volume (volume perm fix)

v0.9.7 moved iii-config.docker.yaml paths from ./data/... to /data/... so the named volume mount is actually reached. But the iiidev/iii image is distroless and runs as UID 65532, while docker volume create initializes the named volume mountpoint as root:root mode 755. The engine has no sh / chown to self-heal at startup, so writes fail Permission denied (os error 13), the API silently buffers in RAM, every call returns success: true, and state evaporates on every container restart — exactly what v0.9.7 set out to fix.

Live repro (before fix, on main)

$ docker compose up -d
$ docker top agentmemory-iii-engine-1
UID  PID    PPID  C   STIME  TTY  TIME      CMD
65532  3258   3235  99  11:44  ?    00:00:49  /app/iii --config /app/config.yaml

$ docker run --rm -v agentmemory_iii-data:/data alpine:3.19 \
    sh -c 'stat -c "%U:%G %u:%g %a" /data'
root:root 0:0 755

$ curl -sX POST localhost:3111/agentmemory/remember \
    -H 'Content-Type: application/json' \
    -d '{"content":"test","project":"/tmp/repro"}'
{"memory":{...},"success":true}     # ← API says success

$ docker run --rm -v agentmemory_iii-data:/data alpine:3.19 \
    sh -c 'find /data -type f; du -sh /data'
                                      # ← (empty)
4.0K  /data                           # ← state never reached disk

Fix

docker-compose.yml now ships an iii-init one-shot service (busybox:1.36, ~4 MB image, exits in <100 ms) that runs chown -R 65532:65532 /data && chmod 755 /data once. The iii-engine service now declares user: "65532:65532" and depends_on.iii-init.condition: service_completed_successfully so it never starts before the volume is owner-correct.

Verified post-fix (live)

$ docker compose up -d
[+] Running 4/4
 ✔ Container agentmemory-iii-init-1   Started
 ✔ Container agentmemory-iii-init-1   Waiting
 ✔ Container agentmemory-iii-init-1   Exited
 ✔ Container agentmemory-iii-engine-1 Started

$ docker run --rm -v agentmemory_iii-data:/data alpine:3.19 \
    sh -c 'stat -c "%U:%G %u:%g %a" /data'
UNKNOWN:UNKNOWN 65532:65532 755     # ← owner-correct

$ curl -sX POST localhost:3111/agentmemory/remember -d '...'
{"success":true,...}

$ docker run --rm -v agentmemory_iii-data:/data alpine:3.19 \
    sh -c 'find /data -type f; du -sh /data'
/data/state_store.db/mem%3Ahealth.bin
/data/state_store.db/mem%3Amemories.bin
44.0K /data                          # ← state persisted through named volume

Why busybox not alpine? ~3 MB smaller image, same sh + chown + chmod surface, identical behaviour for this use.

Migration: existing deployments that already hit the bug should docker compose down && docker compose up -d after upgrading — the init container fixes the volume in place on first run, no manual chown needed anymore.

#299 — viewer dashboard empty behind reverse proxy on 80/443

src/viewer/index.html resolved the REST base URL through:

// before
var viewerPort = params.get('port') || window.location.port || '3113';
var REST = window.location.protocol + '//' + window.location.hostname + ':' + viewerPort;

When the viewer is served on port 80 / 443 (e.g. through nginx / Caddy / Nginx Proxy Manager terminating on a public port), window.location.port is the empty string. The fallback kicks in to the literal '3113', and every browser-side /agentmemory/* fetch hits <host>:3113 — typically loopback-bound on the self-hosted shape, so unreachable from outside. The viewer rendered cleanly but every panel showed the empty "first run" state, even when curl <proxy-host>/agentmemory/sessions (no explicit port) returned correct data.

Fix

// after — when neither ?port= nor window.location.port is set,
// use the same origin the page was served from.
if (paramPort) { /* explicit ?port= path unchanged */ }
else if (locPort) { /* dev path with explicit :3113 unchanged */ }
else {
  REST = window.location.origin;
  WS_URL = wsPort
    ? wsProto + '//' + window.location.hostname + ':' + wsPort
    : wsProto + '//' + window.location.host;
  WS_DIRECT_URL = WS_URL + '/stream/mem-live/viewer';
}

WebSocket gets the same treatment — defaults to window.location.host (whatever port the page was served on) instead of wsPort - 1 so live updates work through the proxy too.

Bundled — mem::context budget loop now continues on oversized blocks

The selection loop in src/functions/context.ts used break when usedTokens + block.tokens > budget. With #288's new pinned-slot injection sorting first via recency: Date.now(), a single fat pinned slot at the top of the list could starve every smaller block downstream that would have fit. Switched to continue so smaller blocks still slip into the remaining budget. Total tokens still bounded by tokenBudget (default 2000); only the composition under contention changes.

Version bump

8 standard files: package.json, packages/mcp/package.json, plugin/.claude-plugin/plugin.json, src/version.ts, src/types.ts (ExportData.version literal), src/functions/export-import.ts (supportedVersions set), test/export-import.test.ts (round-trip expectation), CHANGELOG.md.

Test plan

  • npm test868 / 868.
  • npm run build — tsdown clean.
  • Live reproduce + verify the volume-perm fix (logs above).
  • Tag + GitHub release → publish workflow → npm.

Follow-ups (not in this PR)

  • Open a separate plan to file the broader fix upstream in iii-hq/iii: ship a thin entrypoint script in the engine image that handles UID 65532 ownership at startup, so the init-container dance isn't needed by every downstream consumer.

Summary by CodeRabbit

  • New Features

    • Improved context memory budget selection to continue evaluating smaller blocks after encountering larger entries that exceed the token budget
  • Bug Fixes

    • Fixed Docker Compose data volume write access permissions following container startup
    • Fixed viewer dashboard connectivity issues when deployed behind reverse proxies on standard ports (80/443)
  • Chores

    • Version bumped to 0.9.10

Review Change Stack

…, #301)

@flamerged reported three issues on a real production deployment:

1. (#301) v0.9.7's working-directory fix moved iii-config paths from
   ./data/... to /data/... so the named volume mount is actually
   reached. But iiidev/iii is distroless and runs as UID 65532, while
   `docker volume create` initializes the named volume mountpoint as
   root:root mode 755. Engine writes fail Permission denied (os error
   13), the API silently buffers in RAM, every API call returns
   success, and state evaporates on every container restart — exactly
   what 0.9.7 set out to fix.

   Fix: ship a one-shot iii-init service in docker-compose.yml
   (busybox:1.36, ~4MB, exits in <100ms) that chowns /data to
   65532:65532. iii-engine now has user: "65532:65532" and
   depends_on.iii-init.condition: service_completed_successfully.
   Verified live: pre-fix volume stayed 4.0K after API writes; post-
   fix volume grows to 44K with state_store.db/mem%3A*.bin files
   written through the named volume.

2. (#299) src/viewer/index.html ports detection hardcoded ':3113' as
   the fallback when window.location.port is empty (page served on
   80/443 behind a reverse proxy). Every browser-side /agentmemory/*
   fetch went to <host>:3113, which is typically loopback-only on the
   self-hosted shape — the dashboard rendered cleanly but every
   panel showed the empty "first run" state.

   Fix: when neither ?port=N nor window.location.port is set, use
   window.location.origin as the REST base and window.location.host
   for the WebSocket URL — same-origin path works for both REST and
   live updates. Explicit ?port=N / non-default window.location.port
   paths unchanged.

3. (bundled) mem::context budget loop used `break` on first oversized
   block. With #288's new pinned-slot injection sorting first via
   recency: Date.now(), one fat pinned slot could starve every
   smaller block downstream that would have fit. Switched to
   `continue` so smaller blocks still slip into remaining budget.
   Total tokens still bounded by tokenBudget; only composition under
   contention changes.

Bumping 0.9.9 -> 0.9.10 across the 8 standard files (package.json,
packages/mcp/package.json, plugin/.claude-plugin/plugin.json,
src/version.ts, src/types.ts ExportData literal,
src/functions/export-import.ts supportedVersions, the export
round-trip test expectation, and CHANGELOG.md).

868 / 868 tests pass. Build clean. Volume + viewer fixes verified
end-to-end live.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agentmemory Ready Ready Preview, Comment May 12, 2026 3:10pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 59cd93d7-d68d-4688-9a30-b65d3ca842b7

📥 Commits

Reviewing files that changed from the base of the PR and between 03fb42d and e94d161.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • docker-compose.yml
  • package.json
  • packages/mcp/package.json
  • plugin/.claude-plugin/plugin.json
  • src/functions/context.ts
  • src/functions/export-import.ts
  • src/types.ts
  • src/version.ts
  • src/viewer/index.html
  • test/export-import.test.ts

📝 Walkthrough

Walkthrough

This PR releases version 0.9.10 with three behavioral fixes and infrastructure updates: docker-compose volume permissions initialization, viewer URL construction for reverse-proxy environments on standard ports, and refined context token-budget selection that skips oversized blocks instead of terminating early. All version sources and export-format support are coordinated to the new version.

Changes

0.9.10 Release Coordination

Layer / File(s) Summary
Version metadata and release documentation
src/version.ts, package.json, packages/mcp/package.json, plugin/.claude-plugin/plugin.json, CHANGELOG.md
VERSION constant, package manifests, and plugin manifest updated from 0.9.9 to 0.9.10; CHANGELOG documents three fixes: docker-compose volume permissions, viewer reverse-proxy URL construction, and context-budget selection behavior.
Export format version 0.9.10 support
src/types.ts, src/functions/export-import.ts, test/export-import.test.ts
ExportData.version type union extended to include "0.9.10"; mem::import validation recognizes 0.9.10 as supported export format; test assertion updated to verify exported version is 0.9.10.
Context token-budget selection refinement
src/functions/context.ts
registerContextFunction now skips context blocks exceeding budget using continue instead of break, allowing smaller subsequent blocks to remain eligible for selection.
Viewer REST/WebSocket URL construction for reverse proxies
src/viewer/index.html
Runtime initialization detects port from query parameters or window.location.port; resolves effective REST port (3111 → 3113), computes wsPort, and builds WS_DIRECT_URL for /stream/mem-live/viewer with fallback to window.location.origin/host.
Docker-compose volume permissions and user configuration
docker-compose.yml
Added iii-init one-shot BusyBox service to fix iii-data volume ownership (chown 65532:65532) and permissions (chmod 755); iii-engine runs under UID/GID 65532:65532 and depends on iii-init completion.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • rohitg00/agentmemory#272: Both PRs modify version-related code paths (src/version.ts, src/types.ts, src/functions/export-import.ts) to add new supported release versions.
  • rohitg00/agentmemory#280: Both PRs make overlapping changes to versioning, export-import support, and docker-compose configuration.
  • rohitg00/agentmemory#93: Both PRs modify versioning/export-import files and contain similar context selection logic fixes (switching from break to continue).

Poem

A rabbit hops through versions bright,
From 9.9 to 10's light,
Contexts skip what's much too grand,
Viewers proxy across the land,
And docker-compose now plays so fair—
🐰 permissions blooming everywhere!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 three main fixes included in the PR: distroless volume permissions, viewer proxy URL handling, and the budget loop fix, with issue references.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/299-301-deployment-shape

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.

@rohitg00 rohitg00 merged commit 5ea01c1 into main May 12, 2026
5 checks passed
@rohitg00 rohitg00 deleted the fix/299-301-deployment-shape branch May 12, 2026 15:19
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