openmnemex 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. openmnemex/__init__.py +36 -0
  2. openmnemex/data/config/mnemex.config.md +72 -0
  3. openmnemex/data/integrations/opencode/mnemex.ts +86 -0
  4. openmnemex/data/templates/binding.template.md +23 -0
  5. openmnemex/data/templates/gitattributes.template +29 -0
  6. openmnemex/data/templates/gitignore.template +10 -0
  7. openmnemex/data/templates/index.template.md +20 -0
  8. openmnemex/data/templates/node.template.md +38 -0
  9. openmnemex/data/templates/procedures/capture.core.md +150 -0
  10. openmnemex/data/templates/procedures/curate.core.md +13 -0
  11. openmnemex/data/templates/procedures/fragments/_digests/capture.md +4 -0
  12. openmnemex/data/templates/procedures/fragments/_digests/ingest.md +9 -0
  13. openmnemex/data/templates/procedures/fragments/_digests/promote.md +4 -0
  14. openmnemex/data/templates/procedures/fragments/_digests/read.md +4 -0
  15. openmnemex/data/templates/procedures/fragments/capture/bind_status.claude.md +2 -0
  16. openmnemex/data/templates/procedures/fragments/capture/bind_status.mcp.md +1 -0
  17. openmnemex/data/templates/procedures/fragments/capture/capture_add.claude.md +14 -0
  18. openmnemex/data/templates/procedures/fragments/capture/capture_add.mcp.md +2 -0
  19. openmnemex/data/templates/procedures/fragments/capture/capture_drop_flag.claude.md +1 -0
  20. openmnemex/data/templates/procedures/fragments/capture/capture_drop_flag.mcp.md +1 -0
  21. openmnemex/data/templates/procedures/fragments/capture/capture_drop_flag2.claude.md +1 -0
  22. openmnemex/data/templates/procedures/fragments/capture/capture_drop_flag2.mcp.md +1 -0
  23. openmnemex/data/templates/procedures/fragments/capture/capture_status_budget.claude.md +1 -0
  24. openmnemex/data/templates/procedures/fragments/capture/capture_status_budget.mcp.md +1 -0
  25. openmnemex/data/templates/procedures/fragments/capture/capture_status_list.claude.md +3 -0
  26. openmnemex/data/templates/procedures/fragments/capture/capture_status_list.mcp.md +1 -0
  27. openmnemex/data/templates/procedures/fragments/capture/glean_step.claude.md +3 -0
  28. openmnemex/data/templates/procedures/fragments/capture/glean_step.mcp.md +1 -0
  29. openmnemex/data/templates/procedures/fragments/capture/init.claude.md +1 -0
  30. openmnemex/data/templates/procedures/fragments/capture/init.mcp.md +1 -0
  31. openmnemex/data/templates/procedures/fragments/capture/promote.claude.md +1 -0
  32. openmnemex/data/templates/procedures/fragments/capture/promote.mcp.md +1 -0
  33. openmnemex/data/templates/procedures/fragments/curate/capture_discard_all.claude.md +1 -0
  34. openmnemex/data/templates/procedures/fragments/curate/capture_discard_all.mcp.md +1 -0
  35. openmnemex/data/templates/procedures/fragments/curate/capture_drop.claude.md +1 -0
  36. openmnemex/data/templates/procedures/fragments/curate/capture_drop.mcp.md +1 -0
  37. openmnemex/data/templates/procedures/fragments/curate/capture_status_inline.claude.md +1 -0
  38. openmnemex/data/templates/procedures/fragments/curate/capture_status_inline.mcp.md +1 -0
  39. openmnemex/data/templates/procedures/fragments/curate/status.claude.md +1 -0
  40. openmnemex/data/templates/procedures/fragments/curate/status.mcp.md +1 -0
  41. openmnemex/data/templates/procedures/fragments/curate/trigger_condition.claude.md +3 -0
  42. openmnemex/data/templates/procedures/fragments/curate/trigger_condition.mcp.md +3 -0
  43. openmnemex/data/templates/procedures/fragments/promote/bulk_mode.claude.md +56 -0
  44. openmnemex/data/templates/procedures/fragments/promote/bulk_mode.mcp.md +28 -0
  45. openmnemex/data/templates/procedures/fragments/promote/consolidate_invoke.claude.md +2 -0
  46. openmnemex/data/templates/procedures/fragments/promote/consolidate_invoke.mcp.md +3 -0
  47. openmnemex/data/templates/procedures/fragments/promote/preflight.claude.md +18 -0
  48. openmnemex/data/templates/procedures/fragments/promote/preflight.mcp.md +10 -0
  49. openmnemex/data/templates/procedures/fragments/promote/retry_push.claude.md +11 -0
  50. openmnemex/data/templates/procedures/fragments/promote/retry_push.mcp.md +8 -0
  51. openmnemex/data/templates/procedures/fragments/promote/step1_flush.claude.md +4 -0
  52. openmnemex/data/templates/procedures/fragments/promote/step1_flush.mcp.md +4 -0
  53. openmnemex/data/templates/procedures/fragments/promote/step5_apply.claude.md +30 -0
  54. openmnemex/data/templates/procedures/fragments/promote/step5_apply.mcp.md +31 -0
  55. openmnemex/data/templates/procedures/fragments/read/bind_status.claude.md +2 -0
  56. openmnemex/data/templates/procedures/fragments/read/bind_status.mcp.md +1 -0
  57. openmnemex/data/templates/procedures/fragments/read/capture.claude.md +1 -0
  58. openmnemex/data/templates/procedures/fragments/read/capture.mcp.md +1 -0
  59. openmnemex/data/templates/procedures/fragments/read/empty_fork_check.claude.md +1 -0
  60. openmnemex/data/templates/procedures/fragments/read/empty_fork_check.mcp.md +1 -0
  61. openmnemex/data/templates/procedures/fragments/read/ingest.claude.md +1 -0
  62. openmnemex/data/templates/procedures/fragments/read/ingest.mcp.md +1 -0
  63. openmnemex/data/templates/procedures/fragments/read/init.claude.md +1 -0
  64. openmnemex/data/templates/procedures/fragments/read/init.mcp.md +1 -0
  65. openmnemex/data/templates/procedures/fragments/read/promote.claude.md +1 -0
  66. openmnemex/data/templates/procedures/fragments/read/promote.mcp.md +1 -0
  67. openmnemex/data/templates/procedures/fragments/read/read_cluster.claude.md +3 -0
  68. openmnemex/data/templates/procedures/fragments/read/read_cluster.mcp.md +1 -0
  69. openmnemex/data/templates/procedures/fragments/read/read_frontier.claude.md +3 -0
  70. openmnemex/data/templates/procedures/fragments/read/read_frontier.mcp.md +1 -0
  71. openmnemex/data/templates/procedures/fragments/read/read_nodes.claude.md +4 -0
  72. openmnemex/data/templates/procedures/fragments/read/read_nodes.mcp.md +1 -0
  73. openmnemex/data/templates/procedures/fragments/read/record_usage_revalidated.claude.md +4 -0
  74. openmnemex/data/templates/procedures/fragments/read/record_usage_revalidated.mcp.md +1 -0
  75. openmnemex/data/templates/procedures/fragments/read/record_usage_role.claude.md +4 -0
  76. openmnemex/data/templates/procedures/fragments/read/record_usage_role.mcp.md +1 -0
  77. openmnemex/data/templates/procedures/promote.core.md +141 -0
  78. openmnemex/data/templates/procedures/read.core.md +151 -0
  79. openmnemex/data/templates/registry.template.md +2 -0
  80. openmnemex/data/templates/user-config.template.md +15 -0
  81. openmnemex/mnx_binding.py +1078 -0
  82. openmnemex/mnx_common.py +593 -0
  83. openmnemex/mnx_compact.py +327 -0
  84. openmnemex/mnx_config.py +514 -0
  85. openmnemex/mnx_decay.py +136 -0
  86. openmnemex/mnx_doctor.py +447 -0
  87. openmnemex/mnx_er.py +328 -0
  88. openmnemex/mnx_glean.py +146 -0
  89. openmnemex/mnx_hooks.py +767 -0
  90. openmnemex/mnx_index.py +471 -0
  91. openmnemex/mnx_ingest.py +516 -0
  92. openmnemex/mnx_init.py +347 -0
  93. openmnemex/mnx_install.py +741 -0
  94. openmnemex/mnx_lock.py +259 -0
  95. openmnemex/mnx_mcp.py +1282 -0
  96. openmnemex/mnx_mesh.py +254 -0
  97. openmnemex/mnx_node.py +373 -0
  98. openmnemex/mnx_phonebook.py +352 -0
  99. openmnemex/mnx_procedures.py +202 -0
  100. openmnemex/mnx_promote.py +792 -0
  101. openmnemex/mnx_read.py +305 -0
  102. openmnemex/mnx_regen.py +199 -0
  103. openmnemex/mnx_resolve.py +208 -0
  104. openmnemex/mnx_simindex.py +194 -0
  105. openmnemex/mnx_stage.py +681 -0
  106. openmnemex/mnx_stamp.py +202 -0
  107. openmnemex/mnx_status.py +232 -0
  108. openmnemex-0.1.0.dist-info/METADATA +426 -0
  109. openmnemex-0.1.0.dist-info/RECORD +113 -0
  110. openmnemex-0.1.0.dist-info/WHEEL +5 -0
  111. openmnemex-0.1.0.dist-info/entry_points.txt +3 -0
  112. openmnemex-0.1.0.dist-info/licenses/LICENSE +21 -0
  113. openmnemex-0.1.0.dist-info/top_level.txt +1 -0
openmnemex/__init__.py ADDED
@@ -0,0 +1,36 @@
1
+ """openmnemex — the OpenMnemex Context Graph engine, packaged (plan v2 §7, commit 0a).
2
+
3
+ This bridge makes the flat ``scripts/`` engine importable BOTH ways without touching the
4
+ ``mnx_*`` modules themselves:
5
+
6
+ * **repo / Claude-plugin path (unchanged):** ``python3 scripts/mnx_stage.py …`` and plain
7
+ ``import mnx_stage`` with ``scripts/`` on ``sys.path`` — the modules import each other
8
+ flat (``import mnx_common``), exactly as before. Running a script directly never
9
+ executes this file.
10
+ * **packaged path (pip / uvx):** ``pyproject.toml`` maps this directory to the
11
+ ``openmnemex`` package. Importing ``openmnemex`` (or any ``openmnemex.mnx_*``) runs
12
+ this bridge first, which puts the package directory on ``sys.path`` so the flat
13
+ intra-engine imports keep resolving, then eagerly aliases every engine module as
14
+ ``openmnemex.<name>``.
15
+
16
+ The eager aliasing guarantees a SINGLE module identity per engine module — ``import
17
+ mnx_common`` and ``from openmnemex import mnx_common`` return the very same object — so
18
+ module-level state can never fork between the two import styles (the dual-identity trap
19
+ of path-bridged packages). Guarded by tests/test_packaging_imports.py.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import importlib
24
+ import sys
25
+ from pathlib import Path
26
+
27
+ _PKG_DIR = Path(__file__).resolve().parent
28
+
29
+ if str(_PKG_DIR) not in sys.path:
30
+ sys.path.insert(0, str(_PKG_DIR))
31
+
32
+ for _mod_file in sorted(_PKG_DIR.glob("mnx_*.py")):
33
+ _name = _mod_file.stem
34
+ _module = importlib.import_module(_name) # flat identity, same as the plugin path
35
+ sys.modules[f"{__name__}.{_name}"] = _module # `import openmnemex.mnx_x` → same object
36
+ globals()[_name] = _module # `from openmnemex import mnx_x`
@@ -0,0 +1,72 @@
1
+ ---
2
+ config_version: 1
3
+
4
+ # --- Decay (the ONE knob you set) ---
5
+ half_life_days: 180
6
+ pattern_halflife_bonus: 0.30 # patterns persist ~30% longer than domain facts (derived)
7
+
8
+ # --- Tiers ---
9
+ hot_k: 12
10
+ warm_band: 0.25
11
+ cold_ttl_days: 120
12
+ cold_recall_multiplier: 1.6
13
+ strength_max: 1.0
14
+
15
+ # --- Freshness (revalidation horizon; independent of decay) ---
16
+ freshness_ttl_days: 30 # a fact goes stale this long after it was last verified
17
+ freshness_pattern_bonus: 0.30 # patterns get a ~30% longer horizon (derived; you set one number)
18
+
19
+ # --- Usage boosts ---
20
+ boost:
21
+ contributed: 1.0
22
+ consulted: 0.5
23
+ traversed: 0.0
24
+
25
+ # --- Budget / scale ---
26
+ node_budget: 35
27
+
28
+ # --- Maintenance ---
29
+ compaction_cadence_days: 14
30
+ reconcile_cold_on: update # always | update | never
31
+
32
+ # --- Death policy ---
33
+ purge_dead: false # false = tombstone-and-retain (recommended)
34
+
35
+ # --- Ingestion (bootstrapping the graph from an existing repo; docs/corpus-ingestion.md) ---
36
+ ingest_bulk_soft_atoms: 500 # a bulk batch past this warns (drained continuously by --bulk promote)
37
+ ingest_bulk_hard_atoms: 5000 # a bulk batch refuses past this (drain it first)
38
+ ingest_max_atoms_per_run: 2000 # per-run cost ceiling; excess resumes on the next run
39
+ er_match_threshold: 0.85 # entity-resolution: ≥ this score → same entity (deterministic merge)
40
+ er_possible_threshold: 0.60 # [possible, match) → the HITL "⚠ suggested" band (LLM judge)
41
+ code_extract: gated # gated | deep | off — code value-gate (public/documented/config-only)
42
+ # max_glean_passes lives in USER config (~/.claude/mnemex/config.md; default 2) — glean is shared with capture.
43
+ ---
44
+
45
+ # Mnemex configuration
46
+
47
+ This file controls how your knowledge graph remembers and forgets. It is plain Markdown with a YAML
48
+ front-matter block, so it is readable by both you and the protocol.
49
+
50
+ ## The one decision that matters
51
+
52
+ You set **`half_life_days`** — how long an *unused* domain fact takes to lose half its relevance.
53
+ Everything else has a sensible default. Patterns (the "how") automatically persist longer than domain
54
+ facts (the "what") by `pattern_halflife_bonus` (default +30%); you do not tune two rates.
55
+
56
+ ## When you change a value
57
+
58
+ Bump `config_version`. The next consolidation (the back half of `mnx-promote`) will **re-normalize**
59
+ stored relevance so nothing jumps tiers abruptly, and `mnx-read` will warn you if a change is pending.
60
+ See `docs/configuration.md`.
61
+
62
+ ## Quick guidance
63
+
64
+ - Start with a **long** half-life and **generous** `cold_ttl_days`; tighten once you have observed real
65
+ usage. Nothing should die prematurely while you are still learning the rhythm of your graph.
66
+ - `hot_k` is how many nodes you want visible at *zero* extra read cost (chunk-1). 12–20 is reasonable.
67
+ - `node_budget` exists to keep reconciliation matching fast on writes; size it by write-path comfort.
68
+ - Keep `purge_dead: false` for audit-friendly, recoverable forgetting (tombstones, not deletion).
69
+ - `freshness_ttl_days` is a **separate** clock from decay: it is about whether a fact is still *true*, not
70
+ whether it is still *used*. A fact that is 40 days past verification is flagged `stale` on read so the agent
71
+ re-checks it — even if it is hot. Tag `volatility: timeless` on eternal facts (also stops them ever dying)
72
+ and `volatility: volatile` on fast-rotting ones. See `docs/freshness-and-revalidation.md`.
@@ -0,0 +1,86 @@
1
+ // OpenCode adapter (multi-agent plan v2, Phase 4 / commit 4a).
2
+ //
3
+ // Thin translator only: normalizes OpenCode's plugin event shape into the host-neutral
4
+ // event dict `mnx_hooks.core_*()` expects (see scripts/mnx_hooks.py), then execs the same
5
+ // Python engine Claude Code's hooks/hooks.json shells out to. No side-effect logic lives here.
6
+ //
7
+ // Mapping (session-lifecycle events only, per plan scope):
8
+ // session.created -> session-start (consent primer + graph sync)
9
+ // session.idle -> stop (usage-stamp flush + capture nudge bookkeeping)
10
+ // session.compacted -> pre-compact (re-arms the Stop nudge for the lost window)
11
+ // dispose() -> session-end (safety-net flush; best-effort synthetic session id,
12
+ // since OpenCode's dispose() carries no event payload)
13
+ //
14
+ // `session.compacted` (stable, post-hoc) is used instead of the experimental
15
+ // `experimental.session.compacting` pre-hook: core_pre_compact() is side-effect-only and never
16
+ // injects context into the compaction itself, so before-vs-after timing does not matter for us,
17
+ // and the stable event is a safer bet across OpenCode versions (§12 item 2 — re-verify the event
18
+ // API at integration time; https://opencode.ai/docs/plugins/ as of 2026-07).
19
+ //
20
+ // Delivery gap (documented in LIMITATIONS.md #3): mnx_hooks.py's HookOutcome (context/block/
21
+ // notice text -- e.g. the session-start consent primer) has no rendering surface here; OpenCode's
22
+ // `event` hook is fire-and-forget with no return channel back into the model's context. Only the
23
+ // underlying SIDE EFFECTS run (graph sync, usage-stamp flush, marker bookkeeping, doctor checks
24
+ // via the pre-compact re-arm); the advisory/consent prompts themselves reach the model via
25
+ // AGENTS.md instructions instead, same as any Assisted-tier host. Do not add a `console.log` of
26
+ // the hook's stdout here as a workaround -- it would print to OpenCode's own process output, not
27
+ // the model's context, and would look like working delivery while doing nothing.
28
+ // Scope boundary: this path is correct for this file's location in THIS repo checkout
29
+ // (integrations/opencode/mnemex.ts -> ../../scripts/mnx_hooks.py). When Phase 5's installer
30
+ // copies/emits this file into a consumer project's `.opencode/plugin/mnemex.ts`, that copy needs
31
+ // an installed-package-relative (or absolute, baked in at install time) path instead — rewriting
32
+ // HOOKS_SCRIPT for the target layout is Phase 5's job (commit 5b), not done here.
33
+ import type { Plugin } from "@opencode-ai/plugin"
34
+
35
+ const HOOKS_SCRIPT = new URL("../../scripts/mnx_hooks.py", import.meta.url).pathname
36
+
37
+ async function runHook(
38
+ cwd: string,
39
+ subcommand: string,
40
+ event: Record<string, unknown>,
41
+ ): Promise<void> {
42
+ try {
43
+ const proc = Bun.spawn(["python3", HOOKS_SCRIPT, subcommand], {
44
+ cwd,
45
+ stdin: "pipe",
46
+ stdout: "ignore",
47
+ stderr: "ignore",
48
+ })
49
+ proc.stdin.write(JSON.stringify(event))
50
+ proc.stdin.end()
51
+ await proc.exited
52
+ } catch {
53
+ // advisory hooks must never break a session — same fail-open contract as the Claude adapter
54
+ }
55
+ }
56
+
57
+ export const MnemexPlugin: Plugin = async ({ directory }) => {
58
+ let lastSessionID = ""
59
+
60
+ return {
61
+ event: async ({ event }) => {
62
+ switch (event.type) {
63
+ case "session.created": {
64
+ const sid = (event.properties as any)?.info?.id ?? ""
65
+ lastSessionID = sid
66
+ await runHook(directory, "session-start", { session_id: sid })
67
+ break
68
+ }
69
+ case "session.idle": {
70
+ const sid = (event.properties as any)?.sessionID ?? lastSessionID
71
+ lastSessionID = sid
72
+ await runHook(directory, "stop", { session_id: sid })
73
+ break
74
+ }
75
+ case "session.compacted": {
76
+ const sid = (event.properties as any)?.sessionID ?? lastSessionID
77
+ await runHook(directory, "pre-compact", { session_id: sid })
78
+ break
79
+ }
80
+ }
81
+ },
82
+ dispose: async () => {
83
+ if (lastSessionID) await runHook(directory, "session-end", { session_id: lastSessionID })
84
+ },
85
+ }
86
+ }
@@ -0,0 +1,23 @@
1
+ ---
2
+ # Project-level Mnemex binding. Lives at <project-repo>/.mnemex.md.
3
+ # Tells the plugin which knowledge graph THIS project reads from and writes to.
4
+ # Overrides the user-level default (~/.claude/mnemex/config.md).
5
+ #
6
+ # Set EXACTLY ONE of graph_remote / graph_path (if both are set, graph_path wins + a warning):
7
+ graph_remote: # a git remote — cloned & synced; writes commit + push. e.g. git@github.com:acme/payments-knowledge.git
8
+ graph_path: # a local folder — used in place, no clone/push. e.g. ~/knowledge/payments
9
+ default_team: # optional — default routing team for this project
10
+ author: # optional — identity for usage stamps / commits
11
+ ---
12
+
13
+ # Mnemex binding
14
+
15
+ This project's work is captured into the knowledge graph at `graph_remote` (a git repo) or `graph_path`
16
+ (a local folder). The graph itself — including its decay/tier behavior (`mnemex.config.md`) — lives in
17
+ that separate location, not here.
18
+
19
+ A **local folder** that is itself a git repo gets a local commit per change; a plain (non-git) folder
20
+ gets an append-only audit trail in its `.mnemex/history.log`. A **git remote** commits and pushes.
21
+
22
+ Commit this file to share the binding with the team, or add `.mnemex.md` to `.gitignore` to keep it
23
+ personal. See `docs/binding-and-graph-sync.md`.
@@ -0,0 +1,29 @@
1
+ # Mnemex: truth unions, derived regenerates.
2
+ # Drop this into a knowledge-graph repo as `.gitattributes` (mnx-init does this).
3
+ # The `mnx-regen` driver COMMAND is registered per-clone by `mnx_regen.py install`
4
+ # (mnx-init runs it; mnx-doctor verifies it). Without the command registered, git falls
5
+ # back to a normal 3-way merge — so the doctor flags an unregistered driver.
6
+
7
+ # --- TRUTH: append-only logs concatenate cleanly (built-in union driver) ---
8
+ registry.md merge=union
9
+ **/registry.md merge=union
10
+ .mnemex/highwater/** merge=union
11
+
12
+ # --- DERIVED: discard both sides, regenerate from truth on conflict ---
13
+ index.md merge=mnx-regen
14
+ index.[0-9][0-9][0-9].md merge=mnx-regen
15
+ **/index.md merge=mnx-regen
16
+ **/index.[0-9][0-9][0-9].md merge=mnx-regen
17
+ phonebook.md merge=mnx-regen
18
+ **/phonebook.md merge=mnx-regen
19
+ cross-links.md merge=mnx-regen
20
+ **/cross-links.md merge=mnx-regen
21
+ # split tier files (when tier_files: true)
22
+ warm.md merge=mnx-regen
23
+ cold.md merge=mnx-regen
24
+ dead.md merge=mnx-regen
25
+ cold.[0-9][0-9][0-9].md merge=mnx-regen
26
+ **/warm.md merge=mnx-regen
27
+ **/cold.md merge=mnx-regen
28
+ **/dead.md merge=mnx-regen
29
+ **/cold.[0-9][0-9][0-9].md merge=mnx-regen
@@ -0,0 +1,10 @@
1
+ # Mnemex knowledge-graph .gitignore
2
+ # Ignore ONLY transient runtime state. Durable state (.mnemex/highwater/,
3
+ # config_version, last_compaction) is intentionally committed for reproducibility.
4
+
5
+ # Transient: a live/stranded team or cluster lock, and an in-flight pass plan.
6
+ .mnemex/locks/
7
+ .mnemex/plans/
8
+
9
+ # OS
10
+ .DS_Store
@@ -0,0 +1,20 @@
1
+ # <folder-name> — index
2
+ > One-line description of this cluster. Routing decisions are made on this line. <!-- chunk 1 -->
3
+
4
+ ## Children <!-- chunk 1 -->
5
+ - <child-folder>/ — one-line description
6
+ <!-- (leaf clusters: "none") -->
7
+
8
+ ## Hot <!-- chunk 1 tail; top-K -->
9
+ | id | type | summary | aliases | strength | last_update | stale_after |
10
+ |----|------|---------|---------|----------|-------------|-------------|
11
+
12
+ ## Warm <!-- chunk 2 -->
13
+ | id | type | summary | aliases | strength | last_update | stale_after |
14
+ |----|------|---------|---------|----------|-------------|-------------|
15
+
16
+ ## Cold <!-- chunk 3+ -->
17
+ | id | type | summary | aliases | strength | last_update | stale_after | expires |
18
+ |----|------|---------|---------|----------|-------------|-------------|---------|
19
+
20
+ <!-- GENERATED FILE. Do not hand-edit. Regenerated by mnx-promote apply and mnx-doctor --fix. -->
@@ -0,0 +1,38 @@
1
+ ---
2
+ id: REPLACE-with-stable-slug # [a-z0-9-]+, assigned at creation, NEVER changes
3
+ type: domain # domain | pattern
4
+ title: Human-readable title (may change freely)
5
+ summary: One line that lands in the index verbatim — the match + routing surface.
6
+ aliases: [other name, abbreviation, synonym]
7
+ domain: [sub-domain] # may be a LIST (a node can belong to >1 sub-index)
8
+ status: active # active | dead (retired-why is a field: superseded-by, died)
9
+ confidence: high # high | medium | low
10
+ volatility: default # default | timeless | volatile | <int days> — freshness horizon (Freshness & Revalidation)
11
+ trigger: null # REQUIRED (non-null) for type: pattern; null for domain
12
+ mentions: # GENERATED from body [[wiki-links]] at promote (Link Reconciliation). Author links
13
+ - { name: other-node-id, resolved_id: other-node-id, type: null } # inline in the BODY, not here.
14
+ - { name: page-not-created-yet, resolved_id: null, type: null } # resolved_id null = a red-link
15
+ edges: # GENERATED MIRROR of resolved mentions (Link Reconciliation §8) — not hand-authored
16
+ - { to: other-node-id, type: null } # untyped by default; type is optional
17
+ references: [] # SOFT cross-TEAM pointers only (no integrity guarantee)
18
+ provenance:
19
+ artifact: name-of-build-artifact
20
+ reviews: [] # human review-point ids that fed this node
21
+ session: 1970-01-01T00:00:00Z
22
+ created: 1970-01-01T00:00:00Z
23
+ updated: 1970-01-01T00:00:00Z # meaning-change time (NOT usage)
24
+ verified: 1970-01-01T00:00:00Z # last confirmed-still-true time (NOT usage, NOT meaning-change)
25
+ ---
26
+
27
+ ## Summary
28
+ One paragraph; read first on a body expansion.
29
+
30
+ ## What
31
+ (domain nodes) The knowledge itself. Link to other pages inline by NAME with wiki-links —
32
+ e.g. "…settles against [[other-node-id]] before posting." Promote resolves them (Link Reconciliation).
33
+
34
+ ## How / Notes
35
+ (pattern nodes) The prescriptive procedure / rule and its rationale.
36
+
37
+ ## Provenance
38
+ Why this node exists; trace to the artifact and the specific human review points.
@@ -0,0 +1,150 @@
1
+ ---
2
+ name: mnx-capture
3
+ description: Capture the durable knowledge produced in the current build session into the local Mnemex staging tier — cheap, fast, no lock, no graph mutation. Use this when a user finishes building or designing and wants to persist what was learned — domain facts AND the patterns/decisions from human review — or says "save this to the knowledge graph", "remember this for next time", "capture this", "stage this". Run it INCREMENTALLY at natural checkpoints too, not only at session end — after a sub-task lands, a review settles, or a compaction is signalled — staging only the delta (re-capturing identical content is a no-op), keeping keypoints loss-proof. Runs in-session to mine the artifact and review points from the transcript; extracts atoms, scores each now/later/not-needed, stages them with self-sufficient provenance. Also curates staging (--drop <id>, --discard-all) — the escape valve at the hard cap. Does NOT reconcile or merge into the shared graph — that is the deliberate, batched {{PROC:promote}} step.
4
+ ---
5
+
6
+ # mnx-capture — stage a session's knowledge (the `git commit` of memory)
7
+
8
+ Turn what this session produced — the artifact **and** the human review/clarification points — into
9
+ **staged atoms**: provisional, local, self-sufficient knowledge units. The *how* lives in the
10
+ conversation (the corrections, the rejected alternatives), so mine the transcript, not just the final
11
+ artifact, **now** — by promote time the transcript is gone.
12
+
13
+ Capture is the **fast, local half** of the capture/promote split. It is cheap, takes no lock, never
14
+ reads the graph's cluster indexes, and **never mutates the graph**. Reconcile / merge / consolidate /
15
+ push all happen later in `{{PROC:promote}}`. (Analogy: capture = `git commit`; promote = `git push`/PR.)
16
+
17
+ **Capture incrementally, at checkpoints — not only at the very end.** Since that transcript shrinks at
18
+ every compaction, one end-of-session dump is the most loss-exposed way to run this: prefer to capture the
19
+ **delta** at each natural checkpoint (a sub-task lands, a review decision settles, a compaction is
20
+ signalled). Capture is built for repetition — it stages only what is new and re-staging identical content
21
+ is an idempotent no-op (content-hash id) — so it stays cheap; the delta mechanics are Phase 0b + Phase 1.
22
+
23
+ Background: `docs/staging-and-promotion.md` (the whole model), `docs/rationale-and-concepts.md`
24
+ (node types, ids), `docs/data-model-and-schemas.md` (staged-atom front-matter). Helper:
25
+ `mnx_stage` (the only writer here) and `mnx_binding` (locate the graph).
26
+
27
+ {{INCLUDE:curate}}
28
+ ## Preflight — locate the graph (always first)
29
+ Run {{CALL:bind_status}}.
30
+ - If `resolved` is false → **STOP**: *"No Mnemex graph configured. Run `{{PROC:init}}`."*
31
+ - **Echo the resolved graph before staging** so the author sees where atoms will land: show the
32
+ `resolution` line, e.g. *"Capturing into **payments-knowledge** (source: project .mnemex.md)."*
33
+ If `default_fallback` is true, make it **prominent** — there was no project `.mnemex.md`, so this is
34
+ going to the user's personal graph: *"⚠️ No project binding here — capturing into your personal
35
+ graph **personal-notes**. If that's wrong, cd into the right repo and re-capture."* This closes the
36
+ silent-binding gap (LIMITATIONS.md #2): the graph choice is now visible at capture time, not discovered
37
+ at promote time.
38
+ - Note `graph_root` (for routing intent only — capture writes **nothing** there) and `staging_root`
39
+ (where atoms land). Capture is local; it does **not** need `clone_present` / a sync.
40
+
41
+ ## Phase 0 — Budget pre-check (backpressure)
42
+ Run {{CALL:capture_status_budget}}.
43
+ - `budget.level == "hard"` → **STOP capturing** and give the user the two ways out (the backpressure
44
+ bound): either *"run `{{PROC:promote}}` to merge + drain staging"* **or** *"make room by discarding
45
+ with {{CALL:capture_drop_flag}} or `--discard-all`."* Show the staged list (`mnx_stage.py list`)
46
+ so they can choose what to drop.
47
+ - `budget.level == "soft"` → proceed, but **warn** the user once that a promote is due.
48
+
49
+ ## Phase 0b — Delta ledger (what is already staged)
50
+ Before extracting, load the staged ledger so you capture only the **delta** and can see what is already
51
+ covered:
52
+
53
+ {{CALL:capture_status_list}}
54
+
55
+ Each row is `provisional-id · type · score · summary · age`. Read it as the running record of "keypoints
56
+ already captured this session." In Phase 1, extract only what this ledger does **not** already cover, then
57
+ look for the next uncaptured keypoint. You do not need to diff by hand — re-staging identical content is an
58
+ idempotent no-op (content-hash id) — but consulting the ledger keeps you from re-mining ground you already
59
+ staged and makes the incremental checkpoint loop cheap. A genuinely refined atom (same concept, better
60
+ body) will hash differently and stage as a new atom; that is fine — reconcile collapses the pair at promote.
61
+
62
+ ## Phase 1 — Extract the delta (mine the transcript, honor the node-size budget)
63
+ Decompose the artifact + transcript into candidate atoms, skipping anything the Phase 0b ledger already
64
+ covers — extract the **new** keypoints since your last capture. For each, decide:
65
+ - **`domain`** (a fact about the system/business — the *what*), or
66
+ - **`pattern`** (prescriptive *how*, with a `trigger` = the *when* it applies). **Mine human review
67
+ points specifically**: a correction or a rejected alternative becomes a pattern — *"do X not Y,
68
+ because…"* — with a trigger describing the situation it governs.
69
+
70
+ Draft `summary` (one line), `aliases` (other names the concept goes by), `domain` (routing key(s)),
71
+ and a tight body. **Propose a `volatility`** (freshness horizon, Freshness & Revalidation) from the atom's content shape —
72
+ `volatile` for a fact that rots fast (a URL, version, price, on-call name), `timeless` for a durable
73
+ definition/invariant (also exempts it from ever auto-dying), or leave it `default` (the type-derived
74
+ horizon) for everything else. It is a *suggestion*: the human confirms or overrides it at the promote
75
+ gate, so bias toward `default` when unsure.
76
+
77
+ **Embed links inline as `[[wiki-links]]` (the mesh authoring surface, Link Reconciliation).** When a note refers to
78
+ another concept, name it inline in the body with double brackets — *"…settles against
79
+ `[[iso8583-field124]]` before posting…"*. Link freely **by name**, even to a page that does not exist in
80
+ the graph yet: promote resolves each `[[name]]`, and an unresolved one becomes a **red-link** that goes
81
+ live automatically the day that page is created. The pipe means display text, wiki-native
82
+ (`[[iso8583-field124|Field 124]]`), **not** a relationship type. `mnx_stage` hoists these into the atom's
83
+ `mentions:` for you — you just write natural `[[links]]`. Do **not** hand-author `edges:`; that is a
84
+ generated mirror promote builds.
85
+
86
+ **Node-size budget — do NOT split at capture.** Keep each atom's body focused, but if a genuinely
87
+ single idea runs past the soft cap (`node_body_max_chars`, default ~6000), **capture it whole** — never
88
+ truncate. **Splitting an over-budget note into sibling pages + a link is promote's job, not capture's**
89
+ (promote is graph-aware; capture is local and dumb). Cap the number of atoms per session to what the
90
+ session actually produced; do not pad.
91
+
92
+ ## Phase 1b — Glean (one bounded "what did I miss?" re-scan)
93
+ Pass 1 (Phase 1) is your first extraction pass; a single pass reliably **under-captures the *how*** — the
94
+ review-point patterns are the easiest to miss. So run **one** bounded recall pass (this is the shared
95
+ *gleanings* technique, guardrail mode):
96
+
97
+ - Re-read the transcript once and ask specifically: *"what durable fact — **especially a review-point
98
+ pattern** (a correction, a rejected alternative, a 'gotcha') — did I not stage yet?"*, guided by the
99
+ Phase-0b delta ledger so you look only at ground you have **not** already covered.
100
+ - Any new candidate still passes the Phase-2 `now/later/not-needed` scoring gate and Phase-3 staging;
101
+ re-staging identical content stays an idempotent no-op (content-hash id), so this pass can only add.
102
+ - **Bound the loop deterministically.** After the pass, count staged atoms before vs after and check
103
+ whether to stop:
104
+
105
+ {{CALL:glean_step}}
106
+
107
+ `stop:true` (`reason: no-progress` = the pass added nothing new, or `reason: cap` = the pass budget
108
+ `max_glean_passes` is reached — default **2**, from user config) → stop gleaning and go to Phase 2/4.
109
+ Only if `stop:false` do you run one more pass (`--pass 2`). This is **not** a per-topic walk — it is at
110
+ most a couple of bounded re-scans, keeping capture cheap/fast (the design goal). Episodic capture does
111
+ **not** build a coverage checklist — that (checklist mode) is ingest-only.
112
+
113
+ ## Phase 2 — Score each atom (`now | later | not-needed`)
114
+ A momentary judgement of **intrinsic importance — NOT novelty**. Drift between sessions is fine; there
115
+ is no rigid rubric. Novelty/dedup is decided later at promote (reconcile may drop an atom as a
116
+ duplicate), so do **not** pre-judge "probably already known."
117
+ - **`now`** → stage **with `--urgent`**. (Urgent never inline-pushes — promote is still the only
118
+ writer; urgent only sharpens the nag.)
119
+ - **`later`** → stage normally.
120
+ - **`not-needed`** → **silently drop.** No staging, no audit, no asking the user. Reserve this for the
121
+ clearly ephemeral or trivially derivable.
122
+
123
+ ## Phase 3 — Stage (the only write)
124
+ For each kept atom, write it to the staging tier. Provenance must be **self-sufficient for a cold
125
+ promote** — artifact ref, the specific review ids, rejected alternative(s), the rationale, and the
126
+ session timestamp:
127
+
128
+ {{CALL:capture_add}}
129
+
130
+ (Or use flags for a simple atom: `add --type domain --summary "…" --domain settlement --score later
131
+ --aliases "a;b" --artifact <id> --reviews "r3;r7" --rationale "…" --body "…"`.) The helper mints the
132
+ **provisional id** (a content hash, `stg-…`) — never invent an id, never reuse a real node id. A
133
+ re-capture of identical content is idempotent and reports `action: already-staged` (vs `staged` for
134
+ new content) — count the two separately so your report reflects the true delta.
135
+
136
+ ## Phase 4 — Report
137
+ Summarize what was staged **this pass**: the new atoms (counts by score), any `urgent`, and the
138
+ post-stage `budget.level`. When capturing incrementally, distinguish this pass's delta from the total
139
+ staged set so the checkpoint loop is legible ("staged 2 new; 7 total staged"). If the helper
140
+ **refused** an atom (`action: refused`), surface the hard-cap message and give both ways out —
141
+ `{{PROC:promote}}` to drain staging, or {{CALL:capture_drop_flag2}} / `--discard-all` to make
142
+ room. Then stop — **do not** offer to push or merge; that is promote's job.
143
+
144
+ ## Never
145
+ - Never reconcile, merge, re-tier, or open the graph's cluster indexes — capture is local-only.
146
+ - Never write into `graph_root`, never take the team lock, never commit or push.
147
+ - Never stamp a staged atom or give it a real node id (the `stg-` provisional id is content-derived).
148
+ - Never `not-needed`-drop on a *novelty* guess — only the clearly ephemeral/derivable.
149
+ - Never split, resolve a `[[link]]`, or hand-author `edges:` — capture preserves links; **promote** splits
150
+ over-budget notes and resolves links (Link Reconciliation). Never truncate an over-budget atom — capture it whole.
@@ -0,0 +1,13 @@
1
+ ---
2
+ name: curate
3
+ description: Review, drop, or discard atoms already staged in the local Mnemex staging tier — no extraction, no graph mutation. The un-stage escape valve, including at the hard capture-budget cap.
4
+ ---
5
+ ## Curate mode — review / drop / discard (no extraction)
6
+ {{BLOCK:trigger_condition}}
7
+ - **Review first** when helpful: {{CALL:capture_status_inline}} shows the
8
+ staged atoms (provisional id · score · summary · age). (`{{PROC:status}}` shows the same list.)
9
+ - `--drop <id>` → {{CALL:capture_drop}}; report the dropped atom's id + summary (or that it
10
+ was not found).
11
+ - `--discard-all` → show the list and **confirm with the user**, then {{CALL:capture_discard_all}}; report the
12
+ count removed.
13
+ This touches **only** the local staging tier — never the graph, never the stamp spill. Then stop.
@@ -0,0 +1,4 @@
1
+ Procedure: check `capture_status`'s ledger for what's already staged, mine the transcript for
2
+ the delta (facts + review-point patterns) plus one bounded `glean_step` recall pass, score
3
+ each atom now/later/not-needed, then `capture_add` per atom with self-sufficient provenance.
4
+ Never split an over-budget body — stage it whole; that's promote's job.
@@ -0,0 +1,9 @@
1
+ Procedure: `ingest_acquire` the repo (read-only) → `ingest_probe` for the extraction
2
+ units + a source-tree→cluster map (gate #1: confirm scope) → distil durable atoms with
3
+ `capture_add(ingest_batch=...)`, never transcribe (zero atoms from a file is fine) →
4
+ `glean_coverage` until complete/cap → `er_resolve` to dedupe and MERGE into existing pages
5
+ (one entity → one node) → drain with `promote_begin`/`promote_apply` in bulk. For every
6
+ NEW cluster created, write its one-line description into the cluster `index.md` header
7
+ (`> …` line) — read routing decides on that line, and regeneration preserves it; never
8
+ leave the scaffold placeholder. Two gates
9
+ only (scope, bulk summary); deleted-file orphans surface for the human, never auto-tombstoned.
@@ -0,0 +1,4 @@
1
+ Procedure: `promote_begin` (locks + guards) → `promote_context` (near-matches, link preview)
2
+ → draft one plan (create/merge/supersede/resurrect/drop_dup/hold per staged atom, plus link
3
+ and consolidate decisions) → present it for human approval → `promote_apply` with
4
+ `approved: true`. Contradictions go to `hold`, never force-resolved or silently dropped.
@@ -0,0 +1,4 @@
1
+ Procedure: `read_frontier` to route (by description match) → `read_cluster` (hot tier
2
+ first, widen only if insufficient) → `read_nodes` for only the ids you'll actually use →
3
+ `record_usage` with a role (contributed/consulted/traversed) for every id loaded. Flag stale
4
+ rows and staged-overlay atoms in the answer; never body-merge a staged/graph contradiction.
@@ -0,0 +1,2 @@
1
+ `python3 "${CLAUDE_PLUGIN_ROOT}/scripts/mnx_binding.py" status --session <sid>` (the session id
2
+ from session-start, if you have one — see mnx-init step 1; honors a mid-session graph switch)
@@ -0,0 +1 @@
1
+ the `bind_status` tool
@@ -0,0 +1,14 @@
1
+ ```
2
+ python3 "${CLAUDE_PLUGIN_ROOT}/scripts/mnx_stage.py" add --binding-session <sid> --json <<'JSON'
3
+ { "type": "pattern",
4
+ "summary": "Reconcile settlement before posting",
5
+ "aliases": ["settle-recon"],
6
+ "domain": ["settlement"],
7
+ "trigger": "reviewing or curating a settlement spec",
8
+ "score": "now", "urgent": true, "volatility": "default",
9
+ "provenance": { "artifact": "tap-vic-settlement-spec", "reviews": ["r3","r7"],
10
+ "rejected": ["post-then-reconcile (causes orphaned legs)"],
11
+ "rationale": "human correction in review r7" },
12
+ "body": "Always reconcile the settlement batch against [[iso8583-field124]] before posting legs, because …" }
13
+ JSON
14
+ ```
@@ -0,0 +1,2 @@
1
+ Call `capture_add` with the atom's fields: `type, summary, aliases?, domain?, trigger?
2
+ (required for pattern), score, urgent?, volatility?, provenance?, body`.
@@ -0,0 +1 @@
1
+ `/mnemex:mnx-capture --drop <id>`
@@ -0,0 +1 @@
1
+ `/mnemex:mnx-capture --drop <id>`
@@ -0,0 +1 @@
1
+ `python3 "${CLAUDE_PLUGIN_ROOT}/scripts/mnx_stage.py" status`
@@ -0,0 +1 @@
1
+ the `capture_status` tool (check its `budget.level`)
@@ -0,0 +1,3 @@
1
+ ```
2
+ python3 "${CLAUDE_PLUGIN_ROOT}/scripts/mnx_stage.py" list
3
+ ```
@@ -0,0 +1 @@
1
+ Call `capture_status` (its staged ledger).
@@ -0,0 +1,3 @@
1
+ ```
2
+ python3 "${CLAUDE_PLUGIN_ROOT}/scripts/mnx_glean.py" step --before <n_before> --after <n_after> --pass 1
3
+ ```
@@ -0,0 +1 @@
1
+ Call `glean_step` with `before=<n_before>`, `after=<n_after>`, `pass_no=1`.
@@ -0,0 +1 @@
1
+ /mnemex:mnx-promote
@@ -0,0 +1 @@
1
+ the promote procedure
@@ -0,0 +1 @@
1
+ `capture_discard_all` with `confirm=true`
@@ -0,0 +1 @@
1
+ `mnx_stage.py clear-one --id <id>`
@@ -0,0 +1 @@
1
+ `capture_drop` with `pid=<id>`
@@ -0,0 +1 @@
1
+ `python3 "${CLAUDE_PLUGIN_ROOT}/scripts/mnx_stage.py" list`
@@ -0,0 +1 @@
1
+ /mnemex:mnx-status
@@ -0,0 +1,3 @@
1
+ If invoked with `--drop <provisional-id>` or `--discard-all`, this is the local **un-stage** path — the
2
+ cheap way to prune staging (and the escape valve when the hard cap is blocking new captures). It still
3
+ runs the locate preflight (to find the staging tier) but does **no** extraction, scoring, or staging: