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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 47 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@
4. [Live Demo](#live-demo)
5. [Requirements](#requirements)
6. [Quickstart](#quickstart)
7. [Documentation](#documentation)
8. [Repository Layout](#repository-layout)
9. [Status](#status)
10. [Stability](#stability)
11. [Contributing](#contributing)
7. [Run The Dashboard](#run-the-dashboard)
8. [Documentation](#documentation)
9. [Repository Layout](#repository-layout)
10. [Status](#status)
11. [Stability](#stability)
12. [Contributing](#contributing)

## Why It Exists

Expand Down Expand Up @@ -130,6 +131,46 @@ initial skills and harness files.

After that, normal work runs through the `l-01-session-job-lifecycle` skill. The agent resolves the active context with `c-08-ar-coordination-context-resolver`, checks memory quality with `c-02-memory-quality-control`, reads relevant onboarding beside code, and updates onboarding after approved changes.

## Run The Dashboard

The mission-control dashboard ships inside the MCP package. Install the CLI
once with uv — latest stable, no version pin — then start the cockpit from
anywhere in your workspace:

```text
uv tool install agents-remember-mcp
agents-remember dashboard
```

`--config` is optional: the CLI walks up from the current directory and uses
the nearest `.claude/mcp/agents-remember-settings.json`, or the `--config`
recorded in an `.mcp.json` `agents-remember` entry — the same settings file
the MCP server boots from.

For a dashboard that survives closing the terminal, use daemon mode:

```text
agents-remember dashboard --daemon # detach; state + log under <coordinationRoot>/logs/dashboard/
agents-remember dashboard --status # exit 0 when running, 1 when not
agents-remember dashboard --stop
```

Or let the MCP server supervise it: set `"dashboard": {"autoStart": true}` in
the MCP settings JSON and every server boot ensures the daemon — adopting a
healthy one, starting a missing one, and restarting on version mismatch so an
upgrade is picked up by the next session
([Settings Reference](docs/reference/settings-json.md)).

Pinning a version is the debugging/repro path, not the default: `uv tool
install 'agents-remember-mcp==3.0.0rc2'`, or one-shot without installing,
`uvx --from 'agents-remember-mcp==3.0.0rc2' agents-remember dashboard`.

> **Pre-release note (until 3.0.0 final):** the dashboard currently ships in
> `3.0.0rcN` pre-releases, which default version resolution skips. Install with
> `uv tool install --prerelease allow agents-remember-mcp`, and register the
> MCP server with an explicit `agents-remember-mcp==3.0.0rcN` pin instead of
> `@latest`.

## Documentation

- [Features](docs/features.md) - the concentrated tour of what Agents Remember gives users.
Expand Down Expand Up @@ -198,7 +239,7 @@ ar-coordination/

## Status

Agents Remember is at `3.0.0rc1` and actively developed. The core path — by-path onboarding, drift checks, and approval-gated updates — is in real use and stable enough to rely on. The public contracts listed under [Stability](#stability) are held stable across minor releases and change only on a major bump; the internals beneath them and the optional semantic/relationship providers may still evolve, so pin a version and read the notes for your target version in [GitHub Releases](https://github.com/Foxfire1st/agents-remember/releases) — the repository's canonical changelog — before upgrading. The Claude Code path is the most exercised; other harnesses are supported but less battle-tested.
Agents Remember is at `3.0.0rc2` and actively developed. The core path — by-path onboarding, drift checks, and approval-gated updates — is in real use and stable enough to rely on. The public contracts listed under [Stability](#stability) are held stable across minor releases and change only on a major bump; the internals beneath them and the optional semantic/relationship providers may still evolve, so pin a version and read the notes for your target version in [GitHub Releases](https://github.com/Foxfire1st/agents-remember/releases) — the repository's canonical changelog — before upgrading. The Claude Code path is the most exercised; other harnesses are supported but less battle-tested.

The 3.0 arc: the working session itself is now observable and steerable — a system-managed session job lifecycle with durable approval gates and an event/projection layer, served as the mission-control browser cockpit directly from the MCP package (`agents-remember dashboard`; [#2](https://github.com/Foxfire1st/agents-remember/issues/2), [#43](https://github.com/Foxfire1st/agents-remember/issues/43)). The `rc` tag means the cockpit surface is still settling toward the final 3.0.0 contract; the architecture beneath it is the one described above.

Expand Down
16 changes: 15 additions & 1 deletion docs/reference/settings-json.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,11 @@ watch settings internally.
"toolSeconds": 30,
"providerSetupSeconds": 1800
},
"benchmarksEnabled": false
"benchmarksEnabled": false,
"dashboard": {
"autoStart": false,
"port": 8765
}
}
```

Expand Down Expand Up @@ -188,3 +192,13 @@ dependency install (default 1800). Docker control operations such as
start, stop, and status use a fixed internal cap and are not configurable.
Indexing and database seed or clone are never capped because they scale with
repository size. A value of `0` means unlimited for any cap.

`dashboard` (optional) supervises the mission-control dashboard from the MCP
server. With `dashboard.autoStart` set to `true` (default `false`), every
server boot ensures a detached dashboard daemon on `dashboard.port` (default
`8765`): a healthy same-version daemon is adopted, a missing one is spawned,
and a version or port mismatch restarts it, so an upgrade is picked up by the
next session's boot. Daemon state and logs live under
`<coordinationRoot>/logs/dashboard/`; `agents-remember dashboard --status` /
`--stop` manage the same daemon from the CLI. Unknown `dashboard` keys are
rejected.
6 changes: 5 additions & 1 deletion examples/mcp/settings.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@
"toolSeconds": 30,
"providerSetupSeconds": 1800
},
"benchmarksEnabled": false
"benchmarksEnabled": false,
"dashboard": {
"autoStart": false,
"port": 8765
}
}
25 changes: 25 additions & 0 deletions mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,31 @@ python -m pip install agents-remember-mcp
agents-remember-mcp --config /absolute/path/to/agents-remember-settings.json
```

The package also ships the umbrella CLI `agents-remember`, which carries the
mission-control `dashboard` subcommand. Install it as a uv tool — latest
stable, no version pin; pinning (`agents-remember-mcp==X.Y.Z`, also usable
with `uvx --from`) is the debugging/repro path:

```text
uv tool install agents-remember-mcp
agents-remember dashboard
```

`dashboard` finds its `--config` on its own: it walks up from the current
directory to the nearest `.claude/mcp/agents-remember-settings.json`, or the
`--config` recorded in an `.mcp.json` `agents-remember` entry. `--daemon`
detaches it so it survives the terminal that started it (`--status` /
`--stop` manage it; state and log live under
`<coordinationRoot>/logs/dashboard/`), and the
`"dashboard": {"autoStart": true}` settings key makes every MCP server boot
ensure the daemon — adopting a healthy one, starting a missing one, and
restarting on version mismatch.

> **Pre-release note (until 3.0.0 final):** `3.0.0rcN` pre-releases are
> skipped by default version resolution — install the dashboard-capable CLI
> with `uv tool install --prerelease allow agents-remember-mcp`, and pin the
> server registration (`agents-remember-mcp==3.0.0rcN`) instead of `@latest`.

The config path must be **absolute**, the settings file must live **outside the
`ar-coordination/` runtime folder**, and it should live **under your harness's
registration folder in an `mcp/` subdirectory** (see
Expand Down
2 changes: 1 addition & 1 deletion mcp/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "agents-remember-mcp"
version = "3.0.0rc1"
version = "3.0.0rc2"
description = "Model Context Protocol server for Agents Remember."
readme = "README.md"
requires-python = ">=3.11"
Expand Down
82 changes: 76 additions & 6 deletions mcp/src/agents_remember/cli/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
import uvicorn

import agents_remember
from agents_remember.cli.discovery import ConfigDiscoveryError, discover_config
from agents_remember.mcp.config import ConfigError, load_config
from agents_remember.serving import daemon as serving_daemon
from agents_remember.serving.app import create_app
from agents_remember.serving.sim import SimError, build_sim, parse_sim_speed

Expand All @@ -34,12 +36,21 @@ def _dev_app():

def add_arguments(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"--config", required=True, help="Absolute path to trusted MCP settings JSON."
"--config",
default=None,
help="Path to trusted MCP settings JSON. Omit to discover it from the working "
"directory: the nearest .claude/mcp/agents-remember-settings.json, or the "
"--config recorded in an .mcp.json agents-remember entry.",
)
parser.add_argument(
"--host", default="127.0.0.1", help="Bind host (localhost-only by default; do not expose)."
)
parser.add_argument("--port", type=int, default=8765, help="Bind port (default 8765).")
parser.add_argument(
"--port",
type=int,
default=None,
help="Bind port (default: the dashboard.port settings key, else 8765).",
)
parser.add_argument(
"--interval", type=float, default=1.0, help="Projection refresh interval, seconds."
)
Expand All @@ -59,29 +70,62 @@ def add_arguments(parser: argparse.ArgumentParser) -> None:
default="1",
help="Sim replay speed multiplier (e.g. 1, 10) or 'paused' (default 1).",
)
control = parser.add_mutually_exclusive_group()
control.add_argument(
"--daemon",
action="store_true",
help="Detach: ensure a background dashboard daemon (state and log under "
"<coordinationRoot>/logs/dashboard/) and return. Adopts a healthy matching daemon; "
"restarts one whose version, host, or port differs.",
)
control.add_argument(
"--status",
action="store_true",
help="Report the dashboard daemon's state and exit (exit 0 running, 1 not).",
)
control.add_argument(
"--stop",
action="store_true",
help="Stop the dashboard daemon (TERM, bounded wait, KILL fallback) and exit.",
)
parser.add_argument(
"--no-access-log",
action="store_true",
help="Serve without per-request access logs (the daemon child uses this to keep "
"its log file bounded).",
)


def run(args: argparse.Namespace) -> int:
try:
config = load_config(args.config)
config_path = args.config or discover_config()
except ConfigDiscoveryError as error:
print(f"error: {error}")
return 1
try:
config = load_config(config_path)
except ConfigError as error:
print(f"error: {error}")
return 1
port = args.port if args.port is not None else config.dashboard.port
if args.daemon or args.status or args.stop:
return _run_daemon_command(args, config, port)
if args.reload:
if args.sim:
print("error: --reload is not supported with --sim")
return 1
# Pass an import-string factory (not the built app object) so uvicorn's reloader can
# re-import on change; watch only the package source so node_modules/.git don't churn it.
os.environ[_DEV_CONFIG_ENV] = str(Path(args.config).resolve())
os.environ[_DEV_CONFIG_ENV] = str(Path(config_path).resolve())
os.environ[_DEV_INTERVAL_ENV] = str(args.interval)
uvicorn.run(
"agents_remember.cli.dashboard:_dev_app",
factory=True,
reload=True,
reload_dirs=[str(Path(agents_remember.__file__).parent)],
host=args.host,
port=args.port,
port=port,
access_log=not args.no_access_log,
)
return 0
if args.sim:
Expand All @@ -97,5 +141,31 @@ def run(args: argparse.Namespace) -> int:
)
else:
app = create_app(config, interval=args.interval)
uvicorn.run(app, host=args.host, port=args.port)
uvicorn.run(app, host=args.host, port=port, access_log=not args.no_access_log)
return 0


def _run_daemon_command(
args: argparse.Namespace, config: serving_daemon.McpRuntimeConfig, port: int
) -> int:
"""Dispatch --status/--stop/--daemon against the recorded daemon state."""
if args.sim or args.reload:
print("error: --daemon/--status/--stop are not supported with --sim or --reload")
return 1
directory = serving_daemon.daemon_dir(config)
if args.status:
state, alive = serving_daemon.probe(directory)
if alive and state is not None:
print(
f"dashboard daemon running: pid {state.pid}, http://{state.host}:{state.port}/ "
f"(v{state.version}); log: {state.log_path}"
)
return 0
print("dashboard daemon not running")
return 1
if args.stop:
print(f"dashboard daemon: {serving_daemon.stop(directory)}")
return 0
result = serving_daemon.ensure(config, host=args.host, port=port, interval=args.interval)
print(f"dashboard daemon {result.action}: {result.detail}")
return 0 if result.action in ("adopted", "started", "restarted") else 1
99 changes: 99 additions & 0 deletions mcp/src/agents_remember/cli/discovery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""Trusted-settings discovery for the umbrella CLI (L1 of 260703).

``agents-remember dashboard`` (and future subcommands) should run without
``--config`` from anywhere under the workspace. Discovery walks the current
directory upward; at each level it probes, in order:

1. the settings convention ``.claude/mcp/agents-remember-settings.json``, and
2. an ``.mcp.json`` whose ``mcpServers``/``agents-remember`` entry records a
``--config`` argument — the harness's own registration, reused verbatim so
the CLI and the harness always boot from the same settings file.

The nearest directory wins and the first USABLE file ends the walk — usable
meaning it parses as JSON and its ``coordinationRoot`` is an existing absolute
directory. That semantic probe matters because the repository itself ships a
placeholder template at the convention path (``<PATH/TO/YOUR/...>``), which
must never shadow the real settings above it. A malformed or foreign
``.mcp.json`` is skipped silently: discovery must never crash on someone
else's file. An explicit ``--config`` always bypasses this.
"""

from __future__ import annotations

import json
from pathlib import Path

SETTINGS_CONVENTION = Path(".claude") / "mcp" / "agents-remember-settings.json"
MCP_REGISTRATION = ".mcp.json"
_SERVER_NAME = "agents-remember"


class ConfigDiscoveryError(Exception):
"""No trusted settings file was discoverable from the starting directory."""


def discover_config(start: Path | None = None) -> Path:
"""The nearest trusted settings JSON at or above ``start`` (default: cwd)."""
origin = (start or Path.cwd()).resolve()
for directory in (origin, *origin.parents):
convention = directory / SETTINGS_CONVENTION
if _is_usable_settings(convention):
return convention
registered = _config_from_mcp_registration(directory / MCP_REGISTRATION)
if registered is not None and _is_usable_settings(registered):
return registered
raise ConfigDiscoveryError(
"no trusted settings found: pass --config, or run from a directory at or below "
f"one containing {SETTINGS_CONVENTION.as_posix()} or an {MCP_REGISTRATION} whose "
f"{_SERVER_NAME!r} entry records a --config path (walked up from {origin})"
)


def _config_from_mcp_registration(mcp_json: Path) -> Path | None:
"""The ``--config`` path recorded for the agents-remember server, if any."""
if not mcp_json.is_file():
return None
try:
data = json.loads(mcp_json.read_text(encoding="utf-8"))
except (OSError, ValueError):
return None
if not isinstance(data, dict):
return None
servers = data.get("mcpServers")
if not isinstance(servers, dict):
return None
entry = servers.get(_SERVER_NAME)
if not isinstance(entry, dict):
return None
arguments = entry.get("args")
if not isinstance(arguments, list):
return None
for index, argument in enumerate(arguments):
if argument == "--config" and index + 1 < len(arguments):
candidate = str(arguments[index + 1])
if candidate:
return Path(candidate)
return None


def _is_usable_settings(path: Path) -> bool:
"""True for a real trusted settings file — not the shipped placeholder template.

The probe is semantic, not syntactic: the file must parse as a JSON object whose
``coordinationRoot`` is an existing absolute directory. The repository's tracked
template carries ``<PATH/TO/YOUR/...>`` placeholders and fails this, so a source
checkout never shadows the workspace's real settings.
"""
if not path.is_file():
return False
try:
data = json.loads(path.read_text(encoding="utf-8"))
except (OSError, ValueError):
return False
if not isinstance(data, dict):
return False
root = data.get("coordinationRoot")
if not isinstance(root, str) or not root:
return False
candidate = Path(root)
return candidate.is_absolute() and candidate.is_dir()
2 changes: 1 addition & 1 deletion mcp/src/agents_remember/mcp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
# Single source of truth: the installed package metadata (mcp/pyproject.toml).
SERVER_VERSION = version("agents-remember-mcp")
except PackageNotFoundError: # running from a source checkout without an install
SERVER_VERSION = "3.0.0rc1"
SERVER_VERSION = "3.0.0rc2"
Loading
Loading