diff --git a/HARNESS_INTEGRATIONS_CONTRACTS.md b/HARNESS_INTEGRATIONS_CONTRACTS.md deleted file mode 100644 index 927897c7..00000000 --- a/HARNESS_INTEGRATIONS_CONTRACTS.md +++ /dev/null @@ -1,437 +0,0 @@ -# Harness Integrations - Shared Contracts - -Status: **frozen implementation contract** for the harness-integrations -rework. Contributor-facing; not published to `docs.memory.build/`. - -This document resolves the four coordination seams in -`HARNESS_INTEGRATIONS_REQUIREMENTS.md` section 11. Workstreams may implement -against these interfaces without reaching into another workstream's provider -adapter or config parser. - -## 1. Ownership and module layout - -Add these modules under `packages/cli/`: - -``` -harness/ - registry.ts # supported harness metadata, detection, install facade - installations.ts # installations.yaml schema and read/write/update helpers -local-config.ts # config.yaml harness-profile schema, resolver, writers -``` - -Existing files keep these responsibilities: - -- `credentials.ts` continues to own human CLI credential/server/active-space - resolution. It must not import `local-config.ts` to retarget a user command. -- `harness-contract.ts` remains the sole definition of `AI_AGENT` and - `ME_PROJECT_DIR`. -- Provider adapters remain responsible for their native config mutation, but - must report the artifacts they own through `harness/registry.ts`. - -Dependency direction is one-way: - -``` -commands -> harness/registry -> harness/installations -commands -> local-config -provider adapters -> harness/registry types -credentials (human CLI) -> no local-config dependency -``` - -`local-config.ts` may import the `HarnessName` type from `harness/registry.ts`. -The registry must not import `local-config.ts`. - -## 2. Canonical harness registry - -### 2.1 Names - -`HarnessName` is the only supported-harness identifier type: - -```ts -export const HARNESS_NAMES = [ - "claude", - "opencode", - "codex", -] as const; - -export type HarnessName = (typeof HARNESS_NAMES)[number]; -``` - -The serialized config keys, `AI_AGENT` values, CLI arguments, and -`installations.yaml` keys use these lowercase names. Display names are registry -metadata only: - -| Name | Display name | Binary | -| --- | --- | --- | -| `claude` | Claude Code | `claude` | -| `opencode` | OpenCode | `opencode` | -| `codex` | Codex CLI | `codex` | - -### 2.2 Registry API - -```ts -export interface HarnessDescriptor { - name: HarnessName; - displayName: string; - binary: string; - detect(): boolean; - install(): Promise; - uninstall(record: HarnessInstallation): Promise; -} - -export interface HarnessInstallResult { - artifacts: InstallationArtifact[]; - messages: string[]; -} - -export interface HarnessUninstallResult { - removed: InstallationArtifact[]; - retained: InstallationArtifact[]; - messages: string[]; -} - -export function getHarness(name: HarnessName): HarnessDescriptor; -export function parseHarnessName(value: string): HarnessName; -export function detectInstalledHarnesses(): HarnessDescriptor[]; -export function isHarnessInstalled(name: HarnessName): boolean; -export async function installHarness(name: HarnessName): Promise; -export async function uninstallHarness( - name: HarnessName, - options?: { purge?: boolean }, -): Promise; -``` - -`installHarness` is idempotent. It invokes the adapter, then atomically replaces -that harness's inventory record with the returned artifacts. It may refresh an -already-installed ME artifact, but must not replace unrelated provider config. - -`uninstallHarness` reads the recorded inventory. No record is a successful -no-op. The adapter removes only the artifacts described by that record, then -removes the inventory record. It must not discover-and-delete unrecorded items -named `me`, because they could predate this feature or belong to a user. - -`--purge` is handled by the aggregate command layer after the selected adapters -succeed. It removes only `cli`, `mcp`, and `capture` selections for that harness -from `defaults` and every directory profile. It does not remove a profile that -still contains another harness or any enabled surface for another harness. - -### 2.3 Dispatcher command - -Every new install registers the stable MCP command with its harness identity: - -``` -me mcp --harness -``` - -`` must be a canonical harness name. It selects the per-harness policy -without baking configuration into the provider registration. The command -intentionally contains no server, space, API key, scope, or project path. The -dormant dispatcher resolves all activation at runtime from the local config and -existing credential state. No installation writes a credential or a -per-repository configuration file. - -The provider adapter must separately install its minimum native hook/plugin -plumbing needed to inject the harness contract and run dormant capture. The -hook/plugin itself must make no network call or memory write unless the resolved -capture profile selects that harness. - -## 3. Deployment inventory: `installations.yaml` - -Path: `~/.config/me/installations.yaml` (respect `XDG_CONFIG_HOME` in the same -way as `credentials.ts`). This file is ME-managed. It is neither a policy input -nor a credential store. - -### 3.1 Serialized schema - -```yaml -version: 1 -harnesses: - claude: - installed_at: "2026-08-03T14:00:00.000Z" - me_version: "0.0.0" - artifacts: - - kind: mcp-cli - server_name: me - scope: user - - kind: plugin - marketplace: memory-engine - plugin: memory-engine@memory-engine -``` - -The formal TypeScript representation is: - -```ts -export interface InstallationsFile { - version: 1; - harnesses: Partial>; -} - -export interface HarnessInstallation { - installed_at: string; // ISO-8601 UTC timestamp - me_version: string; - artifacts: InstallationArtifact[]; -} - -export type InstallationArtifact = - | { - kind: "mcp-cli"; - server_name: "me"; - scope?: "user" | "project"; - } - | { - kind: "mcp-json"; - path: string; // absolute canonical path - server_name: "me"; - } - | { - kind: "plugin"; - marketplace: string; - plugin: string; - } - | { - kind: "file"; - path: string; // absolute canonical path - sha256: string; // bytes written by this install - } - | { - kind: "json-hook"; - path: string; // absolute canonical path - event: string; - command: string; - }; -``` - -Artifact semantics: - -- `mcp-cli`: uninstall through the provider's native `mcp remove me` command, - using the recorded scope when supported. -- `mcp-json`: remove only the `mcp.me` entry from the recorded JSON file. Leave - the file and all other entries intact. If the entry no longer describes the - dormant dispatcher command, retain it and report why instead of deleting it. -- `plugin`: uninstall exactly the recorded plugin reference. Do not remove a - marketplace unless its provider has a native operation that proves it has no - other installed users. -- `file`: delete only when its current SHA-256 equals the recorded hash. A - changed file is user-owned from that point and is retained with a message. -- `json-hook`: remove exactly the entry matching event + command. Retain the - config file and unrelated hook entries. - -The registry must record every artifact it mutates. An adapter must not mutate -an artifact type that this union cannot express; extend this union first. - -### 3.2 Read/write behavior - -```ts -export function getInstallationsPath(): string; -export function readInstallations(): InstallationsFile; -export function getInstallation( - harness: HarnessName, -): HarnessInstallation | undefined; -export function writeInstallation( - harness: HarnessName, - installation: HarnessInstallation, -): void; -export function removeInstallation(harness: HarnessName): void; -``` - -- Missing file means `{ version: 1, harnesses: {} }`. -- Invalid YAML or wrong version is a loud error with the file path; never - silently overwrite an unreadable deployment inventory. -- Writes are atomic: write a sibling temporary file with mode `0600`, then - rename it. The config directory remains mode `0700`. -- `installed_at` is the successful completion time; `me_version` comes from the - running CLI version. -- A failed install must not replace a valid old record. An adapter that leaves - partially-created artifacts reports them so the command can either roll them - back before failure or persist a record that permits a safe retry/uninstall. - -## 4. Local config contract: `config.yaml` - -`config.yaml` combines two intentionally separate concerns: - -1. Existing **human CLI state**: `default_server`, `servers.*.active_space`, - and `server_whitelist` remain owned by `credentials.ts`. -2. New **harness policy**: `version`, `defaults`, and `directories` are owned - by `local-config.ts`. - -Writers must preserve the other concern's keys. Neither module may rewrite the -whole YAML document from a narrow type that discards unknown top-level keys. - -### 4.1 Types - -```ts -export type HarnessSelection = Partial>; - -export interface McpSurface { - enabled: boolean; - server?: string; - space?: string; - harnesses: HarnessSelection; -} - -export interface CaptureSurface { - enabled: boolean; - server?: string; - space?: string; - tree?: string; // directory profiles only - tree_root?: string; // defaults only - harnesses: HarnessSelection; -} - -export interface CliSurface { - server?: string; - space?: string; - harnesses: HarnessSelection; -} - -export interface HarnessProfile { - mcp?: McpSurface; - capture?: CaptureSurface; - cli?: CliSurface; -} - -export interface LocalConfig { - version: 1; - defaults?: HarnessProfile; - directories: Record; -} -``` - -Validation at parse and write time: - -- A selected `mcp` or `capture` surface has `enabled: true`, a nonempty - `server`, and at least one `harnesses. === true`. -- Selected `capture` requires `space`. A defaults capture profile has exactly - `tree_root`; a directory capture profile has exactly `tree`. -- A selected `cli` surface has a nonempty `server` and at least one selected - harness. Its `space` is optional. -- Disabled/missing surfaces never require their other fields. -- `tree` and `tree_root` may not coexist in one capture surface. -- Directory keys are absolute, canonical paths. A trailing slash is removed - except for `/`. -- Unknown top-level profile keys and unknown harness names are errors. Unknown - unrelated `config.yaml` top-level keys remain preserved by writers. - -`space` omitted from MCP means MCP multi-space mode. `space` omitted from CLI -means that a selected harness CLI call has a server override only and continues -through the normal space chain (explicit flag, `ME_SPACE`, then `me use`). - -### 4.2 Resolver API - -```ts -export interface ResolvedSurface { - source: "directory" | "defaults" | "disabled"; - profile_path?: string; // canonical matched directory, if source=directory - value?: T; -} - -export interface ResolvedHarnessProfile { - cwd: string; - profile_source: "directory" | "defaults"; - profile_path?: string; - mcp: ResolvedSurface; - capture: ResolvedSurface; -} - -export function resolveHarnessProfile(cwd: string): ResolvedHarnessProfile; -export function resolveMcpProfile(cwd: string): ResolvedSurface; -export function resolveCaptureProfile( - cwd: string, -): ResolvedSurface; -export function resolveHarnessCliProfile( - cwd: string, - harness: HarnessName, -): ResolvedSurface; -``` - -Resolution is exact and non-merging: - -1. Canonicalize cwd (`realpath`; lexical absolute normalization when the path - does not yet exist). -2. Select the segment-aware longest ancestor from `directories`. -3. If one matches, use that **whole profile**. A missing surface is disabled; - `defaults` is not consulted. -4. Otherwise use the whole `defaults` profile. A missing surface is disabled. -5. For every surface, a harness not explicitly selected (`=== true`) is - disabled for that harness. - -`resolveHarnessCliProfile` is called only after the CLI confirms a known -`AI_AGENT` harness contract. If the selected profile does not opt into that -harness, it returns `{ source: "disabled" }`; its caller then uses the existing -human CLI resolution chain. There is intentionally no `resolveCliProfile(cwd)` -for a user command. - -The MCP dispatcher resolves `process.cwd()` first. When that has no directory -profile, the Claude dispatcher next tries `CLAUDE_PROJECT_DIR`, then every -dispatcher tries `ME_PROJECT_DIR`. A fallback must not override an explicitly -matched directory profile, including one that disables MCP. If no location -matches, the initial resolution supplies `defaults`. Codex Desktop and VS Code -have no reliable dynamic project directory for a global MCP registration, so -they intentionally use `defaults` unless configured with a provider-native -per-server `cwd`. Capture hooks use `ME_PROJECT_DIR` as their primary discovery -anchor. - -### 4.3 Writer API - -```ts -export function readLocalConfig(): LocalConfig; -export function writeDefaults(profile: HarnessProfile): void; -export function writeDirectoryProfile( - directory: string, - profile: HarnessProfile, -): void; -export function removeHarnessFromProfiles(harness: HarnessName): void; -``` - -- `writeDefaults` and `writeDirectoryProfile` validate before writing and - always write the complete supplied profile. They do not merge it with an old - profile. -- `writeDirectoryProfile` canonicalizes the supplied directory before storing - it. -- `removeHarnessFromProfiles` removes one harness selection from all three - surfaces and removes a surface only when it no longer selects any harness. - It deletes a directory profile only when it has no remaining surfaces. -- The `me init` wizard builds a complete `HarnessProfile` then calls one writer; - it does not mutate YAML itself. - -## 5. Runtime boundary - -The dormant dispatcher has three consumers, each with a strict gate: - -| Consumer | Context test | Resolver | Inactive behavior | -| --- | --- | --- | --- | -| MCP `tools/list` | provider identity known | `resolveMcpProfile` | return `[]` | -| Capture hook | hook knows provider name | `resolveCaptureProfile` | exit success; no write/network | -| `me` from harness shell | `AI_AGENT` is a `HarnessName` | `resolveHarnessCliProfile` | use human CLI resolution | - -The standard user CLI must not import or resolve a directory profile. A human -running `me` from an activated project, including with `ME_PROJECT_DIR` set but -without a known `AI_AGENT`, uses the current flags -> `ME_*` -> `me use` -> -`default_server` sequence. - -## 6. Required focused tests - -- `harness/registry.test.ts`: names, parse errors, detection, aggregate - install/uninstall dispatch, and `--purge` profile cleanup. -- `harness/installations.test.ts`: schema validation, atomic writes, malformed - inventory failure, and artifact-preserving uninstall behavior. -- `local-config.test.ts`: path canonicalization, segment-aware longest match, - strict no-inheritance, scope-specific capture tree validation, and harness - gates on all surfaces. -- CLI integration tests: user CLI ignores directory profiles; selected harness - CLI uses its profile; unselected harness CLI falls back to human CLI. -- Provider adapter tests: every mutation returns an artifact that can precisely - undo it without affecting unrelated provider config. - -## 7. Change control - -Changing a serialized field, public type, artifact kind, or resolver behavior -in this document requires updating: - -1. `HARNESS_INTEGRATIONS_REQUIREMENTS.md` -2. this document -3. the corresponding focused tests -4. the durable decision memory under - `/share/projects/memory_engine/design/harness-integrations/decisions/` - -Do not add compatibility shims before a real serialized installation or config -format has shipped. diff --git a/HARNESS_INTEGRATIONS_INIT_WIZARD_PLAN.md b/HARNESS_INTEGRATIONS_INIT_WIZARD_PLAN.md deleted file mode 100644 index 9702c02d..00000000 --- a/HARNESS_INTEGRATIONS_INIT_WIZARD_PLAN.md +++ /dev/null @@ -1,50 +0,0 @@ -# `me init` Wizard Plan - -## Decision - -When `me init` runs on a TTY with surface flags but no scope, it prompts only -for scope. The supplied flags remain the complete deterministic profile input; -the command does not enter the interactive surface wizard. - -## Scope - -Implement the machine-local `me init` wizard described in -`HARNESS_INTEGRATIONS_REQUIREMENTS.md`. - -1. Preserve explicit noninteractive flag behavior and validation. -2. Prompt for directory versus `--defaults` scope whenever no scope is supplied - on a TTY. When surface flags are present, this is the only prompt. -3. Build complete profiles with explicitly disabled MCP, capture, and harness - CLI surfaces, then write once after confirmation. -4. Add login/device-flow selection, zero-space personal-space bootstrap, and - optional installation for detected, uninstalled Claude, OpenCode, and Codex - integrations. -5. Configure MCP, capture, and harness-only CLI routing independently. -6. Keep all harness policy machine-local; never write repository configuration. - -## Implementation Order - -1. Refactor `packages/cli/commands/init.ts` into reusable profile-building and - scope-validation helpers. Retain current flag errors while serializing - disabled surfaces explicitly. -2. Extract only the login/bootstrap primitives required by the wizard from - `commands/login.ts`; do not invoke Commander recursively. -3. Add wizard dependencies for prompts, credentials, user-space discovery, and - harness registry operations so unit tests can inject them. -4. Implement scope/replacement, auth/bootstrap, optional install, MCP, - capture, CLI, confirmation, and one final config write in that order. -5. Extend command tests for all cancellation, validation, profile, and - installation cases. Preserve local-config resolver tests as the authority - for canonical paths and no inheritance. -6. Update `docs/cli/me-init.md` and onboarding guidance, then run - `./bun run check:full`. - -## Constraints - -- Only Claude, OpenCode, and Codex are selectable harnesses. -- Capture uses `tree` for directory profiles and `tree_root` for defaults. -- The CLI surface affects only commands invoked under a selected harness - contract, never ordinary user CLI commands. -- Existing profiles require explicit replacement confirmation. -- A zero-space user may create only a personal default space through - `user.space.ensureDefault()`; ordinary pickers never create spaces. diff --git a/HARNESS_INTEGRATIONS_MANUAL_TEST_PLAN.md b/HARNESS_INTEGRATIONS_MANUAL_TEST_PLAN.md deleted file mode 100644 index c56dfc97..00000000 --- a/HARNESS_INTEGRATIONS_MANUAL_TEST_PLAN.md +++ /dev/null @@ -1,272 +0,0 @@ -# Harness Integrations Manual Test Plan - -Use this plan to validate the harness-integration rework in disposable Apple -Container Linux VMs. It is intentionally selective: prioritize the product -contract and UX over exhaustive provider coverage. Budget no more than four -hours. - -## Goal - -Validate that the candidate build has the intended behavior: - -1. Installing is dormant and non-destructive. -2. Machine-local policy activates MCP, capture, and harness-shell CLI routing - independently. -3. A configured harness can use MCP and carries the shell contract. -4. A human shell is not retargeted by a project profile. -5. Uninstall and CI generation are safe and understandable. - -Use an existing dedicated test space and a unique tree such as -`/share/manual-harness/`. Do not use production memories or -credentials. - -## Before Starting - -Allocate 15-20 minutes. - -- Confirm the candidate `me` binary runs in the ARM64 Linux container. -- Record the candidate commit, `me --version`, and the Claude Code, OpenCode, - and Codex versions. -- Give each container an isolated home: - - ```sh - export XDG_CONFIG_HOME="$HOME/.config" - export ME_NO_KEYCHAIN=1 - mkdir -p ~/projects/project-x - ``` - -- Use device login for the primary run: - - ```sh - export ME_SERVER=https://api.memory.build - export ME_SPACE= - me login --device "$ME_SPACE" - me whoami --json - ``` - - Approve the device code from another machine. The device session is stored - in the container's disposable config directory. - -- A mounted `ME_API_KEY` is suitable for quick repeat runs, but do not combine - it with device-login validation: an API key takes precedence over a session. - -## Container A: Mechanical Lifecycle - -Allocate 45 minutes. Start with all three harness CLIs on `PATH` and no -`~/.config/me/`. - -1. Run: - - ```sh - me install - ``` - -2. Check: - - - No Memory Engine login, space, tree, capture, or MCP configuration prompt. - - `~/.config/me/installations.yaml` lists each installed harness. - - No enabled policy is created in `config.yaml`. - - No repository file is created or changed. - -3. Inspect provider artifacts: - - - Claude: a user-scope Memory Engine plugin and MCP registration. - - OpenCode: `~/.config/opencode/opencode.json` contains `mcp.me` invoking - `me mcp --harness opencode`; the generated plugin exists. - - Codex: user MCP registration and `~/.codex/hooks.json` entries exist. - -4. Re-run `me install`. Expect no duplicate artifacts or prompts. - -5. Add an unrelated provider config entry, then run: - - ```sh - me uninstall - ``` - -6. Check: - - - Memory Engine-owned artifacts and inventory are removed. - - The unrelated provider entry remains. - - A second uninstall is a successful no-op. - -Stop and record a blocker if installation, repeatability, or cleanup is -confusing. These are release-critical paths. - -## Container B: Policy And UX - -Allocate 60 minutes. Use an existing login and a fresh `/workspace/project`. - -1. Verify an empty profile is inert: - - ```sh - me init /workspace/project - me doctor /workspace/project --harness claude --json - ``` - - The profile should contain explicit disabled surfaces and no repository file - should exist. - -2. Write a full directory policy for one harness, preferably OpenCode first: - - ```sh - me init /workspace/project \ - --mcp-server "$ME_SERVER" \ - --mcp-space "$ME_SPACE" \ - --mcp-harness opencode \ - --capture-server "$ME_SERVER" \ - --capture-space "$ME_SPACE" \ - --capture-tree /share/manual-harness/ \ - --capture-harness opencode \ - --cli-server "$ME_SERVER" \ - --cli-space "$ME_SPACE" \ - --cli-harness opencode - ``` - -3. Inspect the policy: - - ```sh - me doctor /workspace/project --harness opencode --json - AI_AGENT=opencode ME_PROJECT_DIR=/workspace/project me doctor --json - ``` - -4. Confirm MCP, capture, and CLI are independently configured; the directory - key is canonical; and doctor reports the expected MCP anchor. - -5. Confirm a human shell is not harness-routed: - - ```sh - env -u AI_AGENT -u ME_PROJECT_DIR me status --json - ``` - -## No-Inheritance Check - -Allocate 20 minutes. This validates a core safety invariant. - -1. Configure defaults with MCP enabled for a harness. -2. Configure `/workspace/project` with only capture or CLI enabled. -3. Run: - - ```sh - me doctor /workspace/project --harness opencode --json - ``` - -4. Expect MCP to be inactive in the project. It must not inherit the defaults - MCP block. -5. If practical, start OpenCode in the project and confirm its Memory Engine - MCP tool list is empty. - -## Live Harness Smoke - -Allocate 70-80 minutes. These are the only checks that establish runtime -integration rather than static configuration, and they spend provider tokens. - -For each of Claude Code, OpenCode, and Codex: - -1. Start in `/workspace/project` after installing its integration. -2. Before activating its MCP profile, inspect the harness MCP/tool UI. Memory - Engine may be connected but must expose no tools. -3. Enable only that harness under MCP with `me init`. -4. Start a new harness session and ask it to create a uniquely named Memory - Engine memory under `/share/manual-harness/`. -5. Verify the result: - - ```sh - me memory get /share/manual-harness// - ``` - -6. Ask the harness to execute: - - ```sh - printf '%s %s\n' "$AI_AGENT" "$ME_PROJECT_DIR" - ``` - - Expect its harness name and `/workspace/project`. - -Provider notes: - -- Claude validates plugin MCP activation and `$CLAUDE_ENV_FILE` shell - propagation. -- OpenCode validates MCP and the generated plugin's `shell.env` propagation. -- Codex requires approving the installed hook through `/hooks` before its - shell-environment test. - -Do not test Codex Desktop or VS Code in this pass. They intentionally use the -defaults profile without a provider-native per-server cwd. - -## Capture - -Allocate 25 minutes. Test capture end-to-end for one harness, preferably -OpenCode. Repeat for Claude only if time remains. - -1. Count the destination before the session: - - ```sh - me memory count /share/manual-harness/ - ``` - -2. Run and finish a short session containing a unique phrase. -3. Wait briefly for hook processing, then verify a new memory appears: - - ```sh - me memory tree /share/manual-harness/ - ``` - -4. Replace the directory profile with capture disabled and run another short - session. The count must not increase, and the harness must not be blocked. - -For Codex, verify that hooks do not block a session while capture is disabled. -Treat live capture as out of scope unless it is already known stable. - -## CI Generator - -Allocate 15 minutes. Use a disposable git repository with a GitHub `origin`. - -```sh -me ci install \ - --server "$ME_SERVER" \ - --space "$ME_SPACE" \ - --tree /share/manual-harness//repo \ - --workflow-only -``` - -Check: - -- `.github/workflows/me-import.yml` is created. -- It invokes `me import git` and `me import docs`, never `me import ci`. -- Re-running without `--force` refuses. -- Re-running with `--force` replaces the workflow entirely. - -Do not spend time testing GitHub secret placement or service-account creation -in this pass. - -## Uninstall And Purge - -Allocate 15 minutes in Container B: - -```sh -me uninstall opencode -me doctor /workspace/project --harness opencode --json -me uninstall opencode --purge -``` - -Confirm plain uninstall preserves local policy selections, while `--purge` -removes OpenCode selections without removing other harness selections. - -## Exit Criteria - -Call the run successful if all of the following hold: - -- Install, reinstall, and uninstall are prompt-free, understandable, and - non-destructive. -- A newly installed harness exposes no Memory Engine tools before activation. -- A configured harness exposes MCP tools, carries `AI_AGENT` and - `ME_PROJECT_DIR`, and can write a test memory. -- One capture path writes only to its configured tree; disabled capture writes - nothing. -- A project profile never inherits omitted surfaces from defaults. -- A human shell remains unaffected by the project profile. -- CI generation is explicit and refuses accidental overwrite. - -Record each failure with the container image/version, `me` commit, harness -version, command, expected behavior, actual behavior, and relevant -`me doctor --json` output. diff --git a/HARNESS_INTEGRATIONS_REQUIREMENTS.md b/HARNESS_INTEGRATIONS_REQUIREMENTS.md deleted file mode 100644 index a49480bb..00000000 --- a/HARNESS_INTEGRATIONS_REQUIREMENTS.md +++ /dev/null @@ -1,703 +0,0 @@ -# Harness Integrations — Requirements - -Status: **draft; superseding the current `me project init` / `me project ci` / -`me claude install` / `me opencode install` / `me codex install` design.** -Contributor-facing; not published to -`docs.memory.build/`. - -This document is the implementation contract for a rework of how Memory Engine -integrates with coding-agent harnesses (Claude Code, OpenCode, Codex). -It replaces committed-repo configuration, one-command "installs everything and -turns everything on" flows, and the current project/CI split with a smaller, -sharper model: - -- One dormant dispatcher globally installed per harness. -- One local file that describes what the dispatcher does per directory. -- Three commands (`me install`, `me init`, `me ci install`) that own setup - end-to-end. Everything else is mechanical. - -The decisions that back each requirement live in Memory Engine memories under -`/share/projects/memory_engine/design/harness-integrations/decisions/`; the -implementation-planning notes live under `.../implementation/`. This file is -the terse version an implementer needs. - ---- - -## 1. Guiding principles - -1. **Install ≠ enable.** Installing the dispatcher into a harness must not - activate capture, injection, MCP tool exposure, memory writes, or context - effects anywhere. -2. **Local individual control.** Everything that affects a developer's own - harness lives under `~/.config/me/` on that machine. Nothing about an - individual's harness behavior is stored in a repository. -3. **Independence of bindings.** MCP, capture, and CLI-in-harness are three - independent surfaces. Enabling one must not enable another. -4. **Explicit scope; no inheritance.** A configured directory replaces - `defaults` wholesale for behavior; there is no field-level fallback. -5. **CI configuration lives in the CI workflow.** The generated workflow file - is the source of truth; `me ci install` scaffolds it once, then hands it - over. -6. **No committed policy.** Cloning a repository must not change what Memory - Engine does on a developer's machine. -7. **User CLI stays explicit.** A user typing `me …` at a shell is never - silently retargeted by directory profiles. Space/server targeting for - user CLI is controlled only by explicit flags, `ME_*` env vars, and - `me use`. The `cli` config surface is a harness-context routing policy, - not a general CLI profile system. - ---- - -## 2. Command surface - -### 2.1 `me install [harness...]` - -- No arguments: detect every supported harness on `PATH` (Claude Code, - OpenCode, Codex) and install the dispatcher for each. -- Arguments: install only the named harnesses. Unknown names error. -- Purely mechanical: writes only ME-owned native plumbing (hook entries, MCP - registration, plugin/env-hook files) and updates `installations.yaml`. -- Managed MCP registrations run `me mcp --harness `. The harness identity - selects that harness's local MCP policy; no registration includes a server, - space, credential, scope, or project path. -- **No prompts.** Never asks about auth, space, tree, capture, MCP, or data - collection. -- Idempotent. - -### 2.2 `me uninstall [harness...] [--purge]` - -- Symmetric to `me install`. -- No arguments: uninstall every ME-managed integration recorded in - `installations.yaml`. -- Removes only ME-owned entries; leaves unrelated harness config alone. -- Per-directory activations in `~/.config/me/config.yaml` are preserved by - default. `--purge` also removes them. -- **No prompts.** - -### 2.3 `me claude install` / `me opencode install` / `me codex install` (and matching `uninstall`) - -- The single-harness form of `me install` / `me uninstall`. -- Mechanical; no prompts. - -### 2.4 `me init [directory] [--defaults] [flags]` - -- The one personal-configuration wizard. -- Writes only `~/.config/me/config.yaml`. -- `me init` on a TTY prompts for scope; off a TTY errors out. -- `me init .` / `me init ` writes a directory profile. -- `me init --defaults` writes the fallback `defaults` profile. -- The positional path and `--defaults` are mutually exclusive. -- The positional path is canonicalized (absolute, symlinks resolved) before - being written under `directories.`. -- Non-interactive flags: - - `--mcp-space ` - - `--mcp-multi-space` (conflicts with `--mcp-space`) - - `--mcp-server ` - - `--mcp-harness ` (repeatable) - - `--capture-space ` - - `--capture-server ` - - `--capture-tree ` (directory scope only) - - `--capture-tree-root ` (`--defaults` scope only) - - `--capture-harness ` (repeatable) - - `--cli-space ` - - `--cli-server ` - - `--cli-harness ` (repeatable) - - Rules: - - Absence of any `---*` flag means that surface is disabled. - - A surface enabled with zero harnesses selected is a validation error at - write time. - - `--mcp-space` and `--mcp-multi-space` are mutually exclusive. - - `--capture-tree` requires directory scope; `--capture-tree-root` requires - `--defaults` scope; mixing them is an error. - -### 2.5 `me ci install [flags]` - -- Generates a starter GitHub Actions workflow and (optionally) provisions the - service account + secret. -- Interactive on a TTY; also runnable non-interactively via flags. -- Refuse-or-force file semantics: - - Workflow does not exist → create. - - Workflow exists → refuse. - - `--force` overwrites the workflow entirely (no merge, no marker). -- Non-interactive flags: - - `--server ` - - `--space ` (required off-TTY) - - `--tree ` (default `/share/projects/`) - - `--secret-name ` (default `ME_API_KEY`) - - `--service-account ` (default `-import`) - - `--create-service-account` - - `--workflow-only` - - `--force` - -### 2.6 Removed commands - -- `me project init` — removed. Use `me init`. -- `me project ci` — removed. Use `me ci install`. -- `me import ci` — removed. The generated workflow calls `me import git` and - `me import docs` directly. - -### 2.7 Prompt policy - -- Only two commands are ever interactive: `me init` and `me ci install`. -- Every other command in the install/uninstall/CI space is non-interactive. -- Neither wizard offers "Create a new space" in space pickers. - - `me init` has one exception: when the authenticated user has **zero** - spaces after login, it offers to create a personal space (via - `user.space.ensureDefault()`) as the bootstrap branch. - ---- - -## 3. Local config: `~/.config/me/` - -### 3.1 Files - -``` -~/.config/me/ - config.yaml # user policy (this schema) - installations.yaml # ME-managed deployment inventory - credentials.yaml # existing secret fallback (unchanged) -``` - -- `config.yaml` is human-editable and is what `me init` reads/writes. -- `installations.yaml` is read/written only by `me install` / `me uninstall`; - never by the wizard, dispatcher, or user. -- `credentials.yaml` retains its current secret-fallback role. - -### 3.2 `config.yaml` schema - -```yaml -version: 1 - -# Machine-level fallback used only for out-of-profile / bootstrap CLI -# (e.g. `me login`, `me space list` when no directory profile matches). -default_server: https://api.memory.build - -# Fallback profile: applies when the current directory does not match any -# entry under `directories:`. -defaults: - mcp: - enabled: false - server: https://api.memory.build - space: - harnesses: - claude: false - opencode: false - codex: false - - capture: - enabled: false - server: https://api.memory.build - space: - tree_root: ~/projects # defaults capture uses tree_root - harnesses: - claude: false - opencode: false - codex: false - - cli: - server: https://api.memory.build - space: - harnesses: - claude: false - opencode: false - codex: false - -# Complete per-directory profiles. Presence of a matched entry REPLACES -# defaults wholesale for that directory. -directories: - /Users/alice/projects/widget: - mcp: - enabled: true - server: https://api.memory.build - space: team-memory - harnesses: - claude: true - opencode: true - - capture: - enabled: true - server: https://api.memory.build - space: session-archive - tree: /share/projects/me1 # directory capture uses tree - harnesses: - claude: true - opencode: false - - cli: - server: https://api.memory.build - space: team-memory - harnesses: - claude: true - opencode: true -``` - -### 3.3 Surfaces - -Three independent top-level surface blocks per profile: - -- **`mcp`** — controls MCP tool exposure to harness clients. - - `enabled: false` → `tools/list` returns `[]`. - - `server` — required when enabled. - - `space` (optional) — locked-space MCP; omitted → multi-space MCP. - - `harnesses.` — per-harness allowlist. - -- **`capture`** — controls transcript collection. - - `enabled: false` → hooks no-op. - - `server`, `space` — required when enabled. - - Destination: - - Directory scope profiles use `tree` (full project node, no slug append). - - The `defaults` profile uses `tree_root` (slug-free parent; the - runtime appends a per-project slug). - - Mixing `tree` and `tree_root` in the same profile is an error. - - `harnesses.` — per-harness allowlist. - -- **`cli`** — **harness-context-only** routing policy for Memory Engine CLI - invocations initiated by a harness. This surface has no effect on - user-initiated CLI use. - - `server`, `space` (optional). - - **No `tree` field.** CLI targeting is server + space; individual commands - control their own tree via flags. - - `harnesses.` — per-harness allowlist. - - Applies **only** when the invoking `me` process is running inside a - harness shell. "Inside a harness shell" means the harness contract env - (`AI_AGENT` set to a known harness name, injected by the dormant - dispatcher per `harness-contract.ts`) is present. `ME_PROJECT_DIR` alone - is not sufficient — a human running `cd $ME_PROJECT_DIR && me search foo` - is still a user invocation. - - When the harness contract is present and the matched profile's - `cli.harnesses.` is true, targeting is drawn from that - profile's `cli` block: `server` and (if set) `space`. Missing `space` - means the CLI behaves as multi-space (each command targets what its own - flags / env / active-space fallback resolve to). - - When the harness contract is present but the matched profile does **not** - select that harness under `cli.harnesses`, the CLI falls back to normal - user-CLI behavior (see below). This is deliberate: a harness that the - user has not opted into for CLI routing is treated exactly like any - other shell. - - **User-initiated CLI use never consults `cli`, `defaults.cli`, or the - `directories.*` map.** See section 3.4 for the full user-CLI resolution - order. - -### 3.4 Resolution - -Resolution splits by **consumer**. The `mcp` and `capture` surfaces are -directory-driven for both harness and dispatcher use. The `cli` surface is -directory-driven **only** for harness-initiated CLI. User-initiated CLI uses -a separate, per-command resolution chain that never reads `directories:`, -`defaults.cli`, or `default_server` as a targeting override. - -#### 3.4.1 `mcp` and `capture` (dispatcher / hooks) - -Given the current working directory: - -1. Canonicalize to an absolute, symlink-resolved path. -2. Find the **most-specific** ancestor (or equal) directory listed under - `directories:`, using segment-aware longest-prefix matching. (`/a/foo` does - not match `/a/foobar`.) -3. If a matched entry exists: that profile alone determines behavior for the - `mcp` and `capture` surfaces. -4. Otherwise: the `defaults` profile alone determines behavior. - -**No field-level inheritance.** A missing surface in a matched profile means -that surface is disabled. A missing harness under a surface's `harnesses:` map -means that harness is not selected for that surface. `defaults` is not -consulted to fill in gaps. - -MCP directory propagation is provider-aware. The dispatcher first resolves -against `process.cwd()` at server start. If that finds no directory profile, -the Claude dispatcher tries `CLAUDE_PROJECT_DIR`, then every dispatcher tries -`ME_PROJECT_DIR`. A fallback is used only when the earlier location has no -directory profile, so an explicitly-disabled profile is never bypassed. If no -location matches, the handler resolves against `defaults`. - -OpenCode and the Codex terminal CLI start local MCP servers from the session -directory. Claude documents `CLAUDE_PROJECT_DIR` as the reliable MCP project -signal, but it is a fallback because it can point to the main checkout for a -worktree session. Codex Desktop and the VS Code extension have no reliable -dynamic project directory for global MCP registrations; they intentionally use -the `defaults` profile unless the user configures a provider-native per-server -`cwd`. Per-directory MCP behavior is otherwise a best-effort provider feature; -`me doctor` reports which resolution path was taken. - -#### 3.4.2 `cli` — harness-initiated CLI - -Triggered when the `me` process starts with the harness contract present -(`AI_AGENT` set to a known harness name). Then: - -1. Canonicalize the CLI's cwd. If `ME_PROJECT_DIR` is set by the dispatcher, - canonicalize that instead — it is the discovery anchor the harness - provides. -2. Longest-ancestor match against `directories:` (same rules as 3.4.1). -3. If a matched entry has `cli.harnesses. === true`, target the - command using that profile's `cli.server` and (if set) `cli.space`. -4. If no directory profile matches, and `defaults.cli.harnesses. - === true`, target using `defaults.cli`. -5. Otherwise — matched profile does not select this harness, or defaults do - not — **fall back to user-CLI resolution (3.4.3).** - -Once a `cli` block is selected, its `server`/`space` supply the base -targeting; per-command flags and `ME_*` env vars still override on the same -command. - -#### 3.4.3 `cli` — user-initiated CLI - -Triggered when the `me` process starts **without** the harness contract, or -when 3.4.2 falls through. Resolution is per-existing-CLI precedence, in -order: - -1. Explicit per-command flags (`--server`, `--space`, `--api-key`, …). -2. `ME_SERVER` / `ME_SPACE` / `ME_API_KEY` / `ME_SESSION_TOKEN` from the - environment. -3. The active space selected by `me use ` (persisted in - `~/.config/me/config.yaml` under the existing active-space key, per - `packages/cli/credentials.ts`). -4. `default_server` for the server (login/bootstrap fallback). -5. Interactive prompt or error, per each command's existing behavior. - -**User-initiated CLI never consults `directories:` or `defaults.cli` for -targeting.** Entering a configured project directory does not silently -retarget an interactive `me search`, `me create`, `me space list`, etc. The -only role `~/.config/me/config.yaml` plays for user CLI is the existing -`default_server` + `me use` active-space state. - -This is the deliberate distinction: the `cli` surface exists to give a -harness a **stable, project-scoped identity** without the human having to -`me use` before every session; the human, sitting at a shell, keeps the -current explicit-selection model. - -### 3.5 `installations.yaml` - -The exact serialized schema and artifact ownership rules are frozen in -[`HARNESS_INTEGRATIONS_CONTRACTS.md`](HARNESS_INTEGRATIONS_CONTRACTS.md), -section 3. The short illustrative shape is: - -```yaml -version: 1 - -installations: - claude: - installed_at: 2026-07-31T18:00:00Z - me_version: - files: - - - opencode: - ... - codex: - ... -``` - -Enough detail for `me uninstall` to precisely remove what `me install` -wrote, without touching unrelated provider config. Users do not edit this file. - ---- - -## 4. `me init` wizard - -Ordered steps: - -1. **Scope** - - Skipped when `me init ` or `me init --defaults` is passed. - - TTY: prompt for "this directory" vs. "defaults." - - Non-TTY without a scope flag: error out. - - If the chosen scope already has a profile, prompt to replace. - -2. **Login (if needed)** - - Auto-select login flow based on best-effort browser detection. - - Offer the alternative flow via an explicit prompt. - - Declining exits cleanly. - -3. **Bootstrap: zero-space branch** - - Runs only when the authenticated user has zero spaces. - - Offers a personal-space creation via `user.space.ensureDefault()`. - - Declining exits cleanly with guidance. - -4. **Integrations (if any are missing)** - - Detect installed harnesses via `Bun.which` and their install state via - `installations.yaml`. - - Zero installed harnesses detected: skip step with a one-line note. - - Exactly one uninstalled: yes/no. - - Multiple uninstalled: multiselect. - - Selecting invokes `me install ` inline. Declining is allowed. - - Always call these "MCP tools" in prompts; never bare "tools." - -5. **MCP surface** - - Yes/no. - - Harness multiselect drawn from the detected+installed set. - - Space picker: existing spaces only; plus "Let the agent choose a space for - each request" (which omits `space`). - - Server prompt: default to the login-active server; allow overriding. - -6. **Capture surface** - - Yes/no. - - Harness multiselect drawn from the detected+installed set. - - Space picker. - - Directory scope: prompt for `tree`. - - `--defaults` scope: prompt for `tree_root`; also print an explicit privacy note - that sessions from unconfigured directories will go to that (private) - space. - - Server prompt. - -7. **CLI surface (harness-context only)** - - Prompt copy must make the scope explicit, e.g.: "Route Memory Engine CLI - commands **run by these harnesses** to this space? Your own `me` - commands are not affected — they continue to use `me use` and explicit - flags." - - Yes/no. - - Harness multiselect drawn from the detected+installed set. - - Space picker; server prompt. - - No tree prompt. - - The wizard must not offer any prompt that would change user-CLI - targeting; `me use` remains the mechanism for that. - -8. **Confirmation + write** - - Show the resolved profile. - - On confirm, write the complete profile including explicitly-disabled - surfaces (`enabled: false`, `harnesses: {}`) so the no-inheritance - invariant reads cleanly on future inspection. - -Validation rules: -- A surface `enabled: true` with no harnesses selected: refuse; loop back on - the harness step. -- Directory scope + `tree_root`, or `--defaults` scope + `tree`: refuse. - ---- - -## 5. `me ci install` wizard - -Ordered steps: - -1. **Preflight** - - Fail unless inside a git repository with a GitHub `origin` remote. - - Fail unless the workflow file (`.github/workflows/me-import.yml`) is - absent or `--force` is set. - -2. **Space picker** - - Lists only spaces the user belongs to. - - **No "create a new space" option.** - - Zero eligible spaces: fail with guidance to `me space create` or accept - an invitation. - -3. **Tree** - - Default `/share/projects/`. Editable. - -4. **Secret name** - - Default `ME_API_KEY`. Editable. - -5. **Space-admin check** - - Immediately after space selection, determine whether the caller is an - effective admin of the space (direct admin, or a direct-member user of an - admin group). - -6. **Credential prompt (admin)** - ``` - > I have a service account's ME_API_KEY - Create a service account - ``` - - **Existing-key path**: hidden-input key prompt; require usable `gh` + - repo-secret write access; pipe key directly to - `gh secret set --repo `; **never** print, log, - or persist the key. If a secret with the chosen name is already visible, - prompt one overwrite confirmation. - - **Create-SA path**: create `-import` with the caller in its bound - admin group; grant write at the selected tree; mint a key; pipe directly - into `gh secret set`. Placement failure revokes the just-minted key. - -7. **Credential prompt (non-admin)** - ``` - > I have a service account's ME_API_KEY - Give me instructions - ``` - - Existing-key path: same as admin. - - Instructions path: generate the workflow, then print the exact - `me service create` / `me access grant` / `me apikey create` / - `gh secret set` commands, plus the selected space's admin emails. - -8. **Workflow generation** - - Path: `.github/workflows/me-import.yml`. - - Baked env: `ME_API_KEY: ${{ secrets. }}`, plus `ME_SERVER` - when the resolved server differs from what a bare CI checkout would - resolve, plus `ME_SPACE: `. - - Steps call `me import git --tree ` and - `me import docs . --git-aware --prune --tree ` directly. - -Validation rules: -- Not a git repo, or no GitHub `origin`: fail before prompting. -- Workflow exists without `--force`: fail before prompting. -- `--create-service-account` in scripted mode from a non-admin: server denial - surfaces with admin contacts; the wizard renders the same instructions. - -**No verification of the pasted key's identity in v1.** A wrong key surfaces -as a loud first-CI-run auth error. - ---- - -## 6. CI generated workflow - -The workflow is short, explicit, and single-target. Illustrative shape: - -```yaml -name: Memory Engine import -on: - push: - workflow_dispatch: {} - -concurrency: - group: me-import-${{ github.ref }} - cancel-in-progress: true - -jobs: - import: - if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - name: Install me - run: | - set -o pipefail - mkdir -p "$HOME/.local/bin" - curl -fsSL https://install.memory.build | ME_INSTALL_DIR="$HOME/.local/bin" sh - - name: Import git history - env: - ME_API_KEY: ${{ secrets.ME_API_KEY }} - ME_SPACE: team-memory - run: "$HOME/.local/bin/me" import git --tree /share/projects/widgets - - name: Import docs - env: - ME_API_KEY: ${{ secrets.ME_API_KEY }} - ME_SPACE: team-memory - run: "$HOME/.local/bin/me" import docs . --git-aware --prune --tree /share/projects/widgets -``` - -Users needing separate spaces or credentials per phase edit the YAML directly -(duplicate/rename steps; change each step's `env:`). This is the deliberate -non-goal of `me ci install`; anyone with divergent CI needs owns the workflow -after generation. - ---- - -## 7. Legacy `.me/config.yaml` - -- This is a hard cut. The runtime, capture hooks, MCP dispatcher, `me init`, - and CLI commands do not read, write, discover, validate, or migrate - `.me/config.yaml` or `.me/config.local.yaml`. -- `ME_CONFIG_DIR` and `--config-dir` are removed. -- Existing repository pins have no effect on behavior. Harness policy comes - only from machine-local `~/.config/me/config.yaml` profiles, resolved from - `ME_PROJECT_DIR` or the current working directory. - ---- - -## 8. Hard Cut - -There is no deprecation window or compatibility shim. `me project init`, -`me project ci`, `me import ci`, repository configuration, and their related -environment variables and flags are removed. Users must use the replacement -commands and machine-local configuration directly. - ---- - -## 9. Test/acceptance criteria - -The rework is complete when all of the following hold on `./bun run check:full`: - -- `me install`, `me uninstall`, and the per-harness variants are exercised in - integration tests against the three harnesses (Claude, OpenCode, Codex). No - user prompts appear in any of these tests. -- `installations.yaml` is created/updated by `me install` and consumed by - `me uninstall` such that a full round-trip leaves each harness's native - config file byte-identical to its pre-install state (ignoring unrelated - edits made by the user or provider). -- `me init` writes the schema in section 3.2, including explicitly-disabled - surfaces. Round-tripping a written config through `me init` (or the - non-interactive flag form) produces an identical file. -- Longest-ancestor match, no-inheritance, and canonical-path resolution are - covered by unit tests. Symlink and `..`-traversal inputs land on the - canonical path. -- `me ci install` refuses when the workflow exists and overwrites entirely - with `--force`. Generated workflows are byte-identical across identical - inputs. Piped-key placement never persists the key on disk in any code path. -- The existing invariants around service-account key handling (mint only with - immediate placement, revoke on failed placement, admin-contact enrichment on - denials) are preserved. -- Hooks, MCP `tools/list`, and CLI-in-harness invocations resolve their target - surface via the config resolver and respect the no-inheritance rule. -- Integration test: a user-initiated `me` command run inside a directory that - has a `cli` profile (with the user's own shell — no `AI_AGENT` env) ignores - that profile and resolves targeting solely from flags / `ME_*` env / - `me use` active space / `default_server`. -- Integration test: the same command, run with the harness contract present - (`AI_AGENT=` injected by the dispatcher) and the profile selecting - that harness under `cli.harnesses`, is targeted by the profile's `cli` - block. -- Integration test: harness contract present but `cli.harnesses.` - false → command falls back to user-CLI resolution. -- `me doctor` explains inactivity ("MCP is inactive here because the matched - directory profile has `mcp.enabled: false`" / "Capture is inactive here - because no matched directory profile exists and defaults disable it"). -- `me project init`, `me project ci`, and `me import ci` are absent from the - command surface. -- `packages/cli/docs-cli-links.test.ts`, `packages/cli/mcp/docs-links.test.ts`, - and `packages/docs-site/lib/nav.test.ts` all pass with the new pages - (`docs/cli/me-init.md`, `docs/cli/me-ci.md`, etc.) added and the retired - pages removed. - ---- - -## 10. Non-goals - -- **No launcher/wrapper.** Users continue to invoke `claude`, `opencode`, and - `codex` natively. Memory Engine does not shell over them. -- **No CI-side declarative policy.** `.me/policies.yaml` and `me apply` are - not introduced. -- **No multi-target CI generator.** `me ci install` writes a single-space, - single-tree workflow. Divergent topologies are user edits after generation. -- **No cross-file config precedence.** There is one source of truth - (`~/.config/me/config.yaml`) and no per-field merge into `defaults`. -- **No implicit space creation** in ordinary pickers. Only the zero-space - bootstrap branch of `me init` provisions a personal space. -- **No directory-profile override of user CLI.** A general - "auto-`me use` on `cd`" behavior driven by `directories:` is explicitly - out of scope. Users who want per-directory active-space state can script - it themselves around `me use`. -- **No key identity verification** in `me ci install` v1. -- **No committed harness state.** Cloning a repo cannot alter Memory Engine's - behavior on the cloning machine. - ---- - -## 11. Coordination surface (frozen) - -The implementation contracts are now frozen in -[`HARNESS_INTEGRATIONS_CONTRACTS.md`](HARNESS_INTEGRATIONS_CONTRACTS.md): - -1. `installations.yaml` schema and artifact ownership: section 3. -2. Config schema, resolver, and writer APIs: section 4. -3. Harness registry names and APIs: section 2. -4. Module ownership and dependency direction: section 1. - -Workstreams 1 (CI rewrite), 2a (registry + installations inventory), and 3a -(local config schema + resolver) may now start in parallel. The config and -registry module APIs are stable seams for the `me init` workstream. - ---- - -## 12. Related design memory (authoritative) - -The concrete decisions and their reasoning live in -`/share/projects/memory_engine/design/harness-integrations/`. This document -tracks the outcome, not the debate. When something here is ambiguous, the -decision memories are the source of truth: - -- `decisions/global-dormant-dispatcher-2026-07` -- `decisions/harness-config-local-only-2026-07` -- `decisions/ci-config-lives-in-workflow-2026-07` -- `decisions/install-uninstall-init-ci-commands-2026-07` -- `decisions/local-config-schema-2026-07` -- `decisions/me-ci-install-wizard-2026-07` -- `decisions/me-init-wizard-2026-07` -- `decisions/me-init-bootstrap-space-creation-2026-07` -- `decisions/wave-1-unblockers-2026-07` -- `implementation/workstream-breakdown-2026-07` diff --git a/SELECT_PROJECTION_DESIGN.md b/SELECT_PROJECTION_DESIGN.md deleted file mode 100644 index ae478a1c..00000000 --- a/SELECT_PROJECTION_DESIGN.md +++ /dev/null @@ -1,155 +0,0 @@ -# Field Projection and Format Selection - -**Linear:** TNT-206, TNT-257 - -## Decision - -Implement field projection at the CLI and MCP presentation boundaries, after -the TypeScript client has received a complete JSON-RPC response. - -Do not add `select` to the JSON-RPC protocol, server handlers, or general -TypeScript client. `memory.get`, `memory.getByPath`, and `memory.search` retain -their existing inputs and exact full response types. - -`format` is also a presentation concern for MCP and CLI. It is not an RPC or -general client parameter. - -## Rationale - -The product benefit is reducing MCP context consumption and making CLI output -easier to scan. Both are achieved as long as projection happens before MCP text -serialization or CLI rendering; the model and terminal do not observe the full -internal RPC response. - -Server-side projection would make each affected RPC polymorphic: calls without -`select` return a complete memory, while calls with `select` return a partial -object. That polymorphism leaks into the TypeScript SDK. Optional or dynamic -`select` values cannot safely preserve a strict full-response return type, and -correct overloads require mutually exclusive parameter types plus a union for -dynamic selection. This complexity is not justified for a presentation feature. - -Keeping projection out of JSON-RPC provides stronger contracts by construction: - -- RPC and SDK callers always receive the existing complete response. -- Omitted selection cannot alter response behavior. -- CLI and MCP own the partial presentation shapes they expose. -- Server, engine, and database code remain unchanged. -- No projected response schemas or SDK overloads are needed. - -The tradeoff is that complete content and metadata travel from the server to the -CLI or MCP process before projection. The database query already retrieves the -complete memory, and there is no evidence that this additional transfer is a -bottleneck. If profiling later demonstrates otherwise, server-side projection -can be designed as a deliberate public API rather than leaking presentation -concerns into the existing methods. - -## Presentation API - -Add selection only to these user-facing presentation surfaces: - -- MCP `me_memory_get`, `me_memory_get_by_path`, and `me_memory_search` accept an - optional, nonempty `select` list. -- CLI `me memory get` and `me memory search` accept `--select `. -- Default CLI text search locally projects ID, tree, a 120-code-unit content - preview, and score. - -An omitted selection presents the complete response. An empty selection is -invalid. - -Selectors use the camelCase names of the already-shaped client response: - -| Selector | Result | -| --- | --- | -| `id` | Memory UUID | -| `content` | Full content | -| `content:N` | First `N` content code units and `contentLength` | -| `content:M:N` | Content slice `[M, N)` and `contentLength` | -| `content:M:` | Content from `M` through the end and `contentLength` | -| `meta` | Full metadata object | -| `meta.key` | Requested metadata key within `meta` | -| `tree` | Display tree path | -| `name` | Optional memory name | -| `temporal` | Temporal range | -| `score` | Search relevance score; omitted on get operations | -| `hasEmbedding` | Whether an embedding exists | -| `createdAt` | Creation timestamp | -| `createdBy` | Memory creator (currently always `null`) | -| `updatedAt` | Last-update timestamp | -| `version` | Memory version | -| `versionHash` | Memory version hash | - -### Metadata keys - -Metadata accepts arbitrary string keys. A `meta.` selector therefore preserves -the complete nonempty suffix as the key, including built-in keys such as -`$thread`, `$prev`, and `$next`, and keys containing punctuation. Multiple -metadata-key selectors combine into one `meta` object. A missing key is absent -from that object. The bare `meta` selector takes precedence and returns all -metadata. - -### Content slices - -Use zero-based, end-exclusive JavaScript slice semantics: - -```text -content:200 # [0, 200) -content:100:300 # [100, 300) -content:100: # [100, end) -``` - -Bounds are non-negative safe integers. `contentLength` is the total JavaScript -string length, measured in UTF-16 code units. Negative indexes are deliberately -not part of this initial API. - -When `content` and one slice selector are both requested, the slice takes -precedence. Reject multiple distinct content-slice selectors rather than making -array order affect the result. Exact duplicate selectors are harmless. - -## Format Selection - -MCP get, get-by-path, and search accept presentation-only `format`: - -- `yaml` is the default. -- `json` and `compact` both produce compact JSON. - -MCP optional inputs follow the existing tool convention: they may be omitted or -passed as `null`. CLI keeps the existing global `--json` and `--yaml` behavior. - -## Implementation - -1. Add a shared CLI projection module used by both command and MCP code. It owns - selector validation, parsing, and the pure projection helper. -2. Keep projection types local to the CLI package. They describe presentation - output, not JSON-RPC responses. -3. MCP validates `select` and `format`, fetches a complete response through the - existing client, projects locally when requested, then serializes it. -4. CLI get/search fetch complete responses through the existing client and - project locally before rendering. Default text search uses the same helper - for its preview fields. -5. Do not change `packages/protocol`, `packages/server`, `packages/engine`, - `packages/database`, or public client method signatures for projection. -6. Update CLI and MCP documentation only; the TypeScript client API remains - unchanged. - -## Tests - -- Shared projection unit tests: every bare selector, arbitrary metadata keys, - missing metadata keys, duplicate selectors, conflicting content slices, - non-negative safe-integer bounds, content precedence, UTF-16 lengths, and - score omission for get responses. -- CLI command tests: ID and path get, explicit `--select`, default text-search - projection, previews and ellipses, every score value, and JSON/YAML behavior. -- MCP tool tests: select forwarding to the local projector, omitted and `null` - options, empty-selection rejection, full output when omitted, YAML default, - and compact `json`/`compact` output. -- Client regression tests should continue to assert full response types and - unchanged request payloads; no projection overload tests are needed. -- Run `./bun run check` and `./bun run check:full`. - -## Deferred Optimization - -If profiling shows that transferring complete content or metadata from the -server is costly, add a separately reviewed server/API optimization. Preserve -strict SDK typing, potentially through explicit projected methods rather than -an optional parameter that changes the return shape. Do not introduce that -complexity preemptively. diff --git a/docs/access-control.md b/docs/access-control.md index fda8e8ea..a3673033 100644 --- a/docs/access-control.md +++ b/docs/access-control.md @@ -1,6 +1,7 @@ # Access Control -Memory Engine organizes knowledge into **spaces**. Access within a space is granted on **tree paths**, not by role. There is no Row-Level Security — the server computes a caller's effective access and passes it into every database call. +Memory Engine organizes knowledge into **spaces**. Access within a space is +granted on **tree paths**, not by role, and is enforced for every operation. ## Principals @@ -143,9 +144,13 @@ The default group is surfaced on `me space list` and is what `me space invite` t ## How it's enforced -There is no Row-Level Security. For each request, the server calls `build_tree_access(principalId, spaceId)`, which collapses the principal's own grants and those from any groups it belongs to — but **only if the principal is a direct space member** — into a single set of `(tree_path, access)` rows. That set is passed as an argument into the space's SQL functions (`search_memory`, `get_memory`, …), which filter to the paths the caller may see. +Memory Engine combines a member's direct grants with grants from its groups when +evaluating each request. Group grants apply only after the member has joined the +space directly; belonging to a group alone does not make someone a space member. -The authorization gate to use a space at all is direct **space membership** (`principal_space`). A member may have zero tree grants and still authenticate for structural operations such as group management. Data-plane operations still receive the caller's effective grants; someone with no matching `read`/`write` grant sees no memories and cannot write memories. Someone who is only in a group (never joined the space) resolves to an empty access set and is denied at the membership gate. +Direct space membership allows the account to authenticate for space operations, +but data operations still require matching tree access. A member with no +matching `read` or `write` grant sees no memories and cannot create them. :::warning[Quiet filtering] Access filtering happens inside the query. If you lack `read` on a memory's tree path, a search simply returns fewer rows and `me memory get` reports "not found" — you get no error distinguishing "doesn't exist" from "not visible to you." If you're missing results you expect, check your grants with `me access list `. diff --git a/docs/cli/agent-session-imports.md b/docs/cli/agent-session-imports.md index a1ee8ada..b440b3c7 100644 --- a/docs/cli/agent-session-imports.md +++ b/docs/cli/agent-session-imports.md @@ -46,7 +46,12 @@ The whole run imports into a **single** target: the space resolved from `--space Sessions are **skipped and tallied** (not fatal) when a filter excludes them — a system temp cwd (see `--include-temp-cwd`), a trivial session under two user messages (`--include-trivial`), a Claude subagent sidechain (`--include-sidechains`), or a `--since` / `--until` bound — and parse errors are counted separately. `--verbose` lists each skip; `--dry-run` reports the plan without writing. -Project slugs come from the git repo root directory name when the cwd is inside a repo, or from `basename(cwd)` otherwise. Slug collisions (two different cwds that normalize to the same label) are resolved automatically by appending a 4-char hash suffix -- the first cwd seen gets the plain slug, subsequent ones get `slug_`. The full cwd is always preserved in `meta.source_cwd`. +Project slugs come from the git `origin` repository name when available, then the +git repo root directory name, then `basename(cwd)`. Slug collisions (two +different cwds that normalize to the same label) are resolved automatically by +appending a 4-char hash suffix -- the first cwd seen gets the plain slug, +subsequent ones get `slug_`. The full cwd is always preserved in +`meta.source_cwd`. ## Idempotency diff --git a/docs/cli/me-access.md b/docs/cli/me-access.md index a1d3308d..6f4ce275 100644 --- a/docs/cli/me-access.md +++ b/docs/cli/me-access.md @@ -12,7 +12,7 @@ A grant attaches an access **level** to a principal (user, service account, or g `owner` at the empty (root) path owns the whole space. Granting access requires `owner` on the path in question (an admin can self-grant `owner@root`). See [Access Control](../access-control.md). -These commands authenticate with your **session** and operate on the active space. +These commands require an authenticated credential and operate on the active space. ## Commands diff --git a/docs/cli/me-apikey.md b/docs/cli/me-apikey.md index 6ad908f8..41b8c25a 100644 --- a/docs/cli/me-apikey.md +++ b/docs/cli/me-apikey.md @@ -2,7 +2,7 @@ Manage API keys — your own **personal access token (PAT)** or a key for a **service account** (`--service`). -An API key is a global, per-principal credential — **not** inherently bound to a space. An unrestricted key works in any space its principal has been admitted to. A restricted key works only in its declared spaces and is capped to any declared tree grants; the space comes from the `X-Me-Space` header (`--space` / `ME_SPACE`). Keys are formatted `me..`. +An API key is a global, per-principal credential — **not** inherently bound to a space. An unrestricted key works in any space its principal has been admitted to. A restricted key works only in its declared spaces and is capped to any declared tree grants; the space comes from the `X-Me-Space` header (`ME_SPACE` or the selected active space). Keys are formatted `me..`. There are two access modes: an unrestricted key acts with all of its holder's current authority, while a restricted key is capped to declared spaces and @@ -124,3 +124,5 @@ me apikey delete [-y] - [`me service`](me-service.md) -- create service accounts that hold `--service` keys. - [MCP Integration](../mcp-integration.md) -- supply a key to an MCP-connected agent via `--api-key` or `ME_API_KEY`. +- [Harness Integrations](../harness-integrations.md) -- managed harnesses and + machine-local configuration. diff --git a/docs/cli/me-ci.md b/docs/cli/me-ci.md index 2880af64..3a8f1fd3 100644 --- a/docs/cli/me-ci.md +++ b/docs/cli/me-ci.md @@ -29,6 +29,9 @@ The command refuses to replace an existing workflow. Pass `--force` to replace t | `--workflow-only` | Write the workflow without touching credentials. Intended for scripted use. | | `--force` | Replace an existing `.github/workflows/me-import.yml`. | +Outside an interactive terminal, supply `--space` and exactly one of +`--workflow-only` or `--create-service-account`. + In a terminal, choose a space and then either provide an existing service-account key or create a service account. Existing keys are entered without echo and are piped directly to `gh secret set`; they are never written into the workflow or saved by `me`. Creating credentials requires a space admin and an authenticated `gh` CLI with permission to write repository secrets. A newly minted key is created only for immediate placement in the GitHub secret; if placement fails, `me` revokes it. Non-admins can still place an existing key, or receive commands and space-admin contacts for completing setup manually. @@ -39,3 +42,6 @@ The workflow runs these commands explicitly: me import git --tree /share/projects/ me import docs . --git-aware --prune --tree /share/projects/ ``` + +See [Projects](../projects.md) for choosing a shared CI destination and +[Harness Integrations](../harness-integrations.md) for local coding-agent setup. diff --git a/docs/cli/me-claude.md b/docs/cli/me-claude.md index 9fe717b1..80de296c 100644 --- a/docs/cli/me-claude.md +++ b/docs/cli/me-claude.md @@ -17,7 +17,7 @@ me claude install ``` This mechanical, non-interactive command installs the user-scoped dormant -Claude plugin, whose MCP entry runs exactly `me mcp`. It does not prompt, log +Claude plugin, whose managed MCP entry identifies Claude as its harness. It does not prompt, log in, write credentials, backfill sessions, enable capture, select a server or space, or write repository configuration. @@ -36,6 +36,9 @@ An internal SessionStart helper. It provides `AI_AGENT=claude` and `ME_PROJECT_DIR` to Claude shell commands. It does not activate Memory Engine features or set credentials. +See [Harness Integrations](../harness-integrations.md) for how the plugin uses +the machine-local policy at runtime. + ## me claude hook An internal, best-effort capture helper. It exits successfully without a diff --git a/docs/cli/me-codex.md b/docs/cli/me-codex.md index 2e7d82e0..aa70325f 100644 --- a/docs/cli/me-codex.md +++ b/docs/cli/me-codex.md @@ -23,7 +23,7 @@ This non-interactive command registers an identified managed MCP command and installs user-global Codex hooks: a `PreToolUse` Bash hook that only injects `AI_AGENT=codex` and `ME_PROJECT_DIR` into Bash commands, plus transcript-capture hooks (on session `Stop` and `SessionEnd`). The install writes no credentials, -server, space, tree, cwd, or project configuration — capture stays dormant until +server, space, tree, cwd, or repository configuration — capture stays dormant until you enable it and point it at a server, space, and tree with [`me init`](me-init.md) (inspect the policy that applies to a directory with [`me doctor`](me-doctor.md)). @@ -41,10 +41,12 @@ Engine never automates or bypasses this trust approval. me codex uninstall ``` -Removes only the MCP registration and matching `PreToolUse` hook recorded by +Removes the MCP registration and recorded Codex hooks created by `me codex install`, preserving unrelated hook configuration. For manual MCP client configuration, see [MCP Integration](../mcp-integration.md). +See [Harness Integrations](../harness-integrations.md) for the shared +installation and activation model. ### Known gap: Codex Desktop and the VS Code extension diff --git a/docs/cli/me-doctor.md b/docs/cli/me-doctor.md index 054198c5..8ab464e2 100644 --- a/docs/cli/me-doctor.md +++ b/docs/cli/me-doctor.md @@ -35,3 +35,6 @@ An explicit `[directory]` overrides that anchor. The command reports: adapter. Use `--json` or `--yaml` for structured output. + +See [Harness Integrations](../harness-integrations.md) for how profiles are +installed, resolved, and activated. diff --git a/docs/cli/me-group.md b/docs/cli/me-group.md index f4b225e2..e9be0af6 100644 --- a/docs/cli/me-group.md +++ b/docs/cli/me-group.md @@ -4,9 +4,9 @@ Manage groups in the active space. A **group** is a named bundle of members (users and service accounts). Grant access to a group once and every member who is also a space member gets it. Group membership does **not** by itself make someone a space member: a group's grants (and its admin flag, if it's an admin group) apply to a member only once they've **also** joined the space directly — so you can add someone to a group before they join, and the access stays dormant until they do. -Every space is auto-provisioned with a default group named **`team`** (`read` on `/share`, `write` on `/share/projects`). New members join it by default (`me space invite`), so it's the space's baseline shared access. Being a plain group, an admin can freely re-grant, rename, or delete it — see [Access Control](../access-control.md#the-default-team-group). +By default, spaces provision a **`team`** group (`read` on `/share`, `write` on `/share/projects`). Custom-space creation can rename, omit, or create the default group without grants. See [Access Control](../access-control.md#the-default-team-group). -These commands authenticate with your **session** and operate on the active space. +These commands require an authenticated credential and operate on the active space. ## Commands diff --git a/docs/cli/me-import.md b/docs/cli/me-import.md index 5a0e77a0..730607df 100644 --- a/docs/cli/me-import.md +++ b/docs/cli/me-import.md @@ -154,7 +154,7 @@ Each commit is a named leaf (the commit ``) under the project's `git_histor /git_history/ ``` -`` is the full project node — `--tree`, else `~/projects/` (private). The default project slug is derived exactly as for [agent session imports](agent-session-imports.md#tree-layout) (git remote repo name, else repo root directory name), so a project's commit history sits next to its `agent_sessions` node — e.g. a commit lands at `~/projects/memory_engine/git_history/` and is addressable by that path (or at `/share/projects/memory_engine/git_history/` when you pass `--tree /share/projects/memory_engine`). +`` is the full project node — `--tree`, else `~/projects/` (private). The default project slug is derived exactly as for [agent session imports](agent-session-imports.md#tree-layout) (git `origin` repository name, then repo root directory name), so a project's commit history sits next to its `agent_sessions` node — e.g. a commit lands at `~/projects/memory_engine/git_history/` and is addressable by that path (or at `/share/projects/memory_engine/git_history/` when you pass `--tree /share/projects/memory_engine`). ### Content shape @@ -247,9 +247,9 @@ In plain mode, the fallback project slug comes from the import directory basenam Because slots derive from the import root while a git-aware fallback identity stays repo-level, runs rooted at different directories of the same repo would mint parallel corpora under one docs root — and a cross-root `--prune` would delete the other root's slots. So with `--git-aware`, an import root below the repo toplevel is **refused** unless `--allow-subdir-root` is passed; scope with `--include` from the toplevel instead, and if you do opt into a subfolder root, always use the same one. Plain mode needs no guard: it means exactly "the files under this directory". -### Frontmatter is document data, never engine fields +### Frontmatter is document data, never record fields -Unlike [`me import memories`](#me-import-memories) — where frontmatter *is* the memory record — this importer never reads engine fields (`id`, `name`, `tree`, `temporal`) from frontmatter: real-world docs own that vocabulary (Docusaurus `id:`, Hugo `slug:`), and the engine fields derive from the file path so idempotency keys stay stable. The parsed frontmatter object is preserved verbatim under **`meta.doc`**, and it is stripped from the content. Two exceptions use frontmatter *as data*: a string `title:` wins over the first-H1 heuristic for `meta.title`, and `--temporal-key` designates one key as the temporal source. A file whose frontmatter block is invalid YAML (or a non-object) imports with its content verbatim and no `meta.doc` — a broken header never fails the file. +Unlike [`me import memories`](#me-import-memories) — where frontmatter *is* the memory record — this importer never reads record fields (`id`, `name`, `tree`, `temporal`) from frontmatter: real-world docs own that vocabulary (Docusaurus `id:`, Hugo `slug:`), and the record fields derive from the file path so idempotency keys stay stable. The parsed frontmatter object is preserved verbatim under **`meta.doc`**, and it is stripped from the content. Two exceptions use frontmatter *as data*: a string `title:` wins over the first-H1 heuristic for `meta.title`, and `--temporal-key` designates one key as the temporal source. A file whose frontmatter block is invalid YAML (or a non-object) imports with its content verbatim and no `meta.doc` — a broken header never fails the file. ### Content shape diff --git a/docs/cli/me-init.md b/docs/cli/me-init.md index f4451097..7b9025ab 100644 --- a/docs/cli/me-init.md +++ b/docs/cli/me-init.md @@ -35,6 +35,9 @@ profile ready for a later `me install `. On success, `me init` prints the machine-local config path it updated and points to `me init --verbose` for advanced setup. +See [Harness Integrations](../harness-integrations.md) for installation, +uninstallation, configuration storage, and runtime policy details. + ## Verbose setup Use `me init --verbose` to configure a profile step by step. The verbose wizard diff --git a/docs/cli/me-install.md b/docs/cli/me-install.md index 349a2914..53606bf7 100644 --- a/docs/cli/me-install.md +++ b/docs/cli/me-install.md @@ -18,6 +18,11 @@ Each integration registers an identified managed MCP command without credentials, server, space, or project settings. Runtime behavior is configured separately through `me init`. +Installation is user-global; `me init` is directory-specific. If an integration +cannot be installed immediately, configure the directory first and run +`me install ` later. See [Harness Integrations](../harness-integrations.md) +for the lifecycle and ownership rules. + ## Examples ```bash diff --git a/docs/cli/me-login.md b/docs/cli/me-login.md index ef403435..3e022ba6 100644 --- a/docs/cli/me-login.md +++ b/docs/cli/me-login.md @@ -61,6 +61,8 @@ me login --switch - Non-secret settings (default server and per-server active space) live in `~/.config/me/config.yaml`. - **Humans authenticate with a session, not an API key** — `me login` never creates a key. For headless/CLI use where a session isn't available you can mint a **personal access token** with [`me apikey create`](me-apikey.md#me-apikey-create), or restrict one with `me apikey create --allow ::`; team-owned service-account keys come from `me apikey create --service `. - Use [`me logout`](me-logout.md) to clear the session; the non-secret config is kept so re-login resumes. +- See [Harness Integrations](../harness-integrations.md) for how machine-local + harness policy uses this configuration. ## See also diff --git a/docs/cli/me-mcp.md b/docs/cli/me-mcp.md index c704fc15..232aeb61 100644 --- a/docs/cli/me-mcp.md +++ b/docs/cli/me-mcp.md @@ -27,11 +27,14 @@ Resolution order: - **Auth token**: `--api-key` > `ME_API_KEY` > stored session token. - **Space**: `--space` > `ME_SPACE`; without either, MCP is multi-space. -- **Server URL**: `--server` (global option) > `ME_SERVER` > `https://api.memory.build`. +- **Server URL**: `--server` (global option) > `ME_SERVER` > saved default server > + `https://api.memory.build`. A logged-in developer needs no key or space. `--space` and `ME_SPACE` create a locked server: memory tools cannot select another space. Without either, MCP starts multi-space mode: `me_space_list` is available and memory tools require `space`. Your stored active space does not select an MCP space. This supports manual configuration without an active space, for example `me mcp --api-key --server `. Selecting a space never grants access; the server still enforces membership and tree grants. The server acts as the principal represented by that credential. This command is typically not run directly -- it is invoked by AI tools based on their MCP configuration. +For managed harness installation and policy activation, see [Harness +Integrations](../harness-integrations.md). --- @@ -55,6 +58,6 @@ claude plugin install memory-engine@memory-engine [--scope user|project|local] Then configure the plugin with [`me init`](me-init.md). The managed MCP server exposes tools only when the matched local profile enables MCP for Claude. The plugin has no tree setting — where captured sessions are stored is controlled by -your machine-local capture policy ([`me init`](me-init.md)'s `--capture-tree`, -or the private `~/projects` default). See [`me claude`](me-claude.md) for the -full plugin reference. +your machine-local capture policy. Quick `me init` suggests a shared project +tree and offers `~/projects/` for private capture. See +[`me claude`](me-claude.md) for the full plugin reference. diff --git a/docs/cli/me-memory.md b/docs/cli/me-memory.md index 49484e22..a136347b 100644 --- a/docs/cli/me-memory.md +++ b/docs/cli/me-memory.md @@ -99,7 +99,7 @@ me memory search [query] [options] | `--temporal-within ` | Memory must be within this range (`start,end`). | | `--weight-semantic ` | Semantic weight, 0-1. | | `--weight-fulltext ` | Fulltext weight, 0-1. | -| `--order-by ` | Sort direction: `asc` or `desc`. | +| `--order-by ` | For filter-only searches, sort by recency: `desc` (default) or `asc`. | | `--select ` | Comma-separated response fields to return for each result. Omit for full records in JSON/YAML output; the default text view requests an ID, tree, 120-code-unit content preview, and score. | At least one search criterion is required. A positional `query` runs hybrid search by sending the same text to semantic and fulltext ranking. Use `--semantic` for pure vector search, `--fulltext` for pure keyword search, or both flags to provide different text for each mode. diff --git a/docs/cli/me-opencode.md b/docs/cli/me-opencode.md index 042a3489..d4a23a96 100644 --- a/docs/cli/me-opencode.md +++ b/docs/cli/me-opencode.md @@ -35,6 +35,9 @@ Removes only recorded MCP/plugin artifacts when they remain unchanged. Turning capture on (and pointing it at a server/space/tree) is a separate, machine-local step — see [`me init`](me-init.md). +See [Harness Integrations](../harness-integrations.md) for the managed +installation lifecycle and policy model. + --- ## me opencode hook diff --git a/docs/cli/me-pack.md b/docs/cli/me-pack.md index 55bd4438..e1de252c 100644 --- a/docs/cli/me-pack.md +++ b/docs/cli/me-pack.md @@ -120,4 +120,4 @@ List installed packs in the active space. me pack list ``` -Searches for all memories with pack metadata and displays a table grouped by pack name, showing name, version, and memory count. +Searches up to 1,000 memories with pack metadata and displays a table grouped by pack name, showing name, version, and memory count. diff --git a/docs/cli/me-serve.md b/docs/cli/me-serve.md index 3cb4b727..bd831807 100644 --- a/docs/cli/me-serve.md +++ b/docs/cli/me-serve.md @@ -17,7 +17,7 @@ me serve [--port ] [--host ] [--no-open] Starts a local HTTP server that: - Serves a React-based UI for browsing, searching, viewing, editing, and deleting memories. -- Proxies JSON-RPC calls from the browser to the server, injecting your stored session token so it never leaves the machine. +- Proxies JSON-RPC calls from the browser to the server, injecting the resolved bearer credential (your session or `ME_API_KEY`) so it never reaches the browser. By default the server binds to `127.0.0.1:3000`; if 3000 is busy it tries 3001, 3002, … up to 3019 before giving up. Passing `--port` explicitly is strict — it does not auto-increment. diff --git a/docs/cli/me-space.md b/docs/cli/me-space.md index 56019cd7..b6882f03 100644 --- a/docs/cli/me-space.md +++ b/docs/cli/me-space.md @@ -4,7 +4,7 @@ Manage spaces. A **space** is an isolated collection of memories with its own roster, groups, and access grants. It is identified by an immutable 12-character **slug** (also the `X-Me-Space` header value) and a renamable display **name**. Your *active* space is the one carried on every memory command; set it with `me space use` (or `me login `). -These commands authenticate with your **session** (humans only — `me login`). Invitations operate on the active space. +These commands require an authenticated credential. Invitations operate on the active space. ## Commands @@ -14,7 +14,7 @@ These commands authenticate with your **session** (humans only — `me login`). - [me space create](#me-space-create) -- create a space - [me space rename](#me-space-rename) -- rename a space - [me space delete](#me-space-delete) -- delete a space -- [me space remove-member](#me-space-remove-member) -- remove a user from the space (admin) +- [me space remove-member](#me-space-remove-member) -- remove a member from the space (admin) - [me space leave](#me-space-leave) -- remove yourself from the space - [me space invite](#me-space-invite) -- invite a user (and manage invitations) @@ -89,7 +89,7 @@ me space create [--no-home-grants] [--default-group ] | `--no-default-group-grants` | Create the default group **without** `read@/share` + `write@/share/projects` — a grantless group you configure by hand. | | `--no-default-group` | Don't create a default group at all. | -The name, grants, and existence of the default group are independent axes, so `--default-group team` behaves exactly like the bare default. A fully manual, god-mode space is just `--no-home-grants --no-default-group`. Conflicting combinations error (e.g. `--no-default-group` with `--default-group` or `--no-default-group-grants`). +The name, grants, and existence of the default group are independent axes, so `--default-group team` behaves exactly like the bare default. A fully manual, god-mode space is just `--no-home-grants --no-default-group`. Conflicting combinations error (for example, `--no-default-group` with `--no-default-group-grants`). > **Note:** in a space where members get no automatic access (`--no-home-grants` and no granted default group), a fresh joiner holds **zero grants and is locked out** until you grant them access — grant a default group `read@/share` once and invite through it (see [`me space invite`](#me-space-invite)) so joiners land read-only. @@ -130,7 +130,7 @@ me space delete [--force] ## me space remove-member -Remove a **user** from the active space's roster, scrubbing their access grants and group memberships in that space. **Admin only.** +Remove a **user or service account** from the active space's roster, scrubbing their access grants and group memberships in that space. **Admin only.** ``` me space remove-member [-y] @@ -138,13 +138,13 @@ me space remove-member [-y] | Argument | Required | Description | |----------|----------|-------------| -| `principal` | yes | User **id or name** to remove. | +| `principal` | yes | User or service-account **id or name** to remove. | | Option | Description | |--------|-------------| | `-y, --yes` | Skip the confirmation prompt. | -Members only: a **group** cannot be removed this way — a group leaves a space only by being deleted (`me group delete`). Service accounts are deleted with [`me service delete`](me-service.md#me-service-delete), which also deletes their bound admin group. Passing a group name errors clearly. Removing the space's **sole admin** is rejected (`LAST_ADMIN`) — promote another admin first. To remove yourself, use [`me space leave`](#me-space-leave). +A **group** cannot be removed this way — a group leaves a space only by being deleted (`me group delete`). Use [`me service delete`](me-service.md#me-service-delete) when you also want to delete a service account and its keys. Passing a group name errors clearly. Removing the space's **sole admin** is rejected (`LAST_ADMIN`) — promote another admin first. To remove yourself, use [`me space leave`](#me-space-leave). --- @@ -160,7 +160,7 @@ me space leave [-y] |--------|-------------| | `-y, --yes` | Skip the confirmation prompt. | -If you are the space's **sole admin**, the leave is rejected (`LAST_ADMIN`) — promote another admin (e.g. `me space invite --admin`, or add one) before leaving so the space keeps at least one. +Only user principals can leave; service accounts must be removed by an admin or deleted. If you are the space's **sole admin**, the leave is rejected (`LAST_ADMIN`) — promote another admin before leaving so the space keeps at least one. --- @@ -188,7 +188,7 @@ Example — invite into two groups: me space invite --email alice@example.com --group team --group backend ``` -Exactly one of `--email` or `--anyone` is required. A joining user always receives `owner@home` (their private root); their **shared** access comes from the group they join — the default `team` group grants `read` on `/share` and `write` on `/share/projects`. See [Access Control](../access-control.md#the-default-team-group) for changing these defaults. +Exactly one of `--email` or `--anyone` is required. Unless the space was created with `--no-home-grants`, a joining user receives `owner@home` (their private root). Their **shared** access comes from the group they join. See [Access Control](../access-control.md#the-default-team-group) for changing defaults. ### me space invite list @@ -200,15 +200,15 @@ me space invite list ### me space invite revoke -Revoke a pending invitation by email. +Revoke a pending invitation by its email or an open-link invitation by ID. ``` -me space invite revoke +me space invite revoke ``` | Argument | Required | Description | |----------|----------|-------------| -| `email` | yes | The invited email to revoke. | +| `id-or-email` | yes | The invited email or open-link invitation ID to revoke. | ## See also diff --git a/docs/cli/me-uninstall.md b/docs/cli/me-uninstall.md index ee9292f0..2ab33ddc 100644 --- a/docs/cli/me-uninstall.md +++ b/docs/cli/me-uninstall.md @@ -1,13 +1,14 @@ # me uninstall Remove Memory Engine harness integrations previously installed by `me install`. -Only deployment records in `~/.config/me/installations.yaml` are removed; -unrecorded harness configuration is left untouched. +Memory Engine uses its installation record to remove the artifacts it owns; +unrecorded or modified provider configuration is left untouched. ## Usage ```bash me uninstall [claude|opencode|codex...] +me uninstall --purge [claude|opencode|codex...] ``` With no harness names, `me uninstall` removes every recorded integration. @@ -28,3 +29,9 @@ me uninstall codex The equivalent single-harness commands are `me claude uninstall`, `me opencode uninstall`, and `me codex uninstall`. + +Uninstalling a harness does not remove your machine-local `me init` policy +unless you pass `--purge`. That option also removes the selected harness from +your saved activation profiles. See [Harness +Integrations](../harness-integrations.md) for the distinction between +installation and activation. diff --git a/docs/cli/me-upgrade.md b/docs/cli/me-upgrade.md index 9160bd29..711401ac 100644 --- a/docs/cli/me-upgrade.md +++ b/docs/cli/me-upgrade.md @@ -15,8 +15,9 @@ release version is newer than the running CLI version, it downloads the matching platform binary, verifies its `.sha256` checksum, applies the same macOS signing fixups as `install.sh`, and replaces the running `me` executable. -The command must be run from an installed `me` binary. It refuses to replace a -non-`me` executable such as the Bun runtime used during local development. +Installation is supported on Linux and Apple Silicon macOS. Windows self-upgrade +is unsupported, and macOS x64 is rejected. Replacing an installed release requires +an executable named `me` with write permission; `--check` can run from development. ## Options diff --git a/docs/concepts.md b/docs/concepts.md index 2cf6dcf1..a3424310 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -273,6 +273,9 @@ A **space** is an isolated collection of memories with its own roster, groups, a - A roster of **principals** -- users, service accounts, and groups. - Tree-access grants that control who can read/write/own which paths. -A space is identified by an immutable 12-character **slug** (also the `X-Me-Space` header value) and a renamable display **name**. A user can belong to many spaces; service accounts are created inside one space; each memory lives in exactly one space. There are no organization, engine, or shard concepts above a space. +A space is identified by an immutable 12-character **slug** (also the +`X-Me-Space` header value) and a renamable display **name**. A user can belong +to many spaces; service accounts are created inside one space; each memory lives +in exactly one space. Manage spaces with [`me space`](cli/me-space.md), and see [Access Control](access-control.md) for principals and grants. diff --git a/docs/getting-started.md b/docs/getting-started.md index b66de073..8e082a21 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -24,7 +24,7 @@ This opens your browser to sign in via GitHub or Google (an OAuth 2.1 auth-code On a **headless** host with no local browser (an agent harness in a sandbox, a remote SSH session, a container), use `me login --device` instead: the CLI prints a short URL and code to approve on any device (the OAuth 2.0 device authorization grant), yielding a rolling 7-day session token. See [`me login`](cli/me-login.md). -If you belong to more than one space, pick the active one (it's carried as the `X-Me-Space` on every request): +If you belong to more than one space, pick the active one: ```bash me space list @@ -44,12 +44,12 @@ me version ```bash me memory create "PostgreSQL 18 supports native UUIDv7 generation." \ - --tree share/notes/postgres \ + --tree '~/notes/postgres' \ --name uuidv7 \ --meta '{"topic": "database"}' ``` -A `--tree` is required. Put memories the rest of your space should see under `share/*`, and personal ones under `~/*` (your private home). The optional `--name` gives the memory a filename-like slug (unique within its tree) so you can later address it by path -- `me get share/notes/postgres/uuidv7`. See [Core Concepts](concepts.md#reserved-roots). +A `--tree` is required. Put memories the rest of your space should see under `/share/*`, and personal ones under `~/*` (your private home). The optional `--name` gives the memory a filename-like slug (unique within its tree) so you can later address it by path -- `me get '~/notes/postgres/uuidv7'`. See [Core Concepts](concepts.md#reserved-roots). ## Search @@ -77,33 +77,54 @@ For a richer, visual experience there's a web UI with a tree explorer, hybrid / - **Hosted (no install):** open [**api.memory.build**](https://api.memory.build/) and sign in with GitHub or Google — the same account you'd use for `me login`. This is the quickest way in if you don't want to touch the CLI. - **Local:** run `me serve` to start the same UI against your CLI session on `http://127.0.0.1:3000` (or the next free port). See [`me serve`](cli/me-serve.md). -## Connect to AI tools +## Connect To AI Coding Tools -Register Memory Engine with your AI coding tools: +Memory Engine has first-class integrations for [Claude +Code](https://docs.anthropic.com/en/docs/claude-code), +[OpenCode](https://opencode.ai/), and [Codex +CLI](https://developers.openai.com/codex/cli/). An integration gives the coding +harness access to Memory Engine tools through MCP. You can also opt in to session +capture and configure where `me` commands run by the harness connect. For other +MCP-compatible coding harnesses, see [MCP Integration](mcp-integration.md). + +Install each harness integration once for your user account. Then configure it +machine-wide through fallback defaults, or for one directory and its descendants. +`me init` is the guided tool for that configuration. + +From a repository you want to configure, run: ```bash -me opencode install -me codex install +me init ``` -For Claude Code, `me claude install` installs the one user-scoped Memory Engine plugin (hooks + slash commands + MCP) — run it once, it applies to every project: +This is the recommended coding-agent setup. It signs you in when needed, +selects a space, enables MCP tools and `me` command routing for detected +harnesses, and offers to install missing integrations. It also asks whether to +enable session capture. + +Capture is off by default. When enabled, its suggested tree is +`/share/projects/` for team knowledge; choose +`~/projects/` instead for private captures. + +Verify the result with: ```bash -me claude install # full plugin (once, user scope) -me claude install --mcp-only # or just the MCP server +me doctor ``` -This drives Claude Code's native plugin flow for you (`claude plugin marketplace add` + `claude plugin install`) and installs the plugin **dormant**: it does not log in, write credentials, or turn on capture. Restart Claude Code (or run `/plugin`) afterwards to load the hooks and slash commands. - -Session capture is **off by default** and configured machine-locally. Turn it on — and choose where a project's memories land — with [`me init`](cli/me-init.md): captures default **privately** to `~/projects/`, and sharing with a team is a per-directory choice via a `--capture-tree` under `/share/projects/...` (see [Projects](projects.md)). Use [`me doctor`](cli/me-doctor.md) to see which policy applies to a directory. +Use `me init --verbose` to configure MCP, capture, and CLI routing separately. -After installation, your AI agent has access to memory tools -- create, search, get, update, delete, and more. +`me init` works in headless or unattended environments, use `me login --device` or supply an +API key through `ME_API_KEY`. -See [MCP Integration](mcp-integration.md) for details. +Read [Harness Integrations](harness-integrations.md) for installation, +uninstallation, and machine-local policy details. See [MCP Integration](mcp-integration.md) +for manual MCP client configuration. ## What's next - [Core Concepts](concepts.md) -- understand memories, tree paths, metadata, search modes +- [Harness Integrations](harness-integrations.md) -- install coding-agent integrations and configure local policy - [Projects](projects.md) -- set up repository memory trees and project grants - [Access Control](access-control.md) -- spaces, principals, and tree-access grants - [Memory Packs](memory-packs.md) -- install pre-built knowledge collections diff --git a/docs/harness-integrations.md b/docs/harness-integrations.md new file mode 100644 index 00000000..d5c680db --- /dev/null +++ b/docs/harness-integrations.md @@ -0,0 +1,207 @@ +# Harness Integrations + +Memory Engine has first-class integrations for [Claude +Code](https://docs.anthropic.com/en/docs/claude-code), +[OpenCode](https://opencode.ai/), and [Codex +CLI](https://developers.openai.com/codex/cli/). An integration gives the coding +harness access to Memory Engine tools through MCP. You can also opt in to session +capture and configure where `me` commands run by the harness connect. For other +MCP-compatible coding harnesses, see [MCP Integration](mcp-integration.md). + +Install each harness integration once for your user account. Then configure it +machine-wide through fallback defaults, or for one directory and its descendants. +`me init` is the guided tool for that configuration. + +Integrations have two separate layers: + +1. **Install** registers user-global, provider-specific plumbing for a coding + harness. +2. **Initialize** enables Memory Engine for a directory through your + machine-local policy. + +This separation lets you install an integration once, then choose where and how +it is active without committing configuration or credentials to a repository. + +## Quick Setup + +For the usual per-repository setup, run this from the repository root: + +```bash +me init +``` + +Quick setup: + +- signs you in when needed; +- selects a space, automatically when you have only one; +- enables MCP tools and `me` command routing for every detected coding harness; +- offers optional session capture; and +- offers to install missing detected integrations. + +When capture is enabled, its default destination is +`/share/projects/`. Enter `~/projects/` at the prompt +to keep captures private. + +Run `me doctor` afterwards to inspect the profile that applies to the current +directory. Use `me init --verbose` when you need to configure MCP, capture, and +CLI routing independently. + +### Import Existing Sessions + +To import sessions created before capture was enabled, run `me import claude`, +`me import codex`, or `me import opencode`. See [Agent session +imports](cli/agent-session-imports.md) for options, destination trees, and +idempotent re-imports. + +## Install And Uninstall + +Use the aggregate commands to manage detected harnesses: + +```bash +me install +me uninstall +``` + +Or target a harness directly: + +```bash +me install claude +me install opencode codex +me uninstall codex +``` + +Installation writes only the provider's user-global integration artifacts. It +does not log in, persist an API key, select a space, enable capture, or write +repository configuration. The policy created by `me init` decides whether a +managed integration is active for a directory. + +Memory Engine records the artifacts it creates. Uninstall removes recorded +artifacts when they are unchanged, and preserves unrecorded or modified provider +configuration for you to review manually. Add `--purge` to also remove the +selected harness from your saved activation profiles. + +| Harness | Installation result | Additional step | +| --- | --- | --- | +| Claude Code | Managed plugin with MCP and capture plumbing | Restart Claude Code after installation | +| OpenCode | Managed MCP entry and generated plugin | Restart OpenCode after installation | +| Codex CLI | Managed MCP entry and user-global hooks | Approve hooks with `/hooks`; restart after environment changes | + +See the provider references for details: [`me claude`](cli/me-claude.md), +[`me opencode`](cli/me-opencode.md), and [`me codex`](cli/me-codex.md). + +## Machine-Local Configuration + +By default, Memory Engine stores non-secret configuration in: + +```text +~/.config/me/config.yaml +``` + +If `XDG_CONFIG_HOME` is set, Memory Engine uses `$XDG_CONFIG_HOME/me` instead. +The file holds your default server, active spaces, and harness policy. It does +not store API keys. Login sessions use your system keychain when available (or a +protected credentials file), and an API key is supplied only through +`ME_API_KEY` or an explicit command option. + +The policy has two scopes: + +- **Defaults** apply when no directory profile matches. +- **Directory profiles** apply to their directory and descendants. The most + specific matching directory wins. + +A matching directory profile is complete: omitted MCP, capture, or CLI routing +surfaces are disabled rather than inherited from defaults. This makes a +directory's behavior predictable. + +## Policy Surfaces + +Each profile can configure three independent surfaces: + +- **MCP** controls which managed harnesses receive Memory Engine tools and the + server and space they use. +- **Capture** controls whether supported harness sessions are imported and where + they are stored. +- **CLI routing** controls the server and optional space for `me` commands that + a selected harness runs. It never retargets commands you run in your own + shell. + +An illustrative directory profile looks like this: + +```yaml +directories: + /Users/me/work/acme-api: + mcp: + enabled: true + server: https://api.memory.build + space: acme123def45 + harnesses: + codex: true + opencode: true + capture: + enabled: true + server: https://api.memory.build + space: acme123def45 + tree: /share/projects/acme-api + harnesses: + codex: true + opencode: true + cli: + server: https://api.memory.build + space: acme123def45 + harnesses: + codex: true + opencode: true +``` + +Use `me init --verbose` instead of editing the file when you want guided +configuration. Use `me doctor [directory]` to see which profile and surfaces +are effective. + +## Runtime Context And Overrides + +Harness integrations provide `AI_AGENT` and `ME_PROJECT_DIR` to identify the +harness and its directory. These are runtime context for the integration, not +repository configuration. + +For normal CLI commands, explicit flags and `ME_*` environment variables take +precedence over saved machine-local settings. Managed MCP processes also consult +the matching MCP policy: if the policy is disabled or does not select that +harness, the process starts without Memory Engine tools. + +Manual `me mcp` usage is separate. It can be locked to one space with +`--space` or `ME_SPACE`, or run in multi-space mode without either. See [MCP +Integration](mcp-integration.md) for manual configuration. + +## Headless And Codex Environments + +On a headless host, sign in with a device code: + +```bash +me login --device +``` + +For unattended use, provide a personal or service-account key through +`ME_API_KEY`. Codex forwards `ME_API_KEY`, `ME_SERVER`, and `ME_SPACE` to its +managed MCP process when those variables are present in Codex's environment; it +does not write their values to `~/.codex/config.toml`. Restart Codex after +changing those variables. + +Codex hooks require one-time approval through `/hooks`. Memory Engine never +automates or bypasses that approval. + +## Troubleshooting + +Start with: + +```bash +me doctor +me status +``` + +`me doctor` reports the resolved profile, active and inactive surfaces, the +directory anchor, and harness-specific MCP diagnostics. If an integration was +installed but remains inactive, verify that the profile selects that harness and +that the harness has been restarted. + +See also [`me init`](cli/me-init.md), [`me install`](cli/me-install.md), +[`me uninstall`](cli/me-uninstall.md), and [`me doctor`](cli/me-doctor.md). diff --git a/docs/mcp-integration.md b/docs/mcp-integration.md index 7131e54e..24e05a9f 100644 --- a/docs/mcp-integration.md +++ b/docs/mcp-integration.md @@ -1,235 +1,121 @@ # MCP Integration -Memory Engine integrates with AI coding agents via the [Model Context Protocol](https://modelcontextprotocol.io/) (MCP). This gives agents 15 memory tools they can use to store and retrieve knowledge across conversations. - -## How it works - -When an AI tool launches `me mcp`, it spawns a child process that communicates over **stdin/stdout** using the MCP protocol. The process is a stateless proxy — each tool call is translated into an HTTP request to the Memory Engine API. No data is stored locally. +Memory Engine connects AI agents to persistent memory through the [Model Context +Protocol](https://modelcontextprotocol.io/) (MCP). An MCP client starts `me mcp` +as a local stdio process; that process sends authenticated requests to Memory +Engine on the agent's behalf. +```text +AI coding agent <-> me mcp (stdio) <-> Memory Engine (HTTPS) ``` -┌──────────────┐ stdio (JSON-RPC) ┌──────────┐ HTTPS ┌────────────────┐ -│ AI Agent │ ◄──────────────────► │ me mcp │ ────────► │ Memory Engine │ -└──────────────┘ └──────────┘ └────────────────┘ -``` - -The AI agent never sees or handles credentials — it just calls MCP tools and gets results back. - -Each `me mcp` instance uses one of two space modes. An explicit `--space` or `ME_SPACE` creates a **locked** server: memory tools do not expose a `space` parameter and every call uses that space. Without either selector, it starts in **multi-space** mode: `me_space_list` is available and every memory tool requires `space`. Project configuration and your stored active space never select an MCP space. This makes manual MCP setup work without any local Memory Engine configuration. Authentication is either an API key (`--api-key` or `ME_API_KEY`: a user PAT or service-account key) or, if no key is given, your stored `me login` session token — so a developer install needs no key at all. The server URL defaults to `https://api.memory.build` but can be overridden with `--server` or `ME_SERVER`. - -MCP calls always run as the principal represented by the presented credential. A normal local install uses your login session. For an unattended or restricted installation, pass a restricted PAT or service-account key and expose no stronger credential to that process. A per-tool `space` only selects the target space; the server still checks membership and the credential's grants, including restricted-key declarations. - -## Setup -### Prerequisites +The agent calls tools. It does not need to handle a Memory Engine credential +directly. -Log in with `me login` to run the MCP server locally. Selecting a space is optional: omitting it starts multi-space mode. For an unattended installation, mint an API key and pass it with `--api-key`. For least privilege, use a restricted PAT or service-account key, for example `me apikey create mcp --allow :/share/project:w`. +## Managed Harness Setup -The server defaults to `https://api.memory.build`. Pass `--server ` only if you're running a self-hosted server. - -### Agent-specific installers +For Claude Code, OpenCode, or Codex CLI, use the managed integration path: ```bash -me opencode install -me codex install +me init ``` -These commands register Memory Engine with the named tool, writing a `me mcp` invocation into the tool's MCP configuration. By default they embed no key — the server uses your `me login` session at runtime. Pass `--api-key` to pin a user PAT or service-account key instead, `--space ` to pin a space, and `--server ` to pin a non-default server. For a restricted key, the pinned space must be one of its declarations. - -### Manual multi-space setup - -You can configure any stdio MCP client directly, without running a harness installer or creating a local harness profile: +Run it from the repository you want to configure. It enables MCP for detected +harnesses and offers to install any missing integrations. Managed integrations +are user-global, but MCP availability is controlled by the machine-local profile +that applies to the harness directory. -```bash -me mcp --server --api-key -``` +If the matched profile disables MCP or does not select the harness, its managed +MCP process starts without Memory Engine tools. Use `me doctor --harness ` +to inspect the effective policy. -This starts multi-space mode. Call `me_space_list` first, then pass one -of its slugs as the required `space` argument to a memory tool. Add `--space -` only when you want to lock that MCP process to one space. +Installation and policy configuration are separate operations. See [Harness +Integrations](harness-integrations.md) for the lifecycle, provider-specific +requirements, and `~/.config/me/config.yaml`. -See the agent-specific command references for details: [`me opencode install`](cli/me-opencode.md#me-opencode-install) and [`me codex install`](cli/me-codex.md#me-codex-install). +## Authentication -These installers are mechanical and **dormant**: they wire up the MCP entry and (for Claude Code, OpenCode, and Codex) the capture plumbing, but they never log in, write credentials, or turn anything on. Enable capture and point it at a server, space, and tree with [`me init`](cli/me-init.md); inspect the policy that applies to a directory with [`me doctor`](cli/me-doctor.md). +`me mcp` authenticates in this order: -| Tool | Install command | -|------|-----------------| -| OpenCode | `me opencode install` | -| Codex CLI | `me codex install` | -| Claude Code | `me claude install` (full plugin) / `me claude install --mcp-only` | +1. `--api-key ` +2. `ME_API_KEY` +3. Your stored `me login` session -### Claude Code +A local developer installation normally uses the stored session. For a +headless, unattended, or least-privilege environment, use a personal access +token or service-account key through `ME_API_KEY`. Memory Engine never persists +API keys for you. -```bash -me claude install # full plugin: hooks + slash commands + MCP -me claude install --mcp-only # or just the MCP server -``` +The server URL resolves from `--server`, then `ME_SERVER`, then the saved +default server, then `https://api.memory.build`. -By default `me claude install` installs the Memory Engine plugin, driving Claude Code's native plugin flow for you (`claude plugin marketplace add` + `claude plugin install`) without pinning `server`, `space`, or `api_key` into the plugin. The plugin resolves your live `me` login config at runtime. Pass `--server` / `--space` to pin those values, or `--api-key` for a headless install that bakes in a fixed key + space. The plugin provides the MCP server and captures Claude Code session events as memories. After installing, restart Claude Code (or run `/plugin`) to load the hooks and slash commands; re-run `/plugin` → `memory-engine` → Configure to adjust options. To run the underlying flow by hand instead: +## Space Modes -```bash -claude plugin marketplace add timescale/memory-engine -claude plugin install memory-engine@memory-engine [--scope user|project|local] -``` +An explicit `--space` or `ME_SPACE` creates a **locked** MCP server. Its memory +tools use that one space and do not expose a `space` parameter. -See [`me claude install`](cli/me-claude.md#me-claude-install) for the full option reference. +Without either selector, manual `me mcp` starts in **multi-space** mode. +`me_space_list` is available and memory tools require a `space` argument. A +stored active space does not lock a manual MCP server. -### Codex CLI +Selecting a space never grants access. Every operation remains constrained by +the authenticated principal's membership and tree grants. -```bash -me codex install -``` +## Manual Stdio Setup -To configure manually: +Any MCP client that supports stdio can start Memory Engine directly: ```bash -codex mcp add me -- me mcp --api-key --space --server -``` - -### OpenCode - -`me opencode install` edits `~/.config/opencode/opencode.json` directly, adding an entry under `mcp.me`. To configure manually, add this to that file: - -```json -{ - "mcp": { - "me": { - "type": "local", - "command": ["me", "mcp", "--api-key", "", "--space", "", "--server", ""] - } - } -} -``` - -### VS Code / GitHub Copilot - -Add a `.vscode/mcp.json` file to your workspace: - -```json -{ - "servers": { - "me": { - "command": "me", - "args": ["mcp", "--api-key", "", "--space", "", "--server", ""] - } - } -} +me mcp ``` -This makes Memory Engine available to GitHub Copilot in agent mode. Commit this file to share the configuration with your team (use environment variables or input variables for the API key in shared configs). - -To configure globally across all workspaces, open the Command Palette and run **MCP: Open User Configuration**. - -### Zed - -Open your Zed settings (`Zed > Settings > Open Settings` or `~/.config/zed/settings.json`) and add: - -```json -{ - "context_servers": { - "me": { - "command": "me", - "args": ["mcp", "--api-key", "", "--space", "", "--server", ""] - } - } -} -``` +Give the client a Memory Engine session through its normal process environment, +or allow it to forward `ME_API_KEY`, `ME_SERVER`, and optionally `ME_SPACE`. +For example, a client configuration can launch `me mcp` and allowlist those +environment variable names rather than storing a raw key in its config file. -After saving, check the Agent Panel settings — the indicator next to "me" should turn green when the server is active. - -### Other MCP clients - -Any tool that supports the MCP stdio transport can use Memory Engine. The server command is: +Use a locked manual server only when every request should stay in one space: ```bash -me mcp --api-key --server +me mcp --space ``` -Point your client at this command with `stdio` as the transport type. This is -multi-space mode: call `me_space_list`, then pass `space` to each memory -tool. Add `--space ` to lock the server to one space instead. - -## Available tools - -Once connected, the agent has access to: - -| Tool | Purpose | -|------|---------| -| `me_space_list` | List spaces available for per-call selection (multi-space mode only) | -| `me_memory_context` | Show current identity, active space, and effective access | -| `me_memory_create` | Store a new memory | -| `me_memory_search` | Search by meaning, keywords, or filters | -| `me_memory_get` | Retrieve a memory by ID | -| `me_memory_get_by_path` | Retrieve a named memory by its `tree/name` path | -| `me_memory_update` | Modify an existing memory | -| `me_memory_delete` | Delete a memory by ID | -| `me_memory_delete_by_path` | Delete a named memory by its `tree/name` path | -| `me_memory_delete_tree` | Bulk delete by tree prefix | -| `me_memory_count` | Count memories matching a tree filter | -| `me_memory_copy` | Copy memories between tree paths | -| `me_memory_mv` | Move memories between tree paths | -| `me_memory_tree` | View the tree structure | -| `me_memory_import` | Bulk import from file or content | -| `me_memory_export` | Bulk export with filters | - -See the [MCP Tool Reference](mcp/index.md) for detailed documentation on each tool. +For multi-space use, call `me_space_list` first and pass the returned slug to +each memory tool. -## The AGENTS.md pattern +## Provider Notes -The most effective way to use Memory Engine with AI agents is the **AGENTS.md pattern**: put a file called `AGENTS.md` in your project root that teaches the agent how to use memory. - -A good AGENTS.md includes: - -- **Memory map** -- what's stored where in the tree hierarchy, so the agent knows what to search for. -- **Search examples** -- concrete examples of semantic, fulltext, and hybrid searches. -- **Conventions** -- your tree path structure, metadata conventions, and when to store vs. search. -- **Proactive search instructions** -- tell the agent to search memory before starting work, when making decisions, and after completing work. - -### Example - -```markdown -# Project Memory - -This project uses Memory Engine for persistent knowledge. - -## Memory Map +### Claude Code -- `/share/design/*` -- architecture decisions and design docs -- `/share/research/*` -- research findings and comparisons -- `/share/bugs/*` -- known issues and workarounds +Install with `me claude install`, then restart Claude Code. The managed plugin +supplies MCP and capture plumbing; `me init` decides when they are active. -## How to Search +### OpenCode -Search memory proactively: -- Before starting work: search for prior art and context -- When making decisions: check if the topic was decided before -- After completing work: store decisions and findings +Install with `me opencode install`, then restart OpenCode. It writes a managed +MCP entry and generated plugin without credentials or runtime targeting. -## Search Examples +### Codex CLI -# Hybrid search (meaning + keywords) -me_memory_search({semantic: "database-generated identifiers", fulltext: "database-generated identifiers"}) +Install with `me codex install`, then approve the installed hooks through +`/hooks`. Codex forwards `ME_API_KEY`, `ME_SERVER`, and `ME_SPACE` to the +managed MCP process when they exist in Codex's environment. Restart Codex after +changing those variables. -# Semantic search (by meaning) -me_memory_search({semantic: "how does authentication work"}) +See [`me claude`](cli/me-claude.md), [`me opencode`](cli/me-opencode.md), and +[`me codex`](cli/me-codex.md) for provider details. -# Keyword search -me_memory_search({fulltext: "OAuth JWT"}) +## Available Tools -# Browse a section -me_memory_search({tree: "/share/design/*"}) -``` +Once connected, an agent can inspect its context, store and search memories, +manage trees, and import or export records. See the [MCP Tool Reference](mcp/index.md) +for every tool and [MCP Agent Instructions](mcp/agent-instructions.md) for +recommended agent behavior. ## Troubleshooting -### MCP server shows "failed" or "disabled" - -1. Verify the `me` binary is on your PATH: `which me` -2. Test the server directly: `echo '{}' | me mcp --api-key --space --server ` -3. Re-install with the agent-specific command, for example `me opencode install` or `me codex install`. For Claude Code, open `/plugin` and reconfigure `memory-engine`. - -### Agent can't find memories - -1. Check the MCP execution context with `me_memory_context`. -2. Check that the correct space is active: `me whoami`. -3. Verify memories exist: `me memory search --fulltext ""`. -4. Check that embeddings have been computed: `me memory get ` (look for `hasEmbedding: true`). +1. Confirm `me` is on the MCP client's `PATH`. +2. Run `me status` to check local credentials. +3. Run `me doctor --harness ` for a managed integration. +4. Restart the harness after changing its installation or environment. +5. For Codex, confirm its hooks are approved through `/hooks`. diff --git a/docs/mcp/agent-instructions.md b/docs/mcp/agent-instructions.md index 9b4cc981..2f76b605 100644 --- a/docs/mcp/agent-instructions.md +++ b/docs/mcp/agent-instructions.md @@ -1,8 +1,8 @@ # MCP Agent Instructions This page is for AI agents that already have Memory Engine MCP tools available. -It explains how to use memory during work. For setup, see -[MCP Integration](../mcp-integration.md). +It explains how to use memory during work. For setup, see [MCP Integration](../mcp-integration.md) +and [Harness Integrations](../harness-integrations.md). ## Use Memory Proactively @@ -87,6 +87,8 @@ Do not assume every space has the same layout or grants. Some spaces use may use different defaults or grant only selected paths. Choose a tree from the user's instructions, the project's memory map, prior memories, or visible tree structure. If the right writable tree is unclear, ask the user before storing. +`~/...` is valid only for user principals; service accounts must use explicitly +granted paths such as `/share/...`. ## Store Useful Memories @@ -99,7 +101,7 @@ Prefer this: { "tree": "/share/decisions/auth", "name": "device-flow-session-token", - "content": "Device login returns a bearer session token, not an OAuth refresh token. Treat it as a 7-day sliding session accepted by the resource-server middleware.", + "content": "Device login returns a bearer session token, not an OAuth refresh token. Treat it as a 7-day sliding session.", "meta": { "type": "decision", "topic": "auth" } } ``` diff --git a/docs/mcp/index.md b/docs/mcp/index.md index 234ad936..fa41b4e6 100644 --- a/docs/mcp/index.md +++ b/docs/mcp/index.md @@ -1,6 +1,6 @@ # MCP Tool Reference -Memory Engine exposes 15 memory tools to AI agents over the [Model Context Protocol](https://modelcontextprotocol.io/). In multi-space mode it also exposes a space-discovery tool. Once an agent is connected (see [MCP Integration](../mcp-integration.md)), it can inspect its context, store, search, and organize memories with the tools below. +Memory Engine exposes 15 memory tools to AI agents over the [Model Context Protocol](https://modelcontextprotocol.io/). In multi-space mode it also exposes a space-discovery tool. Once an agent is connected (see [MCP Integration](../mcp-integration.md)), it can inspect its context, store, search, and organize memories with the tools below. Managed coding-agent setup is described in [Harness Integrations](../harness-integrations.md). If you are an agent using these tools, start with [MCP Agent Instructions](agent-instructions.md) for when to search, what to store, and how access control affects visible and writable trees. diff --git a/docs/mcp/me_memory_create.md b/docs/mcp/me_memory_create.md index 3ac667e3..563b8af1 100644 --- a/docs/mcp/me_memory_create.md +++ b/docs/mcp/me_memory_create.md @@ -38,7 +38,7 @@ The full memory object as created: "versionHash": "5f3e9c2a8b1d4f7e0c3a6b9d2e5f8c1a", "hasEmbedding": false, "createdAt": "2025-04-15T12:00:00Z", - "createdBy": "user_abc", + "createdBy": null, "updatedAt": null } ``` @@ -55,7 +55,7 @@ The full memory object as created: | `versionHash` | `string` | 32-char md5 hex over `tree`, `name`, `meta`, `temporal`, `content`. Pass back as `version_hash` to `me_memory_update` for optimistic concurrency control. | | `hasEmbedding` | `boolean` | Whether a vector embedding has been computed yet. | | `createdAt` | `string` | ISO 8601 creation timestamp. | -| `createdBy` | `string \| null` | The user that created the memory. | +| `createdBy` | `string \| null` | Reserved field; per-memory creators are not currently tracked, so it is currently `null`. | | `updatedAt` | `string \| null` | ISO 8601 timestamp of last update, or `null`. | ## Example diff --git a/docs/mcp/me_memory_export.md b/docs/mcp/me_memory_export.md index fa623225..23aa7b36 100644 --- a/docs/mcp/me_memory_export.md +++ b/docs/mcp/me_memory_export.md @@ -14,7 +14,7 @@ Prefer `path` to write directly to a file instead of returning content through t | `temporal` | `object \| null` | no | Temporal filter. Omit or pass `null` to skip. | | `format` | `string` | yes | Output format: `"json"`, `"yaml"`, or `"md"`. | | `limit` | `integer \| null` | no | Maximum memories to export. Omit or pass `null` for default (1000). | -| `path` | `string \| null` | no | Absolute file or directory path. For `md` format, use a directory path to write one `.md` file per memory. Omit or pass `null` to return content inline. | +| `path` | `string \| null` | no | File or directory path. Relative paths resolve from the MCP server's working directory. For `md` format, use a directory path to write one `.md` file per memory. Omit or pass `null` to return content inline. | ### temporal diff --git a/docs/mcp/me_memory_get.md b/docs/mcp/me_memory_get.md index 175792c9..e0e65560 100644 --- a/docs/mcp/me_memory_get.md +++ b/docs/mcp/me_memory_get.md @@ -31,7 +31,7 @@ JSON text. "versionHash": "5f3e9c2a8b1d4f7e0c3a6b9d2e5f8c1a", "hasEmbedding": true, "createdAt": "2025-04-15T12:00:00Z", - "createdBy": "user_abc", + "createdBy": null, "updatedAt": null } ``` @@ -48,7 +48,7 @@ JSON text. | `versionHash` | `string` | 32-char md5 hex over `tree`, `name`, `meta`, `temporal`, `content`. Pass back as `version_hash` to `me_memory_update` for optimistic concurrency control. | | `hasEmbedding` | `boolean` | Whether a vector embedding has been computed. | | `createdAt` | `string` | ISO 8601 creation timestamp. | -| `createdBy` | `string \| null` | The user that created the memory. | +| `createdBy` | `string \| null` | Reserved field; per-memory creators are not currently tracked, so it is currently `null`. | | `updatedAt` | `string \| null` | ISO 8601 timestamp of last update, or `null`. | ## Example diff --git a/docs/mcp/me_memory_import.md b/docs/mcp/me_memory_import.md index 5fb525a1..ff59b15f 100644 --- a/docs/mcp/me_memory_import.md +++ b/docs/mcp/me_memory_import.md @@ -9,11 +9,11 @@ Parses the input according to the specified format and creates all memories in o | Name | Type | Required | Description | |------|------|----------|-------------| | `space` | `string` | varies | Absent in locked mode; required nonempty string in multi-space mode. It selects the same-server space for this call. | -| `path` | `string \| null` | no | Absolute path to a file or directory. Directories are imported recursively. Format is inferred from extension (`.json`, `.yaml`, `.yml`, `.md`, `.ndjson`, `.jsonl`). Mutually exclusive with `content`. Omit or pass `null` if providing `content`. | -| `content` | `string \| null` | no | Raw content to import (JSON array, YAML array, or Markdown with frontmatter). Mutually exclusive with `path`. Omit or pass `null` if providing `path`. | -| `format` | `string \| null` | no | Content format: `"json"`, `"yaml"`, or `"md"`. Required when using `content`. Optional when using `path` (inferred from file extension). Omit or pass `null` to skip. | +| `path` | `string \| null` | no | File or directory path. Directories are imported recursively. Format is inferred from extension (`.json`, `.yaml`, `.yml`, `.md`, `.ndjson`, `.jsonl`). When both inputs are provided, `path` takes precedence. | +| `content` | `string \| null` | no | Raw content to import (JSON array, YAML array, or Markdown with frontmatter). Used when `path` is omitted. | +| `format` | `string \| null` | no | Content format: `"json"`, `"yaml"`, or `"md"`. Optional; content is detected when omitted. | -One of `path` or `content` must be provided. +At least one of `path` or `content` must be provided. ### Supported formats @@ -21,7 +21,7 @@ JSON (array or single object), NDJSON, YAML (array or single object), and Markdo Each memory object supports fields: `id`, `content` (required), `name`, `meta`, `tree`, `temporal`. Unlike `me_memory_create` (which requires an explicit `tree`), a record with no `tree` is imported into the shared root `share`. -Import submits with `onConflict: 'ignore'`, so a record whose idempotency key -- its `id`, or its `(tree, name)` slot -- already exists is skipped rather than erroring. Re-importing the same data is a no-op. +Import submits with `onConflict: 'ignore'`, so a record whose idempotency key already exists is skipped rather than erroring. A named record uses its `(tree, name)` slot in preference to its `id`. Re-importing the same data is a no-op. See [File Formats](../formats.md) for full schema documentation, examples, and format detection rules. @@ -46,13 +46,13 @@ See [File Formats](../formats.md) for full schema documentation, examples, and f | Field | Type | Description | |-------|------|-------------| | `imported` | `number` | Number of memories successfully imported on this call. | -| `skipped` | `number` | Number of memories whose explicit `id` already existed in the space. Always present (may be `0`). | +| `skipped` | `number` | Number of memories whose idempotency key already existed in the space. Always present (may be `0`). | | `failed` | `number` | Number of memories in chunks that errored before reaching the server. Always present (may be `0`). | | `ids` | `string[]` | UUIDs of the memories actually inserted on this call. | -| `skippedIds` | `string[]` | The explicit ids that were skipped because they already existed. Always present (may be empty). Inspect any of these with `me_memory_get` to see what's there. | +| `skippedIds` | `string[]` | Stored IDs of skipped memories. Always present (may be empty). Inspect any of these with `me_memory_get` to see what's there. | | `errors` | `Array<{ chunkIndex, itemCount, ids, error }>` | One entry per failed chunk. Always present (may be empty). | -The tool is idempotent: re-calling with the same arguments leaves the space in the same state, with previously-imported explicit ids appearing in `skippedIds` instead of `ids`. A record with neither an `id` nor a `name` gets a server-generated UUIDv7 and never collides; a named record is keyed on its `(tree, name)` slot (such skips aren't listed by id in `skippedIds`). +The tool is idempotent: re-calling with the same arguments leaves the space in the same state. A record with neither an `id` nor a `name` gets a server-generated UUIDv7 and never collides; a named record is keyed on its `(tree, name)` slot. ### Chunking and partial failures diff --git a/docs/mcp/me_memory_search.md b/docs/mcp/me_memory_search.md index 7aa0b270..d2ae5d6b 100644 --- a/docs/mcp/me_memory_search.md +++ b/docs/mcp/me_memory_search.md @@ -70,7 +70,7 @@ pass `format: "json"` or `format: "compact"` for compact JSON text. "versionHash": "5f3e9c2a8b1d4f7e0c3a6b9d2e5f8c1a", "hasEmbedding": true, "createdAt": "2025-04-15T12:00:00Z", - "createdBy": "user_abc", + "createdBy": null, "updatedAt": null, "score": 0.85 } diff --git a/docs/mcp/me_memory_update.md b/docs/mcp/me_memory_update.md index d2cf837a..edc55b1e 100644 --- a/docs/mcp/me_memory_update.md +++ b/docs/mcp/me_memory_update.md @@ -42,7 +42,7 @@ The full updated memory object: "versionHash": "9b7e4c5e8a1f3d2c6b0a4f7e8d1c2b3a", "hasEmbedding": true, "createdAt": "2025-04-15T12:00:00Z", - "createdBy": "user_abc", + "createdBy": null, "updatedAt": "2025-04-15T14:00:00Z" } ``` diff --git a/docs/projects.md b/docs/projects.md index 3a0c3ae7..fbc159f7 100644 --- a/docs/projects.md +++ b/docs/projects.md @@ -1,70 +1,73 @@ # Projects -A Memory Engine **project** is a convention for one repository's memories. It is -not a separate object in the server: a project is a tree path, plus the grants -that decide who can write there. +A Memory Engine **project** is a convention for memories from one repository. +It is not a separate server object and it does not require repository +configuration. A project is a tree path plus the access grants that control who +can use that path. -Nothing in the repository configures this. You point a project's captures and -imports at its tree with machine-local, per-directory policy ([`me init`](cli/me-init.md)) -and with the `--tree` / `--tree-root` flags on the import commands. The main -choice is where the project's tree should live. +Use `me init` from a repository to configure machine-local capture and harness +policy for that directory. Use [Harness Integrations](harness-integrations.md) +for the installation and policy model. -Some layouts need help from a **space admin** or a path owner. You can set up -layouts that use access you already have, but creating groups is admin-gated, -and granting access requires owner access at the target path. +## Choose A Project Tree -## Common Layouts +The main decision is whether project knowledge is shared or private. -| Goal | Project tree | Grants | -|------|--------------|--------| -| Whole team can write project memories | `/share/projects/` | The default `team` group is enough | -| Whole team can read, one group writes | `/share//` | Grant that group `write` on the group or project path | -| Only one group can read and write | `//` | Grant that group `write` on the group or project path | -| CI imports git/docs | The selected CI tree | `me ci install` can create a service account and grant it write access | +| Goal | Project tree | Typical access | +| --- | --- | --- | +| Team-shared repository knowledge | `/share/projects/` | Members with write access under `/share/projects` | +| Shared visibility, subgroup writes | `/share//` | Team reads `/share`; subgroup receives write access | +| Group-private repository knowledge | `//` | Only the group receives access | +| Personal repository knowledge | `~/projects/` | Your home-tree access | +| CI imports | A shared project tree | Service account receives write access at that tree | -Project trees are full paths. When a project tree is `/share/projects/acme-api`, -captures and imports land directly under that node: +When quick setup enables capture, it suggests `/share/projects/`. +Choose `~/projects/` at the prompt when captures should remain +private. Use `me init --verbose` to choose a different tree or configure capture +separately from MCP and CLI routing. + +Project trees are full paths. For `/share/projects/acme-api`, common child nodes +include: - `/share/projects/acme-api/agent_sessions` - `/share/projects/acme-api/git_history` - `/share/projects/acme-api/docs` -No extra project slug is appended. +No additional repository slug is appended to a tree you explicitly choose. -## Team-Writable Projects +## Team-Shared Projects -Use `/share/projects/` when everyone in the space's default `team` -group should be able to write memories for the repo. +`/share/projects/` is the usual destination for repository knowledge +the whole space should be able to read and contribute to. -Point this directory's session capture at that tree (machine-local; each -teammate runs it once for their own checkout): +Default spaces commonly provision a default group with read access to `/share` +and write access to `/share/projects`. That is a convention of the default space +setup, not a guarantee for every custom space. Check your effective access +before relying on it: ```bash -me init . \ - --capture-server https://api.memory.build \ - --capture-space abc123def456 \ - --capture-tree /share/projects/acme-api \ - --capture-harness claude +me access mine --effective +me tree /share/projects --levels 2 ``` -This works in a default space because the auto-provisioned `team` group carries: +For a repository named `acme-api`, run `me init` and accept or enter: -- `read@/share` -- `write@/share/projects` +```text +/share/projects/acme-api +``` -Invitations add new members to `team` by default, so teammates can read shared -knowledge and write under `/share/projects/...` without a per-project grant. -This is the happy path for shared repository memory. One-off imports target the -same tree with `--tree`, e.g. `me import git --tree /share/projects/acme-api`. +The same full tree can be used for one-off imports: -## Group-Writable Projects +```bash +me import git --tree /share/projects/acme-api +me import docs . --tree /share/projects/acme-api +``` -Use a group path when the whole team may read the project, but only a subgroup -should write it. For example, a payments team might keep projects under -`/share/payments/...`: +## Subgroup-Writable Projects -Ask a space admin to create the group and add members. Then have someone with -owner access at the target path grant the group write access: +Use a subgroup path when everyone may read a project but only selected members +should write it. A space admin creates and manages the group; a path owner grants +the group's access: ```bash me group create payments @@ -73,28 +76,15 @@ me group add payments bob@example.com me access grant payments /share/payments w ``` -Then point the repo's capture (and imports) at a tree under that group path, -e.g. `--capture-tree /share/payments/acme-billing`. - -The grant at `/share/payments` covers every project below it. If you want one -repo at a time instead, grant the project node directly: - -```bash -me access grant payments /share/payments/acme-billing w -``` - -In a default space, `team` still has `read@/share`, so other teammates can read -the project memories but cannot write them unless they are also in `payments`. +Then configure the repository to use a child tree such as +`/share/payments/acme-billing`. A grant at `/share/payments` covers every +project below it. Grant the individual project path when access should be scoped +to one repository only. -## Group-Private Projects +## Private Group And Personal Projects -If the project should not be broadly visible, do not put it under `/share`. -`/share` is the convention for space-wide shared knowledge, and in a default -space the `team` group can read it. - -Instead, create a top-level tree for the group and grant only that group access. -A space admin can create the group and add members; granting write access at the -target path requires owner access there. +Do not place a private group project under `/share` when the wider space should +not read it. Instead, create a separate tree and grant only that group: ```bash me group create group-x @@ -103,34 +93,32 @@ me group add group-x bob@example.com me access grant group-x /group-x w ``` -Then point the repo's capture at a tree under that node, e.g. -`--capture-tree /group-x/secret-project`. A single write grant is enough when the -same group should both read and write: `write` includes `read`. +Configure a project below that tree, for example +`/group-x/secret-project`. -Put personal project notes under `~/projects/` when they are only for -you — that private layout is the default when you don't pin a `--capture-tree`. +For personal notes and private captures, use `~/projects/`. A home tree +is private by default, subject to any access you explicitly grant to others. ## CI Imports -For git-history and docs imports, run: +Use `me ci install` from a GitHub repository to create an import workflow for +git history and Markdown documentation: ```bash me ci install ``` -CI runs as a service account. Service accounts do not join `team` and do not get -a home tree. When it provisions credentials, `me ci install` creates a write -grant at the selected CI tree; an existing key is verified by the first CI run. - -For teams that use `/share/projects/` everywhere, a space admin may -also grant one shared service account `write@/share/projects`. For per-project -or per-group service accounts, grant only the specific project tree. +CI runs with a service-account key. Service accounts do not have a home tree, so +their destination must be a shared or otherwise explicitly granted project tree. +When `me ci install` creates credentials, it grants write access at the selected +tree. See [`me ci`](cli/me-ci.md) for workflow and credential details. -## Changing a Project Later +## Change A Project Later -To move a repository's future captures, rerun [`me init`](cli/me-init.md) for -that directory with a different `--capture-tree`; for imports, pass a different -`--tree`. Existing memories stay at their old paths until you move or copy them. +Rerun `me init` in the repository to update its quick configuration, or use +`me init --verbose` to choose a different capture tree while preserving precise +control of the other surfaces. Existing memories stay at their original paths; +changing future capture or import destinations does not move them. Useful checks: @@ -138,8 +126,7 @@ Useful checks: me doctor me whoami me access mine --effective -me tree /share/projects --levels 2 ``` -See also [`me init`](cli/me-init.md), [`me doctor`](cli/me-doctor.md), -[`me group`](cli/me-group.md), and [`me access`](cli/me-access.md). +See also [Harness Integrations](harness-integrations.md), [`me init`](cli/me-init.md), +[`me access`](cli/me-access.md), and [`me group`](cli/me-group.md). diff --git a/packages/docs-site/lib/nav.ts b/packages/docs-site/lib/nav.ts index dd39b4f9..087cdee7 100644 --- a/packages/docs-site/lib/nav.ts +++ b/packages/docs-site/lib/nav.ts @@ -19,6 +19,7 @@ export const NAV: NavSection[] = [ { label: "Getting Started", slug: "getting-started" }, { label: "Joining a Space", slug: "joining-a-space" }, { label: "Core Concepts", slug: "concepts" }, + { label: "Harness Integrations", slug: "harness-integrations" }, { label: "Projects", slug: "projects" }, { label: "File Formats", slug: "formats" }, { label: "Access Control", slug: "access-control" },