Skip to content

feat(cli): install iii-engine binary first, Docker opt-in#396

Merged
rohitg00 merged 2 commits into
mainfrom
feat/cli-installer-first
May 15, 2026
Merged

feat(cli): install iii-engine binary first, Docker opt-in#396
rohitg00 merged 2 commits into
mainfrom
feat/cli-installer-first

Conversation

@rohitg00
Copy link
Copy Markdown
Owner

@rohitg00 rohitg00 commented May 15, 2026

Summary

  • Install pinned iii-engine v0.11.2 binary by default on first run instead of falling back to Docker compose
  • Docker compose is now opt-in via AGENTMEMORY_USE_DOCKER=1, an explicit prompt choice, or auto-fallback when install fails
  • New agentmemory stop command with SIGTERM → SIGKILL escalation (iii ignores SIGTERM) and lsof :3111 fallback
  • Pidfile written to ~/.agentmemory/iii.pid from spawnEngineBackground so stop doesn't have to grep ps

Why

Cold-start npx @agentmemory/agentmemory could spend 15s waiting for /livez against nothing when Docker was the only fallback. docker compose up -d returns 0 even when the underlying daemon is degraded — most recently hit on Rancher Desktop, where the engine never starts but the CLI thinks it did and times out with a misleading "engine started but REST never responded".

The native iii binary path was already implemented in runUpgrade (curl + tar to ~/.local/bin), it just wasn't wired into first-run. This PR extracts it into runIiiInstaller() and makes it the default tier.

Persistence is unchanged: detached: true + child.unref() means memories keep flowing after npx exits. A second npx short-circuits at isEngineRunning(). agentmemory stop is the explicit teardown.

New fallback chain (cli.ts:271)

  1. iii on PATH → start
  2. ~/.local/bin/iii / fallback paths → start
  3. clack p.select { install / docker / manual }
    • non-TTY / CI=1 auto-picks install
    • AGENTMEMORY_USE_DOCKER=1 auto-picks docker
  4. Docker compose only via opt-in or post-install fallback
  5. fail-loud install instructions

Behaviour matrix

Env First run picks
Interactive TTY, no iii, no opt-in clack prompt (default = install)
Interactive TTY, AGENTMEMORY_USE_DOCKER=1 Docker
CI / non-TTY auto-install, Docker fallback on installer fail
Windows print manual instructions (no auto-install — zip asset)
iii --version matches IIPINNED_VERSION on second run short-circuit at isEngineRunning(), no prompt

agentmemory stop

$ agentmemory stop
┌  agentmemory stop
│
◇  Stopped pid 92387
│
└  Stopped. Memories persisted to disk; restart anytime with: npx @agentmemory/agentmemory
  • Reads ~/.agentmemory/iii.pid first, falls back to lsof -i :3111 -t
  • SIGTERM with 3s wait, then SIGKILL (iii doesn't handle SIGTERM cleanly today)
  • Clears pidfile on success; short-circuits with Nothing to stop when port closed

Test plan

  • npm test — 903/903 passing
  • npm run build — clean (44.41 kB cli.mjs)
  • node dist/cli.mjs --help — shows new stop command and env vars
  • node dist/cli.mjs stop against a running iii engine on :3111 — SIGTERM → SIGKILL, port released
  • node dist/cli.mjs stop against no engine — short-circuits with "Nothing to stop"
  • Cold install path: nuke ~/.local/bin/iii, run npx @agentmemory/agentmemory, expect clack prompt → install → start
  • Warm: re-run, expect short-circuit at isEngineRunning()
  • Rancher Desktop: PATH includes ~/.rd/bin/docker but no iii; expect clack prompt (not Docker)
  • CI: CI=1 npx @agentmemory/agentmemory non-interactively installs binary
  • Opt-in Docker: AGENTMEMORY_USE_DOCKER=1 npx ... keeps current Docker behaviour

Summary by CodeRabbit

  • New Features

    • Added a stop command to manage and terminate engine processes
    • Added an auto-installer for the engine with version handling
  • Improvements

    • Updated OS-specific installation guidance
    • Improved engine startup, installation and upgrade flows
    • Added documentation for relevant environment variables

Review Change Stack

Reorder startEngine() fallback so the native iii binary is the happy
path. Docker compose becomes opt-in via AGENTMEMORY_USE_DOCKER=1, an
explicit prompt choice, or CI mode falls back to it only when
auto-install fails. Fixes a class of "engine started but REST never
responded" failures observed on Rancher Desktop, where docker compose
returns 0 on a degraded pull and we wait 15s for /livez against
nothing.

New tier order:
  1. iii on PATH
  2. ~/.local/bin/iii / fallback paths
  3. clack select (install / docker / manual) -- auto-install in CI
  4. Docker compose only via explicit opt-in
  5. fail-loud with install instructions

Extracts the installer logic out of runUpgrade into runIiiInstaller()
so first-run and upgrade share the same pinned-v0.11.2 curl+tar path.

Adds an `agentmemory stop` command:
  - writes ~/.agentmemory/iii.pid from spawnEngineBackground
  - SIGTERM with 3s wait, escalates to SIGKILL (iii ignores SIGTERM)
  - lsof :3111 fallback when pidfile is stale or missing
  - clears pidfile on success

Engine persistence is unchanged: `detached: true` + `child.unref()`
means memories keep flowing after npx exits. A second `npx` short-
circuits at isEngineRunning() before re-prompting.

Tightens installInstructions() copy: drops the AGENTMEMORY_III_VERSION
rationale tail (now in --help) and reorders so the curl path leads,
Docker is path B with the opt-in env var.

Help: documents stop, AGENTMEMORY_USE_DOCKER, AGENTMEMORY_III_VERSION.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 15, 2026

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

Project Deployment Actions Updated (UTC)
agentmemory Ready Ready Preview, Comment May 15, 2026 1:44pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 15, 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: f8951471-6aad-40b5-a14d-87170de4a2b0

📥 Commits

Reviewing files that changed from the base of the PR and between 5482aab and 9df8b8e.

📒 Files selected for processing (1)
  • src/cli.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/cli.ts

📝 Walkthrough

Walkthrough

This PR refactors the CLI's iii-engine lifecycle management: it adds version probing, pidfile tracking for non-Docker processes, an auto-installer that downloads pinned releases, rewrites the startup decision logic to interactively choose between Docker/install/manual modes based on environment and TTY status, updates install guidance, simplifies the upgrade flow, and introduces a new stop command for graceful process shutdown with signal escalation.

Changes

Engine Lifecycle and Installation Management

Layer / File(s) Summary
Documentation and imports
src/cli.ts (lines 9–108)
Expand filesystem imports and add new help text for the stop command and environment variable documentation for AGENTMEMORY_USE_DOCKER and AGENTMEMORY_III_VERSION.
Engine helpers and auto-installer
src/cli.ts (lines 228–366)
Implement pinned-release URL helpers, iiiBinVersion probing, pidfile/state read/write/clear helpers, docker-compose discovery, and runIiiInstaller() that attempts an auto-install of the pinned iii-engine release with platform/asset compatibility checks and structured failures.
Engine startup and pidfile tracking
src/cli.ts (lines 390–556)
Refactor startEngine() decision flow to compute Docker opt-in and interactivity, prompt or auto-install as appropriate, add startIiiBin() and non-Docker pidfile persistence, clear pidfile/state on abnormal non-Docker exit, and update early-crash failure classification by engine type.
Installation instructions and upgrade simplification
src/cli.ts (lines 574–1338)
Refresh installInstructions() messaging for Windows and Linux to match pinned-version install guidance; simplify runUpgrade() so installer confirmation directly calls runIiiInstaller().
Stop command implementation
src/cli.ts (lines 1363–1723)
Add stop command: pidAlive(), signalAndWait() (SIGTERM then SIGKILL escalation), findEnginePidsByPort() using lsof on non-Windows, stopDockerEngine() for compose, runStop() orchestration that stops candidate PIDs, clears pidfile/state, and register stop in the CLI dispatch map.

Sequence Diagrams

flowchart TD
  A["startEngine"] --> B{"AGENTMEMORY_USE_DOCKER set?"}
  B -->|Yes| C["Use Docker compose"]
  B -->|No| D{"Interactive mode<br/>TTY and not CI?"}
  D -->|Yes| E["Prompt user:<br/>install/docker/manual"]
  D -->|No| F{"docker-compose.yml exists?"}
  E -->|install| G["runIiiInstaller"]
  E -->|docker| C
  E -->|manual| H["Set no-engine"]
  G -->|Success| I["startIiiBin<br/>record PID to pidfile"]
  G -->|Fail| J{"Interactive?"}
  J -->|Yes| C
  J -->|No| H
  F -->|Yes| C
  F -->|No| H
  C --> K["Docker compose startup"]
  I --> L["Engine running"]
  H --> M["No engine mode"]
  K --> N["Docker engine running"]
Loading
sequenceDiagram
  participant User
  participant runStop
  participant pidfile
  participant lsof
  participant Process
  User->>runStop: stop command
  runStop->>pidfile: read pidfile
  runStop->>lsof: findEnginePidsByPort (non-Windows)
  runStop->>Process: signalAndWait: SIGTERM
  Process-->>runStop: exit or timeout
  alt timeout
    runStop->>Process: escalate SIGKILL
  end
  Process-->>runStop: confirmed exit
  runStop->>pidfile: clear pidfile
  runStop-->>User: success or error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • rohitg00/agentmemory#128: Overlapping changes to src/cli.ts affecting engine startup/install logic, Docker compose discovery, and installation instructions.
  • rohitg00/agentmemory#260: Related changes around pinned AGENTMEMORY_III_VERSION installs and auto-installer logic.
  • rohitg00/agentmemory#95: Related edits to engine liveness and running-state checks that overlap with this PR's lifecycle/refactor work.

Poem

🐰 A hop, a curl, a tarball in flight,

I probe, I pidfile, I start in the night,
When signals grow weary I gently implore,
Then if needed I kick with a KILL at the door,
The CLI hums content — engines rest, I snore.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.35% 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 clearly and specifically summarizes the main change: making iii-engine binary the default install path with Docker becoming opt-in, which directly corresponds to the core objective of the PR.
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 feat/cli-installer-first

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: 2

🤖 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 `@src/cli.ts`:
- Around line 1376-1415: runStop currently treats any port-bound process as the
engine and will kill host proxies when the engine was started via Docker
compose; modify runStop/readEnginePidfile logic to detect Docker-started engines
and avoid killing host PIDs: when starting via Docker compose, write a small
state file (engine kind and compose file path) alongside the pidfile; in
runStop, check readEnginePidfile() and this state file first—if the state
indicates "docker-compose" or a docker-compose.yml is discoverable and pidfile
is empty, refuse to signal host PIDs and instead either run `docker compose -f
<file> down` (using the stored compose path) or print a clear instruction to run
that command and exit non-zero; keep existing behavior (findEnginePidsByPort +
signalAndWait) only when state shows a non-docker native engine or no compose
file is present, and still call clearEnginePidfile()/clean up state when
shutdown completes.
- Around line 1378-1385: The stop flow currently short-circuits on
isEngineRunning() and clears the pidfile even if a process exists; change the
logic to first call getRestPort(), then call findEnginePidsByPort(port) (or
equivalent PID lookup) in addition to isEngineRunning(), and only call
clearEnginePidfile() and p.outro("Nothing to stop.") when isEngineRunning() is
false AND findEnginePidsByPort(port) returns no candidate PIDs; if candidate
PIDs are found but the HTTP probe failed, preserve the pidfile and surface the
PID(s) so the user can investigate.
🪄 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: 2a46ef2a-881a-4f4c-94df-eeaa64a28de0

📥 Commits

Reviewing files that changed from the base of the PR and between ff3e024 and 5482aab.

📒 Files selected for processing (1)
  • src/cli.ts

Comment thread src/cli.ts
Comment thread src/cli.ts
Two issues caught in PR #396 review:

1. Docker-started engine would let runStop kill host proxies.

   `findEnginePidsByPort` returned every PID holding :3111. When
   the engine was started via `docker compose up -d`, the listening
   socket lives inside the container; on the host, lsof reports the
   Docker socket proxy (`com.docker.backend`, vmnetd, etc.). The
   stop flow would happily SIGTERM/SIGKILL those, taking down
   Docker Desktop networking for every other container the user
   has running.

   Adds an EngineState record at ~/.agentmemory/engine-state.json
   written by startEngine at spawn time (kind=native|docker,
   configPath or composeFile). runStop checks state first:
     - kind=docker: run `docker compose -f <file> down`, never
       signal host PIDs. Verify the docker binary is still on PATH
       and the compose file still exists; otherwise print the exact
       command the user should run.
     - kind=native: existing pidfile + lsof flow.
     - state missing + compose file discoverable + port held + no
       pidfile: refuse to signal blind, point at `docker compose
       down`. Catches engines started by a different shell or older
       agentmemory build that didn't write state.

2. runStop cleared the pidfile when isEngineRunning() returned
   false, even if a stale process was still bound to the port.

   The HTTP probe can fail while the engine is hung, paused
   (SIGSTOP), or in a half-closed state. Clearing the pidfile in
   that case made the next run silently start a second engine on a
   conflicting port. Now we keep the pidfile and surface the live
   PIDs with `ps -p` + `lsof` hints so the user can investigate
   before manual cleanup.

3. Bonus fix: `lsof -i :3111 -t` also returns CLIENT PIDs, not just
   the LISTEN socket owner. The CLI's own keep-alive fetch in
   isEngineRunning() showed up as a client, so signalAndWait would
   SIGKILL its own parent — exit code 137, files never cleaned up.
   Restrict to `-sTCP:LISTEN` and filter out `process.pid` as a
   belt-and-braces guard.

Verified with four scenarios on a real iii engine:
  - native + pidfile + state: SIGTERM → SIGKILL → exit 0, state cleared
  - docker state pointing at missing compose: refusal, state preserved,
    engine untouched
  - paused engine (kill -STOP): preserves pidfile, surfaces PIDs
  - lsof self-pid filter: CLI no longer kills itself

Build: dist/cli.mjs 44.46 kB. Tests: 892/903 (11 pre-existing
mcp-standalone failures unrelated to CLI changes).
@rohitg00 rohitg00 merged commit 9faf737 into main May 15, 2026
5 checks passed
@rohitg00 rohitg00 deleted the feat/cli-installer-first branch May 15, 2026 13:59
@rohitg00 rohitg00 mentioned this pull request May 15, 2026
4 tasks
rohitg00 added a commit that referenced this pull request May 15, 2026
…cker-aware teardown (#401)

Patch bump. No breaking changes to the public API or the existing CLI
subcommands. CLI bootstrap flow changes are additive; existing Docker
users keep working via the new AGENTMEMORY_USE_DOCKER=1 opt-in.

Files bumped (9):
  - package.json
  - packages/mcp/package.json
  - plugin/.claude-plugin/plugin.json
  - plugin/.codex-plugin/plugin.json
  - src/version.ts
  - src/types.ts (ExportData.version union — also adds 0.9.13
    which was missed in the previous release)
  - src/functions/export-import.ts (supportedVersions Set)
  - test/export-import.test.ts
  - CHANGELOG.md

PRs included since v0.9.13:
  #396 — feat(cli): install iii-engine binary first, Docker opt-in;
         agentmemory stop with Docker-aware teardown; LISTEN-only
         lsof filter so the CLI no longer signals its own parent.
  #397 — docs(readme): move OpenHuman after pi in agents grid.
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