Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 41 additions & 2 deletions DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ Response:
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
| `ENGRAM_DATA_DIR` | Override data directory | `~/.engram` |
| `ENGRAM_PORT` | Override HTTP server port | `7437` |
| `ENGRAM_PROJECT` | Default project for `engram serve` `GET /sync/status` when no `project` query param is supplied. When unset, cwd detection is used as the fallback. | cwd-detected project |
| `ENGRAM_PROJECT` | Process-level default project override. For `engram serve`: used as the fallback when `GET /sync/status` receives no `project` query param. For `engram mcp`: sets `MCPConfig.DefaultProject`, which takes precedence over cwd detection for all read and write tools for the lifetime of that MCP process. When unset, cwd detection is used as the fallback. | cwd-detected project |
| `ENGRAM_HTTP_TOKEN` | Optional Bearer auth for the local HTTP server. When set, the following routes require `Authorization: Bearer <token>`: `DELETE /sessions/{id}`, `DELETE /observations/{id}`, `DELETE /prompts/{id}`, `GET /export`, `POST /import`, `POST /projects/migrate`. Comparison is constant-time. Token is read at request time (no restart needed). When unset, all routes are open (zero-config default). | (unset — open) |
| `ENGRAM_TIMEZONE` | Timezone for timestamp display in the TUI and cloud dashboard. Accepts any IANA zone name (e.g. `America/New_York`, `Europe/Berlin`). Falls back to system local time when unset or invalid. | system local |
| `ENGRAM_AGENT_CLI` | LLM runner name used by `engram conflicts scan --semantic` and the HTTP `/conflicts/scan` endpoint. Accepted values: `claude`, `opencode`. | (unset) |
Expand Down Expand Up @@ -1038,7 +1038,7 @@ MCP tools resolve project names at call time using the shared detection chain:
5. Multiple git-repo children of cwd returns `ambiguous_project` with `available_projects`
6. Current working directory basename

The MCP command does not support startup-time `--project` or `ENGRAM_PROJECT` overrides; `engram mcp` only parses `--tools` for MCP tool allowlisting.
`engram mcp` accepts a process-level default project via `--project <name>` / `--project=<name>` or `ENGRAM_PROJECT=<name>`. This override takes precedence over cwd detection for all read and write tools throughout the lifetime of that MCP process. It is a trusted startup-time value — use it when the host cannot supply a reliable cwd (VS Code, WSL, CI, Docker).

### Similar-project warnings

Expand Down Expand Up @@ -1251,6 +1251,45 @@ To unload (stop and disable): `launchctl unload ~/Library/LaunchAgents/com.gentl

> **Note on `brew upgrade`:** launchd does not expand `$HOME` or `~` inside plist values, which is why the template uses literal absolute paths.

### Using Windows Task Scheduler

Windows Task Scheduler is the native service equivalent on Windows. It restarts `engram serve` on login and after reboots, keeping autosync alive without a third-party service manager.

**Setup steps:**

1. Confirm `engram.exe` is in your `PATH`: open PowerShell and run `Get-Command engram`.
2. Set `ENGRAM_CLOUD_TOKEN` (and any other cloud vars) as a **user or system environment variable** in System Properties → Advanced → Environment Variables. Task Scheduler does not inherit session environment variables, so tokens set in your shell profile or in `$env:...` within a PowerShell session will not be visible to the scheduled task.
3. Create the scheduled task by running the PowerShell snippet below in an elevated terminal (Run as Administrator), or import it manually through the Task Scheduler GUI.
4. Verify: after the next login (or trigger manually), run `engram cloud status` — the `Local daemon:` line should report `running on port 7437`.

```powershell
$action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument "-ExecutionPolicy Bypass -WindowStyle Hidden -Command `"Start-Process engram -ArgumentList 'serve' -NoNewWindow`""

$trigger = New-ScheduledTaskTrigger -AtLogOn

$settings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit (New-TimeSpan -Hours 0) `
-RestartCount 5 `
-RestartInterval (New-TimeSpan -Minutes 1) `
-StartWhenAvailable

Register-ScheduledTask `
-TaskName "EngramMemoryServer" `
-Action $action `
-Trigger $trigger `
-Settings $settings `
-RunLevel Limited `
-Description "Engram persistent memory server (engram serve)"
```

> **Environment variables:** `ENGRAM_CLOUD_TOKEN`, `ENGRAM_CLOUD_SERVER`, `ENGRAM_CLOUD_AUTOSYNC`, and `ENGRAM_DATA_DIR` must be set as persistent user or system environment variables (Control Panel → System → Advanced → Environment Variables) so Task Scheduler can read them. Variables you `export` or set with `$env:` in a terminal session are not visible to scheduled tasks.

> **Logs:** To capture stdout/stderr, redirect output in the PowerShell command string, for example: `... -Command "Start-Process engram -ArgumentList 'serve' -NoNewWindow -RedirectStandardOutput '$env:USERPROFILE\.engram\serve.out.log' -RedirectStandardError '$env:USERPROFILE\.engram\serve.err.log'"`. Ensure the log files are opened with UTF-8 encoding (`-Encoding UTF8`) if you post-process them.

> **Stopping the task:** `Stop-ScheduledTask -TaskName "EngramMemoryServer"` or `Unregister-ScheduledTask -TaskName "EngramMemoryServer" -Confirm:$false` to remove it entirely.

---

## Design Decisions
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ Your production engram is fully untouched throughout.
| ------------------------------------------ | --------------------------------------------------------------- |
| `engram setup [agent]` | Install agent integration |
| `engram serve [port]` | Start HTTP API (default: 7437) |
| `engram mcp [--tools=PROFILE]` | Start MCP server (stdio transport) |
| `engram mcp [--tools=PROFILE] [--project NAME]` | Start MCP server (stdio transport) |
| `engram tui` | Launch terminal UI |
| `engram search <query>` | Search memories |
| `engram save <title> <msg>` | Save a memory |
Expand Down
8 changes: 6 additions & 2 deletions cmd/engram/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2437,11 +2437,13 @@ Usage:

Commands:
serve [port] Start HTTP API server (default: 7437)
mcp [--tools=PROFILE]
mcp [--tools=PROFILE] [--project NAME]
Start MCP server (stdio transport, for any AI agent)
Profiles: agent (15 tools), admin (4 tools), all (default, 19)
Combine: --tools=agent,admin or pick individual tools
Example: engram mcp --tools=agent
--project NAME Set process-level default project (overrides cwd detection).
Also accepted as ENGRAM_PROJECT=NAME env var.
tui Launch interactive terminal UI
search <query> Search memories [--type TYPE] [--project PROJECT] [--scope SCOPE] [--limit N]
save <title> <msg> Save a memory [--type TYPE] [--project PROJECT] [--scope SCOPE]
Expand Down Expand Up @@ -2500,7 +2502,9 @@ Commands:
Environment:
ENGRAM_DATA_DIR Override data directory (default: ~/.engram)
ENGRAM_PORT Override HTTP server port (default: 7437)
ENGRAM_PROJECT Default project hint for serve sync status fallback
ENGRAM_PROJECT Process-level default project override.
For "engram serve": fallback for GET /sync/status with no project param.
For "engram mcp": sets DefaultProject, overriding cwd detection for all tools.
ENGRAM_HTTP_TOKEN Optional Bearer auth for local HTTP server (engram serve).
When set, the following routes require Authorization: Bearer <token>:
DELETE /sessions/{id}, DELETE /observations/{id}, DELETE /prompts/{id},
Expand Down
73 changes: 73 additions & 0 deletions docs/AGENT-SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,35 @@ PowerShell fallback test and local override example:

See [Plugins → Claude Code Plugin](PLUGINS.md#claude-code-plugin) for details on what the plugin provides.

### Troubleshooting: Claude Code plugin install on Linux

If `claude plugin install engram` fails on Linux with an error like:

```
EXDEV: cross-device link not permitted
```

this is a Node.js `fs.rename` limitation, not an Engram bug. Node uses `fs.rename` to move the downloaded plugin archive from the system temp directory (`/tmp`) to the plugin destination under your home directory. On many Linux systems `/tmp` and `/home` live on separate filesystems (common with `tmpfs` on `/tmp`), and the kernel rejects cross-device renames.

**One-shot workaround** — set `TMPDIR` to a location on the same filesystem as your home directory before running the install:

```bash
mkdir -p ~/.cache/claude-tmp
TMPDIR=~/.cache/claude-tmp claude plugin install engram
```

**Permanent fix** — add the export to your shell rc file so all future `claude plugin install` commands work without the prefix:

```bash
# ~/.bashrc or ~/.zshrc
export TMPDIR="$HOME/.cache/claude-tmp"
mkdir -p "$TMPDIR"
```

Then reload your shell (`source ~/.bashrc`) and re-run the install.

> This is an upstream Claude Code CLI limitation that affects any plugin installed via `claude plugin install`, not just Engram. Docker-based environments are typically not affected because the container's `/tmp` and `/home` usually share the same overlay filesystem.

---

## Gemini CLI
Expand Down Expand Up @@ -463,6 +492,50 @@ The Memory Protocol tells the agent:

See [Surviving Compaction](#surviving-compaction-recommended) for the minimal version, or [DOCS.md](../DOCS.md#memory-protocol-full-text) for the full Memory Protocol text you can copy-paste.

### Project detection in VS Code, WSL, and CI

VS Code, WSL, and most CI runners start the MCP server process without inheriting the shell's working directory, so cwd-based project detection may resolve to the wrong project or fall back to a directory basename you don't recognise.

The reliable fix is to pin the project explicitly at startup time. Both forms below work:

**Flag form** (recommended — visible in config):

```json
{
"servers": {
"engram": {
"command": "engram",
"args": ["mcp", "--project=my-project", "--tools=agent"]
}
}
}
```

**Environment variable form** (useful when the config format does not support extra args, or when you want to override without editing the config file):

```json
{
"servers": {
"engram": {
"command": "engram",
"args": ["mcp", "--tools=agent"],
"env": {
"ENGRAM_PROJECT": "my-project"
}
}
}
}
```

Both `--project=my-project` and `ENGRAM_PROJECT=my-project` set `MCPConfig.DefaultProject`, which takes precedence over cwd detection for every read and write tool for the lifetime of that MCP process.

> The `--project` flag and `ENGRAM_PROJECT` env var are the same mechanism. If both are supplied, the flag wins. The value must match an existing project name in your Engram store; unknown names are rejected so typos fail loudly instead of silently creating a new project bucket.

Same pattern applies to:
- WSL terminals where VS Code opens a remote window (`\\wsl$\...` paths) — the MCP server process runs inside WSL but VS Code does not forward the workspace directory as cwd.
- CI pipelines (GitHub Actions, GitLab CI, etc.) where the agent runs in a container and the checkout path differs from the project name you use locally.
- Any Docker-based agent host where the container cwd does not match your Engram project name.

---

## Antigravity
Expand Down
99 changes: 89 additions & 10 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,22 +102,101 @@ Token-efficient memory retrieval — don't dump everything, drill in:

## Topic Key Workflow (Recommended)

Use this when a topic evolves over time (architecture, long-running feature decisions, etc.):
### What topic_key is

`topic_key` turns `mem_save` into an **upsert**: if a memory with the same `project + scope + topic_key` already exists, the existing observation is updated in place (`revision_count++`) instead of creating a new row. Without a `topic_key`, every `mem_save` creates a new observation even when the content describes the same evolving topic.

Use topic keys for knowledge that changes over time: architecture decisions, long-running feature notes, recurring patterns, configuration choices. Skip them for one-off bugs, single facts, or anything that does not evolve.

### Format convention

Topic keys follow **slash-separated lowercase kebab-case**:

```
family/specific-description
```

Examples:
- `architecture/auth-model`
- `bug/nil-panic-in-user-list`
- `decision/database-choice`
- `pattern/error-handling-convention`
- `config/ci-environment`

**Why this format?** SQLite FTS5 tokenises on word boundaries. Lowercase kebab-case ensures the key fragments are individually searchable and do not create unexpected FTS5 token splits.

**Anti-patterns to avoid:**

| Anti-pattern | Problem | Correct form |
|---|---|---|
| `authModel` | camelCase breaks FTS5 tokenisation | `architecture/auth-model` |
| `auth model` | spaces create accidental multi-token keys | `architecture/auth-model` |
| `ARCHITECTURE/AUTH` | uppercase is inconsistent with FTS5 normalisation | `architecture/auth-model` |
| `auth/model/v2/final` | more than 2 levels — use `v2` in the description | `architecture/auth-model-v2` |
| `bugfix` | no slash — looks like a family with no description | `bug/auth-nil-panic` |

### Decision table — when to use topic_key

| Situation | Use topic_key? | Reasoning |
|---|---|---|
| Architecture or design decision that may evolve | Yes | Keeps history in one observation, incrementing `revision_count` |
| Long-running feature work (spans multiple sessions) | Yes | Single source of truth across sessions |
| A pattern or convention established for the project | Yes | One canonical entry, updated as the pattern matures |
| Bug fix that was self-contained and is now closed | No | A single observation is fine; no future updates expected |
| One-off discovery or fact | No | Creating a key you will never reuse adds noise |
| Multiple independent decisions on the same broad topic | No — use distinct keys | Different decisions must have different keys or they will overwrite each other |

### The mem_suggest_topic_key-first workflow

When you are not sure which key to use, call `mem_suggest_topic_key` before `mem_save`. It applies a family heuristic based on the observation type and title, returning a suggested key you can use directly or adjust:

```text
1. mem_suggest_topic_key(type="architecture", title="Auth architecture")
2. mem_save(..., topic_key="architecture-auth-architecture")
3. Later change on same topic -> mem_save(..., same topic_key)
=> existing observation is updated (revision_count++)
1. mem_suggest_topic_key(type="architecture", title="Auth model")
→ returns: "architecture/auth-model"

2. mem_save(..., topic_key="architecture/auth-model")
→ creates new observation (revision_count=1)

3. (later session) mem_save(..., topic_key="architecture/auth-model")
→ updates existing observation (revision_count=2)
```

Different topics should use different keys (e.g. `architecture/auth-model` vs `bug/auth-nil-panic`) so they never overwrite each other.
`mem_suggest_topic_key` families:

- `architecture/*` — architecture, design, ADR-like observations
- `bug/*` — bug fixes, regressions, panics, error root causes
- `decision/*` — explicit decisions with tradeoffs
- `pattern/*` — naming conventions, structural patterns, coding standards
- `config/*` — configuration and environment setup
- `discovery/*` — non-obvious findings about the codebase
- `learning/*` — team knowledge and onboarding notes

If none of these families fit, it is usually fine to skip the key and let `mem_save` create a plain observation.

`mem_suggest_topic_key` now applies a family heuristic for consistency across sessions:
### Hierarchical keys — max 2 levels

Keys are organisational only; there is no parent–child relationship in the store. Two levels (`family/description`) cover almost every case. Use the description segment to add specificity rather than adding more slashes:

```
architecture/auth-model ✓ two levels, specific
architecture/auth-model-v2 ✓ version in description
architecture/auth/model/detail ✗ three levels — flatten to two
```

### Lifecycle and pruning

Topic keys are not pruned automatically. An observation updated via upsert keeps a single row with the latest content and an incremented `revision_count`. Use `mem_delete` to remove an observation (soft-delete by default) when a topic is no longer relevant. Soft-deleted observations are excluded from search and context but their IDs remain in the store for audit purposes. Use `--hard` to remove them permanently.

### Scope interaction

`topic_key` upsert is scoped to `project + scope + topic_key`. The same key used with different scopes creates independent observations:

```
project=engram, scope=project, topic_key=architecture/auth-model → observation A
project=engram, scope=personal, topic_key=architecture/auth-model → observation B (independent)
```

- `architecture/*` for architecture/design/ADR-like changes
- `bug/*` for fixes, regressions, errors, panics
- `decision/*`, `pattern/*`, `config/*`, `discovery/*`, `learning/*` when detected
This means a `personal` note on the same topic does not overwrite the shared `project` observation.

---

Expand Down