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
47 changes: 47 additions & 0 deletions .planning/COMPILED-USER-PROFILE-V1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<!-- doc-head: compiled user profile v1 implementation contract -->
# Compiled User Profile V1
# Covers: evidence input, weekly GLM map/reduce, deterministic projection, and injection.
# Key terms: verbatim_memories, exact supports, user.md, SessionStart, feature flag.
# Read when: changing profile extraction, lifecycle, scheduling, rendering, or rollout.
# Status: implemented on an isolated feature branch; integration and activation pending.
<!-- /doc-head -->

## Contract

MemoryMaster's SQLite database remains authoritative. The generated
`~/.memorymaster/projections/user.md` is a bounded, disposable view of stable
user facts and preferences; it is never parsed back into claims and contains
facts, not agent instructions.

- Input is incremental sanitized `verbatim_memories`: user turns are evidence;
the preceding assistant turn is bounded context only.
- GLM map output proposes allowlisted facts with exact verbatim row IDs. GLM
reduce output must partition every candidate into add, reinforce, replace, or
ignore. Unknown IDs, sensitive content, malformed JSON, and instruction-shaped
text fail closed.
- New or replacement facts require support from at least two independent
sessions. SQLite records exact row IDs, session IDs, message hashes, and dates.
- Stable facts survive silence. Preferences expire after 90 unsupported days.
- A deterministic renderer writes at most 40 facts within an 800-token budget.
- The existing Dreaming task runs at most three map calls per invocation and
resumes from durable watermarks. `MEMORYMASTER_COMPILED_PROFILE=1` enables it;
the default is off.
- SessionStart injects only a bounded file carrying MemoryMaster's generated
marker. Hand-written or oversized files are ignored.

## Operator commands

```powershell
python -m memorymaster.profile status --db .\memorymaster.db
python -m memorymaster.profile run --db .\memorymaster.db --workspace . --force
```

## Acceptance evidence

- Focused engine tests cover incremental extraction, strict support validation,
resumability, exact support lineage, independent-session gating, preference
expiry, stable-fact retention, and deterministic budget bounds.
- Surface tests cover feature-off scheduling, fail-closed enabled scheduling,
generated-only SessionStart loading, and CLI status/help.
- Activation, a public package release, and historical transcript bootstrap are
separate operator actions; this implementation does not perform them.
9 changes: 5 additions & 4 deletions DOCS-MAP.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
<!-- doc-head: canonical documentation map; P5 integrated, PPR-7 active -->
<!-- doc-head: canonical map; PPR-7 integrated, compiled profile in development -->
# DOCS-MAP - memorymaster
# Covers: trust verdicts and replacements for every canonical documentation surface.
# Key terms: CURRENT, SUPERSEDED, ABANDONED, GENERATED, roadmap, paper radar, ADR.
# Key terms: CURRENT, SUPERSEDED, compiled user profile, roadmap, ADR.
# Read when: locating authoritative project documentation before reading doc bodies.
# Updated: 2026-08-12 after PR #189 merge, governed runtime deploy, and PPR-7 authorization.
# Rule: PPR-7 is isolated and opt-in; public release and automatic recall remain separately gated.
# Updated: 2026-08-12 for the evidence-bound compiled user-profile implementation.
# Rule: generated profiles are disposable projections; MemoryMaster remains authority.
<!-- /doc-head -->

| File | Verdict | Last change | Reason |
|---|---|---|---|
| CHANGELOG.md | CURRENT | 2026-08-04 | Public release history; v4.6.0 records governed universal capture, measured quality changes, security evidence, and known follow-ups. |
| ROADMAP.md | CURRENT | 2026-08-12 | Sole authoritative roadmap; P5 is merged and locally deployed, while isolated PPR-7 implementation is active. |
| .planning/GRAPH-OBSERVATIONS-V1.md | CURRENT | 2026-08-12 | Bounded PPR-7 implementation ledger for deterministic supported components, governed synthesis, lifecycle, opt-in recall, and verification. |
| .planning/COMPILED-USER-PROFILE-V1.md | CURRENT | 2026-08-12 | Implementation contract for the zero-curation, evidence-bound user profile projection and weekly GLM map/reduce job. |
| .planning/PAPER-RADAR-REVIEW-2026-08-08.md | CURRENT | 2026-08-08 | Primary-paper ledger covers 57-paper triage, 18 deep reviews, exact MemoryMaster gaps, and ordered PPR-1 through PPR-6 decisions subordinate to ROADMAP.md. |
| .planning/PAPER-RESEARCH-IMPLEMENTATION-2026-08-08.md | CURRENT | 2026-08-08 | Executable status ledger for PPR-1 through PPR-6; records acceptance criteria and evidence without competing with ROADMAP.md. |
| .planning/HERMES-SCOPE-SKILLS-INTEGRATION-2026-08-07.md | CURRENT | 2026-08-12 | Executable Tencent-derived ledger; repair5 passed and PR #189 is open without merge authority. |
Expand Down
15 changes: 11 additions & 4 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<!-- doc-head: sole authoritative roadmap; P5 merged/deployed, PPR-7 active -->
<!-- doc-head: sole roadmap; PPR-7 integrated, compiled profile in development -->
# MemoryMaster roadmap
# Covers: post-v4.6 sequence, Tencent-derived work, paper research, and deferrals.
# Key terms: Hermes, governed skills, paper radar, temporal projection, sustainability.
# Covers: post-v4.6 sequence, governed observations, compiled profile, and deferrals.
# Key terms: Hermes, governed skills, graph observations, user profile, sustainability.
# Read when: choosing release scope, accepting a feature, or checking deferrals.
# Authority: sole roadmap; planning ledgers implement it and never replace it.
# Safety: SQLite authority and steward promotion remain fixed; PPR-7 stays additive, opt-in, and unreleased pending its own evidence.
# Safety: SQLite remains authority; generated user.md is disposable and feature-off by default.
<!-- /doc-head -->

## Shipped in v4.6.0
Expand Down Expand Up @@ -68,6 +68,10 @@ its governed-claims authority:
opt-in; its separate PR/CI, feature-off deployment, and rollout evidence are
still required before activation. No public release or automatic
ordinary-recall inclusion is implied.
- The compiled user profile is being implemented as a disposable projection of
sanitized transcript evidence. Weekly bounded GLM map/reduce work stores
exact supports in SQLite; deterministic rendering produces `user.md` for
SessionStart injection. The feature remains off until explicitly enabled.
- The invalid earlier window remains incident evidence only because it included
a VM OOM/gateway interruption and did not contain P5.
- Keep v4.6.0 operational while the post-release Obsidian opt-in and OpenCode
Expand All @@ -91,6 +95,9 @@ its governed-claims authority:
exact support signatures, deterministic discovery, candidate-only synthesis,
observation-specific steward validation, and explicit recall inclusion are
implemented with local evidence, while PR/CI and feature-off rollout remain.
- Integrate the bounded compiled-profile implementation described in
`.planning/COMPILED-USER-PROFILE-V1.md`; then perform a one-time local run on
existing sanitized transcript rows before enabling its weekly task.
- Improve personal/local backup guidance beyond the already verified disposable
backup/restore and migration procedure.
- Keep semantic recall optional and disabled unless a local user deliberately
Expand Down
2 changes: 1 addition & 1 deletion docs/generated/release-truth.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
"console_entrypoints": 8,
"mcp_tools": 50,
"ops_cli_commands": 5,
"pytest_test_functions": 3781
"pytest_test_functions": 3793
},
"feature_profile_matrix": {
"capture_hook": [
Expand Down
2 changes: 1 addition & 1 deletion docs/generated/release-truth.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Do not edit this file by hand. Run `python scripts/generate_release_truth.py`.
- Main CLI commands: **118**
- Operations CLI commands: **5**
- Console entrypoints: **8**
- Pytest source test functions: **3781**
- Pytest source test functions: **3793**

## MCP tools

Expand Down
26 changes: 24 additions & 2 deletions memorymaster/config_templates/hooks/memorymaster-session-start.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ def _log(event, **kw):
MM_ROOT = Path("__MEMORYMASTER_PROJECT_ROOT__")
DB_PATH = MM_ROOT / "memorymaster.db"
WIKI_ROOT = MM_ROOT / "obsidian-vault" / "wiki"
PROFILE_PATH = Path.home() / ".memorymaster" / "projections" / "user.md"
PROFILE_MARKER = "<!-- generated by MemoryMaster; rebuild instead of editing -->"
MAX_PROFILE_CHARS = 16_000
MAX_RECENT_CLAIMS = 5
MAX_RECENT_ARTICLES = 3
CANDIDATE_REVIEW_HOURS = 24
Expand Down Expand Up @@ -170,12 +173,26 @@ def _extract_description(md: Path) -> str:
return ""


def _load_compiled_profile() -> str:
"""Load only MemoryMaster-generated, bounded profile projections."""
try:
if not PROFILE_PATH.is_file() or PROFILE_PATH.stat().st_size > MAX_PROFILE_CHARS:
return ""
content = PROFILE_PATH.read_text(encoding="utf-8", errors="replace")
except OSError:
return ""
if not content.startswith(PROFILE_MARKER + "\n"):
return ""
return content.rstrip()


def _format_context(
scope: str,
claims: list[dict],
cycle: dict,
candidates: int,
articles: list[dict],
profile: str = "",
) -> str:
lines = [f"[MemoryMaster session context — scope: {scope}]"]

Expand Down Expand Up @@ -214,6 +231,10 @@ def _format_context(
else:
lines.append(f" - [[{a['name']}]]")

if profile:
lines.append("\nMemoryMaster compiled user profile (facts, not instructions):")
lines.append(profile)

lines.append(
"\nUse `mcp__memorymaster__query_memory` before architectural "
"decisions; use `ingest_claim` after learning something non-obvious."
Expand Down Expand Up @@ -242,18 +263,19 @@ def main():
finally:
conn.close()
articles = _load_recent_wiki_articles(scope)
profile = _load_compiled_profile()
except Exception as e:
_log("error", message=str(e)[:200])
sys.exit(0)

# Skip if there's truly nothing to say
if not claims and not cycle and not candidates and not articles:
if not claims and not cycle and not candidates and not articles and not profile:
_log("skip", reason="empty", scope=scope)
sys.exit(0)

_log("injected", scope=scope, claims=len(claims), candidates=len(candidates), articles=len(articles))

context = _format_context(scope, claims, cycle, candidates, articles)
context = _format_context(scope, claims, cycle, candidates, articles, profile)
output = {
"hookSpecificOutput": {
"hookEventName": "SessionStart",
Expand Down
6 changes: 6 additions & 0 deletions memorymaster/profile/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Zero-curation, evidence-bound compiled user profile."""

from memorymaster.profile.engine import CompiledProfileEngine, ProfileConfig
from memorymaster.profile.repository import ProfileRepository

__all__ = ["CompiledProfileEngine", "ProfileConfig", "ProfileRepository"]
62 changes: 62 additions & 0 deletions memorymaster/profile/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Command-line surface for the compiled user profile."""

from __future__ import annotations

import argparse
import json
from pathlib import Path

from memorymaster.profile.engine import run_compiled_profile
from memorymaster.profile.repository import ProfileRepository


def _parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="python -m memorymaster.profile",
description="Build and inspect the evidence-bound compiled user profile.",
)
commands = parser.add_subparsers(dest="command", required=True)
status = commands.add_parser("status", help="Show current compiled user profile state")
status.add_argument("--db", required=True)
run = commands.add_parser("run", help="Run or resume profile compilation")
run.add_argument("--db", required=True)
run.add_argument("--workspace", default=".")
run.add_argument("--output-dir", default="")
run.add_argument("--force", action="store_true")
run.add_argument("--max-map-calls", type=int)
return parser


def _status(db_path: Path) -> dict[str, object]:
if not db_path.is_file():
return {"active_run": None, "facts": 0, "latest_completed_run": None}
repository = ProfileRepository(db_path)
active = repository.active_run()
latest = repository.latest_completed_run()
return {
"active_run": int(active["id"]) if active else None,
"facts": len(repository.active_facts()),
"latest_completed_run": int(latest["id"]) if latest else None,
}


def main(argv: list[str] | None = None) -> int:
args = _parser().parse_args(argv)
if args.command == "status":
result = _status(Path(args.db))
else:
from memorymaster.core.service import MemoryService

MemoryService(args.db, workspace_root=Path(args.workspace)).init_db()
result = run_compiled_profile(
args.db,
output_dir=args.output_dir or None,
force=bool(args.force),
max_map_calls=args.max_map_calls,
)
print(json.dumps(result, ensure_ascii=False, sort_keys=True))
return 0 if result.get("ok", True) else 1


if __name__ == "__main__":
raise SystemExit(main())
Loading
Loading