Skip to content
Draft
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
54 changes: 37 additions & 17 deletions RELEASE-INVENTORY.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"excluded_self": "RELEASE-INVENTORY.json",
"file_count": 400,
"file_count": 404,
"files": [
{
"bytes": 1914,
Expand Down Expand Up @@ -808,9 +808,9 @@
"sha256": "e59dbdbab95ab3749451d539f8bca3462af6bb79ff6924fec031bc15acefbb77"
},
{
"bytes": 6629,
"bytes": 11367,
"path": "docs/BLUEPRINT.md",
"sha256": "264355fa8af08b6c6f529a35e497da9da746540d11457d2fdccf95fffe27aa98"
"sha256": "7315efe823b1dc7a3efaffd494c630a7b94b620c15faf87d7d56d07649108e1f"
},
{
"bytes": 14235,
Expand Down Expand Up @@ -1008,9 +1008,9 @@
"sha256": "4a9edbc2d7704f8d88eb4d797c7d50f411533152cfb44da6b6eae45acc43fe9e"
},
{
"bytes": 9714,
"bytes": 11587,
"path": "installer/cli.py",
"sha256": "cebd7eacfcb53efe76b666bef26beda8a789afb80dc1f2e6f3b53d170056743f"
"sha256": "eb784acd24ac37af252c6212313edc2496eae21a97d4ac1bbb5a85e7f2fc5431"
},
{
"bytes": 10302,
Expand Down Expand Up @@ -1048,9 +1048,9 @@
"sha256": "b8573095ab48abf8bede9582452e598afd7118078f6a571d5ee86832f58a7826"
},
{
"bytes": 19270,
"bytes": 20895,
"path": "installer/health.py",
"sha256": "88b70d35ff8e029a90c0ad70a80871a8aa51aff2098e56acdea7213ca9670c2e"
"sha256": "f0433a51c99ccf7ceb848ce063945998ccc1014dcafe59373cc4f1a493a9c040"
},
{
"bytes": 14377,
Expand Down Expand Up @@ -1088,15 +1088,20 @@
"sha256": "0d34acd4457a0de2457a2e0b1ce9d29f31e1a4d9dc2db69e358ccae79f9da79c"
},
{
"bytes": 20705,
"bytes": 22256,
"path": "installer/onboarding.py",
"sha256": "02860640403068e6184562161f8e452cbce31746371697b461be16a371edd281"
"sha256": "b8bae6cd2e3f971d40c95410d54866fe75d08aafa209fdc75bfd643417271468"
},
{
"bytes": 28468,
"path": "installer/release_guard.py",
"sha256": "ee32045bf09da38d41f85eb175e9073b04da6f1a388f983c8f3ae81b8bd16684"
},
{
"bytes": 32347,
"path": "installer/remote_memory.py",
"sha256": "ffa6c816ef476863523c96223a83d246b5bda0e92ec9e9cb311fecdd80df9832"
},
{
"bytes": 352,
"path": "installer/requirements.in",
Expand Down Expand Up @@ -1167,6 +1172,11 @@
"path": "installer/test_release_guard.py",
"sha256": "846c401865704f0cd8a3b62971a957ed12067059db33ea05b09103783d074dc2"
},
{
"bytes": 45233,
"path": "installer/test_remote_memory.py",
"sha256": "451874a5d6147a64bbee7dd4630a89b0e3654107b648e1f62330811279ecec93"
},
{
"bytes": 1547,
"path": "installer/test_service_limits.py",
Expand All @@ -1187,6 +1197,11 @@
"path": "installer/web.py",
"sha256": "b5605179c9629848b535aff4a22e840bff7d462443ba28435b0b469dfc64a925"
},
{
"bytes": 3316,
"path": "memory/bin/borg_client_config.py",
"sha256": "b3fb3319aa32d870475b3cd3716f3749babebe7aad887f7330e792e9b0103826"
},
{
"bytes": 8775,
"path": "memory/bin/borg_config.py",
Expand Down Expand Up @@ -1263,14 +1278,14 @@
"sha256": "26d85a5bcec6c6eeb657cf7402e0a899f548f4a81627a3acc54b7cd018f8f51c"
},
{
"bytes": 45554,
"bytes": 48738,
"path": "memory/bin/mem0-fleet-configure",
"sha256": "6c837bfb9a8288d3ab242971313a2a6b3b629d72db52b4c010608bf72fd85b65"
"sha256": "73b237a5126db3e5ffee195806714bd7eb9467b3db479377407ddb7e1d87f1d8"
},
{
"bytes": 123922,
"bytes": 124225,
"path": "memory/bin/mem0-fleet-hook",
"sha256": "dd950dd7dfeb9fc5d9b206a8655b41017d8e721031dc7610a3750c12d1433b59"
"sha256": "b005e1022ca34dedaea5ac58c4974e7a7f9d58fb5a1bb22971ed7cb85de3bb3b"
},
{
"bytes": 22952,
Expand Down Expand Up @@ -1512,6 +1527,11 @@
"path": "memory/tests/borg_test_support.py",
"sha256": "ff928f3e63f28f3fe668bd499172ad20cc8cea71a884a0ae76cf87c1f083ecec"
},
{
"bytes": 6792,
"path": "memory/tests/test_borg_client_config.py",
"sha256": "57d0d87bc6adf7b4f90cb19a84d7d393c95f2a363bc811f0a3cf0bfbd0714474"
},
{
"bytes": 5921,
"path": "memory/tests/test_borg_config.py",
Expand All @@ -1533,9 +1553,9 @@
"sha256": "2c299e59ec5cef6b77475fb7b345a5288f6f9670af8400cc3b32c64a3288a643"
},
{
"bytes": 6110,
"bytes": 6898,
"path": "memory/tests/test_mem0_fleet_hook.py",
"sha256": "b82f9fa200c6de9ad9e973d54046c7d6d21c357f721877ee9c0fe631f6e0e7f0"
"sha256": "a33fbd0e23931651f71e2375265eeae23bfc009db83d1f5a4c9c6b5eacee31dc"
},
{
"bytes": 6709,
Expand Down Expand Up @@ -2003,7 +2023,7 @@
"sha256": "cd4bdb4529012e0cfcd38e059215f9ea433b8ee1fa636276b36c6515e7949e28"
}
],
"inventory_sha256": "927bc4b7fc93fcc354eea91f24337b98d289d616292f38fab72c87a95eb85cac",
"inventory_sha256": "dcf01dcb0b8747ad96d829d624ce98c9ed9a9c7adb0e5a0f42f0f0cf89237d5e",
"schema": "borg-public-inventory/v1",
"total_bytes": 88047043
"total_bytes": 88148793
}
19 changes: 19 additions & 0 deletions docs/BLUEPRINT.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,25 @@ The shell installer needs Python 3 on PATH to validate blueprint input before bo

The complete source and locked runtime package remain installed for either profile. Selection controls configured services and owner setup, not package trimming. Beads without Inbox prepares the native coordination configuration that its custody wrapper needs, without starting an Inbox service. Tools-only Codex gets the isolated BORG MCP connection with zero memory lifecycle hooks. Its watchdog probes authenticated connector liveness; absent memory is not treated as a working memory service.

An owner may explicitly opt a tools node into the existing remote Mem0 lifecycle service after the BORG primary profile and pinned Codex runtime are installed. This is a sidecar binding, not a blueprint change or a local memory service. The owner or fleet controller must first verify the native machine identity and the existing private HTTPS `/mcp` Hub route. Stage a fresh token inside this BORG home with a principal bound to its instance UUID:

```sh
borg memory-client stage --home /absolute/private/borg-home \
--machine exact-native-machine-id --hub-machine exact-hub-machine-id \
--endpoint https://verified-private-hub.example/mcp \
--principal borg-life-exact-native-machine-id-BORG-INSTANCE-UUID \
--read-scope personal:owner --read-scope team:project --read-scope ops \
--write-scope personal:owner
borg memory-client check --home /absolute/private/borg-home
borg memory-client enable --home /absolute/private/borg-home
```

Use the actual `personal:<BORG owner>` scope and the exact `instance_id` from this home's private installation configuration. Stage prints only the token SHA-256 digest and binding metadata. Grant that digest to the named principal on the existing Hub with the exact sorted read scopes and personal write scope, using the Hub's native grant authority; no token value or provider profile is copied. `enable` first checks `memory_whoami` against the binding, then uses the bundled configurator's native `config/read`, `hooks/list` and versioned `config/batchWrite` to install and trust the four lifecycle hooks in this home's isolated primary Codex profile. It preserves unrelated hooks and their trust; a native policy or permission refusal stays a refusal. Run `check` again after native approval if needed. The generated hooks bind `BORG_HOME` explicitly so a direct isolated Codex launch uses the same owned route. The default tools profile remains hook-free until this explicit step.

`borg doctor` reports `remote_memory_client` only for a staged sidecar. On macOS, `memory-client enable` also registers one instance-owned user LaunchAgent for bounded periodic replay of retained capture requests. It uses the reviewed driver, owned Python in isolated `-I -B` mode, and private token/endpoint **file paths**, with a five-minute interval and caps of four requests and 30 seconds per pass. It preserves the driver's `MEM0_CAPTURE_SKIP` kill switch. It does not put credential values in the job, modify the global miner roster, or restart other services. An absent job reports `NEEDS_REPLAY`/`replay_scheduler_state=MISSING`; an exact loaded job reports `VERIFIED`/`VERIFIED` only when its native command, environment, `/dev/null` output paths and process priority match the owned definition. A foreign or stale job refuses without replacement, and a timed-out native registration remains `NATIVE_UNCERTAIN` until manually inspected. Unsupported platforms report `REPLAY_UNSUPPORTED`/`UNSUPPORTED`. `VERIFIED` means the scoped route authenticated, all four native hooks were trusted, and this exact scheduler is loaded; it does **not** mean a conversation was captured or recalled. `lifecycle_e2e` remains `NOT_VERIFIED` until a separate real conversation canary proves capture, recall and replay on the Hub. A missing grant, changed binding, unavailable route, or native hook refusal cannot be reported as ready. The opt-in does not turn on the local Mem0, graph or model services and does not change Inbox hooks.

For an existing tools installation whose `bin/borg` predates `memory-client`, stage the complete reviewed source release under the **same** owner's `BORG_HOME/tools/releases/<40-character-reviewed-commit>`. Preserve its original release inventory and allowlist, owner-controlled regular files and directory modes; do not put a symlink or mutable checkout at that path. Run `python3 -B BORG_HOME/tools/releases/<reviewed-commit>/borg.py memory-client stage|check|enable --home BORG_HOME` with the same stage arguments above. The controller must verify the exact release inventory and ownership **before** executing that source; the client checks the inventory again. The bundled configurator pins its sibling hook driver in the trusted native command, and the client uses the release's sibling curl for route checks. This remote client resolves only its owned BORG home and state path; it needs no local model identity or local extraction service. The original installed driver and curl remain byte-matched to their existing app manifest and are not replaced. This source-only recovery leaves the installed app, its source manifest, runtime, services, account and Inbox state untouched. It is not an in-place app upgrade or a substitute for native conversation acceptance.

Grok and Claude binaries are not supplied by checking their boxes. Their provider entries remain disabled until the owner configures and qualifies their own runtime. Onboarding documents dedicated profiles and native sign-in, plus the bundled launch-bus/provider limitations. The installer does not enable automatic provider authentication. Prepared configuration does not prove a running service, provider account or usable quota.

Run `borg onboard` for selected setup instructions and `borg doctor` for observed readiness. `local_services_ready` covers selected native installation services. `ready` remains false when a selected external/research/operational capability has no automatic acceptance probe; `selected_setup` names those unverified choices. Follow the documented owner checks. Neither a checkbox nor a source directory proves those capabilities operational.
Expand Down
25 changes: 25 additions & 0 deletions installer/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ def parser() -> argparse.ArgumentParser:
fleet.add_argument("--instance-id")
fleet.add_argument("--label")
fleet.add_argument("--role", action="append", default=[])
memory_client = commands.add_parser("memory-client", help="stage and verify this tools node's scoped remote memory lifecycle client")
memory_client.add_argument("operation", choices=["stage", "check", "enable"])
memory_client.add_argument("--home", type=Path, default=Path(os.environ.get("BORG_HOME", Path.home() / ".borg")))
memory_client.add_argument("--machine")
memory_client.add_argument("--hub-machine")
memory_client.add_argument("--endpoint")
memory_client.add_argument("--principal")
memory_client.add_argument("--read-scope", action="append", default=[])
memory_client.add_argument("--write-scope")
return cli


Expand Down Expand Up @@ -124,6 +133,22 @@ def main(argv: list[str] | None = None) -> int:
elif args.command == "fleet":
from installer.fleet import manage
print(json.dumps(manage(doc, args), indent=2))
elif args.command == "memory-client":
from installer import remote_memory
if args.operation == "stage":
if not all((args.machine, args.hub_machine, args.endpoint, args.principal,
args.read_scope, args.write_scope)):
raise ValueError("memory-client stage requires machine, hub-machine, endpoint, principal, read-scope and write-scope")
result = remote_memory.stage(doc, machine=args.machine, hub_machine=args.hub_machine,
endpoint=args.endpoint, principal=args.principal, read_scopes=args.read_scope,
write_scope=args.write_scope)
else:
if any((args.machine, args.hub_machine, args.endpoint, args.principal,
args.read_scope, args.write_scope)):
raise ValueError("memory-client check/enable use the existing instance-bound stage; no route arguments")
result = remote_memory.check(doc) if args.operation == "check" else remote_memory.enable(doc)
print(json.dumps(result, sort_keys=True))
return 0 if args.operation != "enable" or result["state"] == "VERIFIED" else 1
elif args.command == "start":
from installer import services
result = services.start(doc, args.components or None)
Expand Down
36 changes: 30 additions & 6 deletions installer/health.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,23 @@ def status(doc: dict) -> dict:
"connector": "authenticated", "inbox": "authenticated", "models": "digest_verified",
"capture_hooks": "registered_and_trusted", "brain": "cycle_verified", "watchdog": "running",
"graph_llm": "identity_verified"}
remote_opt_in = False
if "conductor" in enabled and not blueprint.full(doc):
components["codex_client"] = tools_client_health(doc)
expected["codex_client"] = "configured_without_capture"
from installer import remote_memory
binding = root / "mem0/data/remote-client.json"
remote_opt_in = binding.exists() or binding.is_symlink()
remote = None
if remote_opt_in:
try:
remote = remote_memory.check(doc)
components["remote_memory_client"] = {
key: remote[key] for key in ("state", "route_authenticated", "native_trust_verified", "lifecycle_e2e")}
except (OSError, ValueError, RuntimeError, KeyError, TypeError):
components["remote_memory_client"] = {"state": "INVALID_BINDING",
"route_authenticated": False, "native_trust_verified": False,
"lifecycle_e2e": "NOT_VERIFIED"}
components["codex_client"] = tools_client_health(doc, remote=remote)
expected["codex_client"] = "configured_with_remote_capture" if remote_opt_in else "configured_without_capture"
if doc.get("blueprint") and blueprint.selected(doc, "beads"):
try:
probe = subprocess.run([str(root / "bin/bd"), "list", "--limit", "1", "--json"], env=env,
Expand All @@ -291,14 +305,17 @@ def status(doc: dict) -> dict:
if item["id"] not in {"codex", "inbox", "beads"}]
result["selected_setup"] = owner_setup
result["ready"] = (result["local_services_ready"] and not owner_setup
and (not remote_opt_in or components["remote_memory_client"]["state"] == "VERIFIED")
and ("conductor" not in enabled or components["provider_login"]["state"] == "authenticated_and_pinned"))
result["state"] = ("ready" if result["ready"] else "selected_setup_required" if result["local_services_ready"] and owner_setup
result["state"] = ("ready" if result["ready"] else "remote_memory_setup_required" if result["local_services_ready"]
and remote_opt_in and components["remote_memory_client"]["state"] != "VERIFIED" else
"selected_setup_required" if result["local_services_ready"] and owner_setup
else "provider_sign_in_required" if result["local_services_ready"] else "setup_incomplete")
return result


def tools_client_health(doc: dict) -> dict:
"""Check live isolated Codex configuration; tools nodes must have no memory hooks."""
def tools_client_health(doc: dict, *, remote: dict | None = None) -> dict:
"""Check the live isolated client, including only explicitly staged remote capture."""
root = Path(doc["home"])
try:
payload = {"method": "config/read", "params": {"includeLayers": True}, "timeoutMs": 8000}
Expand All @@ -315,6 +332,13 @@ def tools_client_health(doc: dict) -> dict:
stale = any(" hook --home " in hook.get("command", "")
for groups in client.get("hooks", {}).values() if isinstance(groups, list)
for group in groups for hook in group.get("hooks", []))
return {"state": "configured_without_capture" if matched and not stale else "incomplete"}
fleet = any("mem0-fleet-hook" in hook.get("command", "")
for groups in client.get("hooks", {}).values() if isinstance(groups, list)
for group in groups for hook in group.get("hooks", []))
if not matched or stale:
return {"state": "incomplete"}
if remote is not None:
return {"state": "configured_with_remote_capture" if remote.get("native_trust_verified") is True else "incomplete"}
return {"state": "incomplete" if fleet else "configured_without_capture"}
except (OSError, ValueError, KeyError, TypeError):
return {"state": "unavailable"}
17 changes: 17 additions & 0 deletions installer/onboarding.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,23 @@ def command(*args: str, purpose: str, requires: list[str] | None = None) -> dict
command("doctor", purpose="Verify native hook registration and trust"), install_command],
["The receipt describes an earlier configuration action, not today's hook or MCP readiness.",
"Installer client setup targets only conductors/primary/profile, never a shared global profile."]))
if doc.get("blueprint") and not blueprint.full(doc) and blueprint.selected(doc, "codex"):
binding_path = root / "mem0/data/remote-client.json"
if binding_path.exists() or binding_path.is_symlink():
binding, binding_read = _metadata(root, "mem0/data/remote-client.json")
bound = (binding_read == "observed" and binding.get("schema") == "borg-memory-client/v1"
and binding.get("home") == str(root) and binding.get("owner") == doc["owner"]
and binding.get("instance_id") == doc["instance_id"]
and binding.get("profile") == profile)
steps.append(_step("remote-memory-client", "Verify the opt-in remote memory lifecycle client",
"staged" if bound else "incomplete",
{"binding": binding_read, "instance_matches": bound, "lifecycle_e2e": "NOT_VERIFIED"},
["Exact scoped Hub grant, authenticated route, four trusted native hooks, and a real capture/recall/replay canary"],
[command("memory-client", "check", purpose="Verify the scoped route and native trusted hooks"),
command("memory-client", "enable", purpose="Enable four hooks through the native Codex CAS configurator",
requires=["A matching hash-only Hub grant and native profile write approval"])],
["A staged token digest never proves the Hub grant or route.",
"Trusted configuration does not prove conversation capture, recall or replay."]))
native_commands = []
runtime = conductor.get("runtime", {})
node = runtime.get("nodeBin") if isinstance(runtime, dict) else None
Expand Down
Loading
Loading