generated from coulomb/repo-seed
Add Markitect bridge and activation quality
This commit is contained in:
@@ -88,4 +88,7 @@ and a package compilation request for the `ContextPackageCompiler` boundary.
|
||||
See [docs/architecture.md](docs/architecture.md) for the first architecture
|
||||
sketch, [docs/local-persistence.md](docs/local-persistence.md) for the local
|
||||
file-backed adapter, [docs/policy-audit.md](docs/policy-audit.md) for local
|
||||
policy and review gates, and [SCOPE.md](SCOPE.md) for repository boundaries.
|
||||
policy and review gates, [docs/markitect-interop.md](docs/markitect-interop.md)
|
||||
for package bridge boundaries, [docs/activation-quality.md](docs/activation-quality.md)
|
||||
for retrieval and evaluation behavior, and [SCOPE.md](SCOPE.md) for repository
|
||||
boundaries.
|
||||
|
||||
52
docs/activation-quality.md
Normal file
52
docs/activation-quality.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Activation Quality
|
||||
|
||||
`phase-memory` activation can now be planned from deterministic graph
|
||||
neighborhoods and event paths without requiring embeddings, vector stores, or
|
||||
LLM ranking.
|
||||
|
||||
## Graph Neighborhood Retrieval
|
||||
|
||||
`retrieve_graph_neighborhood` expands from seed nodes across graph edges.
|
||||
|
||||
Supported controls:
|
||||
|
||||
- maximum hops
|
||||
- edge kind filters
|
||||
- `in`, `out`, or `both` direction
|
||||
- phase filters
|
||||
- memory kind filters
|
||||
|
||||
Candidates receive deterministic scores based on explicit priority, graph
|
||||
distance, phase, lifecycle state, confidence, source-backed status, freshness,
|
||||
and policy allowance.
|
||||
|
||||
## Event Path Activation
|
||||
|
||||
`select_event_path` activates ordered event ids from a structured
|
||||
`MemoryPath`. Active paths are selected by default. Abandoned, merged, or
|
||||
compacted paths can be included explicitly when a caller wants to inspect
|
||||
inactive branches.
|
||||
|
||||
## Token Estimation
|
||||
|
||||
`TokenEstimator` is a protocol. The default implementation is
|
||||
`WordCountTokenEstimator`, which keeps tests deterministic and dependency-free.
|
||||
Provider-specific tokenizers can be supplied later without changing retrieval
|
||||
contracts.
|
||||
|
||||
## Quality Report
|
||||
|
||||
`activation_quality_report` emits local metrics suitable for later export to
|
||||
evaluation systems:
|
||||
|
||||
- selected expected nodes
|
||||
- omitted required nodes
|
||||
- policy-denied required nodes
|
||||
- token budget utilization
|
||||
- stale item activation count
|
||||
- provenance coverage
|
||||
- source span coverage
|
||||
- explanation coverage
|
||||
|
||||
The fixture `tests/fixtures/activation-quality-report.json` pins a small
|
||||
expected report for regression tests.
|
||||
93
docs/markitect-interop.md
Normal file
93
docs/markitect-interop.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Markitect Interop
|
||||
|
||||
`phase-memory` consumes and emits Markitect-compatible memory contracts while
|
||||
keeping ownership boundaries explicit.
|
||||
|
||||
## Ownership
|
||||
|
||||
Markitect owns:
|
||||
|
||||
- markdown-facing memory profile syntax
|
||||
- memory graph contract vocabulary
|
||||
- memory selection validation
|
||||
- context-package internals
|
||||
- package compilation semantics
|
||||
|
||||
`phase-memory` owns:
|
||||
|
||||
- phase-aware runtime planning
|
||||
- lifecycle planning
|
||||
- activation planning
|
||||
- policy/audit/review checks around memory runtime behavior
|
||||
- adapter orchestration
|
||||
- package compile request handoff
|
||||
|
||||
## Contract Inputs
|
||||
|
||||
The dependency-light boundary accepts already-valid dictionaries for:
|
||||
|
||||
- `markitect.memory.profile.v1`
|
||||
- `markitect.memory.graph.v1`
|
||||
- `markitect.memory.selection.v1`
|
||||
|
||||
`LocalMarkitectValidator` uses local ingress diagnostics only. It checks
|
||||
schema ids, required ids, known first-slice memory kinds, and graph edge
|
||||
integrity. It does not claim to be the Markitect schema owner.
|
||||
|
||||
`OptionalMarkitectValidator` can wrap a Markitect-owned validator object with
|
||||
methods such as `validate_memory_profile`, `validate_memory_graph`, and
|
||||
`validate_memory_selection`. If no delegate is configured, it falls back to the
|
||||
local boundary.
|
||||
|
||||
## Package Request
|
||||
|
||||
Activation planning emits a Markitect-compatible selection and a package
|
||||
request envelope:
|
||||
|
||||
```text
|
||||
phase_memory.markitect.package_request.v1
|
||||
```
|
||||
|
||||
The request includes:
|
||||
|
||||
- selection id
|
||||
- graph id
|
||||
- profile id
|
||||
- selected node ids
|
||||
- selected event ids
|
||||
- budget metadata
|
||||
- policy metadata
|
||||
- selected item provenance metadata
|
||||
- compiler name
|
||||
- compiler diagnostics
|
||||
- original selection
|
||||
|
||||
Selected item metadata preserves source spans, provenance, confidence,
|
||||
freshness, namespace, policy labels, and reason selected. This gives Markitect
|
||||
enough input to build inspectable packages without requiring phase-memory to
|
||||
understand package internals.
|
||||
|
||||
## Package Response
|
||||
|
||||
Package compiler responses are wrapped in:
|
||||
|
||||
```text
|
||||
phase_memory.markitect.package_response.v1
|
||||
```
|
||||
|
||||
The wrapper keeps the Markitect response opaque and extracts only a
|
||||
`package_ref` when present.
|
||||
|
||||
## Fixture Catalog
|
||||
|
||||
Compatibility fixtures live under `tests/fixtures/`:
|
||||
|
||||
- `memory-profile.json` - valid Markitect-compatible profile
|
||||
- `memory-graph.json` - valid Markitect-compatible graph
|
||||
- `markitect-invalid-profile.json` - invalid profile diagnostics
|
||||
- `markitect-invalid-graph.json` - invalid graph diagnostics
|
||||
- `runtime-activation-plan-snapshot.json` - activation/package request shape
|
||||
- `markitect-package-response.json` - opaque package response fixture
|
||||
|
||||
These fixtures are small and deterministic so adjacent repositories can reuse
|
||||
them as examples without installing Markitect.
|
||||
@@ -1,6 +1,14 @@
|
||||
"""Profile-driven memory phase planning."""
|
||||
|
||||
from .activation import plan_activation
|
||||
from .bridge import (
|
||||
MARKITECT_PACKAGE_REQUEST_SCHEMA,
|
||||
MARKITECT_PACKAGE_RESPONSE_SCHEMA,
|
||||
LocalMarkitectValidator,
|
||||
OptionalMarkitectValidator,
|
||||
package_request_from_selection,
|
||||
package_response_envelope,
|
||||
)
|
||||
from .contracts import graph_from_markitect, profile_from_markitect
|
||||
from .lifecycle import (
|
||||
plan_compaction,
|
||||
@@ -30,6 +38,13 @@ from .models import (
|
||||
)
|
||||
from .paths import abandon_path, branch_path, compact_path, create_path, merge_path, path_event
|
||||
from .policy import POLICY_OPERATION_POINTS, MemoryOperation, make_review_record
|
||||
from .retrieval import (
|
||||
WordCountTokenEstimator,
|
||||
activation_quality_report,
|
||||
plan_neighborhood_activation,
|
||||
retrieve_graph_neighborhood,
|
||||
select_event_path,
|
||||
)
|
||||
from .planner import plan_profile_execution
|
||||
from .runtime import PhaseMemoryRuntime
|
||||
|
||||
@@ -55,6 +70,10 @@ __all__ = [
|
||||
"PhaseMemoryRuntime",
|
||||
"POLICY_OPERATION_POINTS",
|
||||
"MemoryOperation",
|
||||
"MARKITECT_PACKAGE_REQUEST_SCHEMA",
|
||||
"MARKITECT_PACKAGE_RESPONSE_SCHEMA",
|
||||
"LocalMarkitectValidator",
|
||||
"OptionalMarkitectValidator",
|
||||
"abandon_path",
|
||||
"branch_path",
|
||||
"compact_path",
|
||||
@@ -70,6 +89,13 @@ __all__ = [
|
||||
"plan_retention",
|
||||
"profile_from_markitect",
|
||||
"path_event",
|
||||
"package_request_from_selection",
|
||||
"package_response_envelope",
|
||||
"WordCountTokenEstimator",
|
||||
"activation_quality_report",
|
||||
"plan_neighborhood_activation",
|
||||
"retrieve_graph_neighborhood",
|
||||
"select_event_path",
|
||||
]
|
||||
|
||||
__version__ = "0.1.0"
|
||||
|
||||
@@ -17,6 +17,7 @@ def plan_activation(
|
||||
) -> ActivationPlan:
|
||||
selected: list[str] = []
|
||||
omitted: list[dict[str, object]] = []
|
||||
selected_items: dict[str, dict[str, object]] = {}
|
||||
token_estimate = 0
|
||||
|
||||
ordered_nodes = _ordered_nodes(graph, priority_node_ids)
|
||||
@@ -29,6 +30,15 @@ def plan_activation(
|
||||
omitted.append({"id": node.node_id, "reason": "max_tokens", "tokens": node_tokens})
|
||||
continue
|
||||
selected.append(node.node_id)
|
||||
selected_items[node.node_id] = {
|
||||
"source_spans": list(node.source_spans),
|
||||
"provenance": list(node.provenance),
|
||||
"confidence": node.confidence,
|
||||
"freshness": dict(node.freshness),
|
||||
"namespace": dict(node.namespace),
|
||||
"policy": dict(node.policy),
|
||||
"reason_selected": "priority" if node.node_id in priority_node_ids else "budget_order",
|
||||
}
|
||||
token_estimate += node_tokens
|
||||
|
||||
selected_event_ids: tuple[str, ...] = ()
|
||||
@@ -54,6 +64,7 @@ def plan_activation(
|
||||
"max_items": max_items,
|
||||
"max_tokens": max_tokens,
|
||||
"omitted": omitted,
|
||||
"selected_items": selected_items,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
97
src/phase_memory/bridge.py
Normal file
97
src/phase_memory/bridge.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""Markitect package bridge helpers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Protocol
|
||||
|
||||
from .contracts import ContractIngressResult, graph_from_markitect, profile_from_markitect, selection_from_markitect
|
||||
from .models import Diagnostic
|
||||
from .utils import stable_digest
|
||||
|
||||
MARKITECT_PACKAGE_REQUEST_SCHEMA = "phase_memory.markitect.package_request.v1"
|
||||
MARKITECT_PACKAGE_RESPONSE_SCHEMA = "phase_memory.markitect.package_response.v1"
|
||||
|
||||
|
||||
class MarkitectValidator(Protocol):
|
||||
def validate_profile(self, data: dict[str, Any]) -> ContractIngressResult: ...
|
||||
def validate_graph(self, data: dict[str, Any]) -> ContractIngressResult: ...
|
||||
def validate_selection(self, data: dict[str, Any]) -> ContractIngressResult: ...
|
||||
|
||||
|
||||
class LocalMarkitectValidator:
|
||||
"""Dependency-light validation boundary using local ingress diagnostics."""
|
||||
|
||||
def validate_profile(self, data: dict[str, Any]) -> ContractIngressResult:
|
||||
return profile_from_markitect(data)
|
||||
|
||||
def validate_graph(self, data: dict[str, Any]) -> ContractIngressResult:
|
||||
return graph_from_markitect(data)
|
||||
|
||||
def validate_selection(self, data: dict[str, Any]) -> ContractIngressResult:
|
||||
return selection_from_markitect(data)
|
||||
|
||||
|
||||
class OptionalMarkitectValidator:
|
||||
"""Adapter shell for optional Markitect-owned validation."""
|
||||
|
||||
def __init__(self, delegate: Any | None = None) -> None:
|
||||
self.delegate = delegate
|
||||
self.local = LocalMarkitectValidator()
|
||||
|
||||
def validate_profile(self, data: dict[str, Any]) -> ContractIngressResult:
|
||||
if self.delegate and hasattr(self.delegate, "validate_memory_profile"):
|
||||
return self.delegate.validate_memory_profile(data)
|
||||
return self.local.validate_profile(data)
|
||||
|
||||
def validate_graph(self, data: dict[str, Any]) -> ContractIngressResult:
|
||||
if self.delegate and hasattr(self.delegate, "validate_memory_graph"):
|
||||
return self.delegate.validate_memory_graph(data)
|
||||
return self.local.validate_graph(data)
|
||||
|
||||
def validate_selection(self, data: dict[str, Any]) -> ContractIngressResult:
|
||||
if self.delegate and hasattr(self.delegate, "validate_memory_selection"):
|
||||
return self.delegate.validate_memory_selection(data)
|
||||
return self.local.validate_selection(data)
|
||||
|
||||
|
||||
def package_request_from_selection(
|
||||
selection: dict[str, Any],
|
||||
*,
|
||||
compiler: str,
|
||||
diagnostics: tuple[Diagnostic, ...] = (),
|
||||
) -> dict[str, Any]:
|
||||
metadata = dict(selection.get("metadata") or {})
|
||||
request_id = f"markitect-package-request:{stable_digest([selection, compiler])}"
|
||||
return {
|
||||
"schema_version": MARKITECT_PACKAGE_REQUEST_SCHEMA,
|
||||
"id": request_id,
|
||||
"selection_id": selection.get("id"),
|
||||
"graph_id": selection.get("graph"),
|
||||
"profile_id": selection.get("profile"),
|
||||
"selected_nodes": list(selection.get("nodes", ())),
|
||||
"selected_events": list(selection.get("events", ())),
|
||||
"budget": {
|
||||
"max_items": metadata.get("max_items"),
|
||||
"max_tokens": metadata.get("max_tokens"),
|
||||
"token_estimate": metadata.get("token_estimate"),
|
||||
},
|
||||
"policy": metadata.get("policy", {}),
|
||||
"provenance": {
|
||||
"selected_items": metadata.get("selected_items", {}),
|
||||
"planned_by": metadata.get("planned_by"),
|
||||
},
|
||||
"compiler": compiler,
|
||||
"compiler_diagnostics": [diagnostic.to_dict() for diagnostic in diagnostics],
|
||||
"selection": dict(selection),
|
||||
"dry_run": True,
|
||||
}
|
||||
|
||||
|
||||
def package_response_envelope(response: dict[str, Any], *, request_id: str) -> dict[str, Any]:
|
||||
return {
|
||||
"schema_version": MARKITECT_PACKAGE_RESPONSE_SCHEMA,
|
||||
"request_id": request_id,
|
||||
"package_ref": response.get("package_id") or response.get("package_ref") or "",
|
||||
"compiler_diagnostics": list(response.get("diagnostics", ())),
|
||||
"response": dict(response),
|
||||
}
|
||||
182
src/phase_memory/retrieval.py
Normal file
182
src/phase_memory/retrieval.py
Normal file
@@ -0,0 +1,182 @@
|
||||
"""Deterministic retrieval and activation quality helpers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Protocol
|
||||
|
||||
from .activation import plan_activation
|
||||
from .models import ActivationPlan, MemoryEvent, MemoryGraph, MemoryNode, MemoryPath, MemoryPathState
|
||||
|
||||
|
||||
class TokenEstimator(Protocol):
|
||||
def estimate_text(self, text: str) -> int: ...
|
||||
def estimate_node(self, node: MemoryNode) -> int: ...
|
||||
def estimate_event(self, event: MemoryEvent) -> int: ...
|
||||
|
||||
|
||||
class WordCountTokenEstimator:
|
||||
def estimate_text(self, text: str) -> int:
|
||||
return max(1, len(text.split()))
|
||||
|
||||
def estimate_node(self, node: MemoryNode) -> int:
|
||||
return self.estimate_text(node.text or node.kind)
|
||||
|
||||
def estimate_event(self, event: MemoryEvent) -> int:
|
||||
return self.estimate_text(" ".join([event.kind, *event.package_refs, *event.activation_refs]))
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RetrievalCandidate:
|
||||
node_id: str
|
||||
distance: int
|
||||
score: int
|
||||
reasons: tuple[str, ...]
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"node_id": self.node_id,
|
||||
"distance": self.distance,
|
||||
"score": self.score,
|
||||
"reasons": list(self.reasons),
|
||||
}
|
||||
|
||||
|
||||
def retrieve_graph_neighborhood(
|
||||
graph: MemoryGraph,
|
||||
*,
|
||||
seed_node_ids: tuple[str, ...],
|
||||
max_hops: int = 1,
|
||||
edge_kinds: tuple[str, ...] = (),
|
||||
direction: str = "both",
|
||||
phases: tuple[str, ...] = (),
|
||||
kinds: tuple[str, ...] = (),
|
||||
) -> tuple[RetrievalCandidate, ...]:
|
||||
distances = {node_id: 0 for node_id in seed_node_ids if node_id in graph.node_ids}
|
||||
frontier = list(distances)
|
||||
for hop in range(1, max_hops + 1):
|
||||
next_frontier: list[str] = []
|
||||
for node_id in frontier:
|
||||
for edge in graph.edges:
|
||||
if edge_kinds and edge.kind not in edge_kinds:
|
||||
continue
|
||||
neighbors: list[str] = []
|
||||
if direction in {"out", "both"} and edge.source == node_id:
|
||||
neighbors.append(edge.target)
|
||||
if direction in {"in", "both"} and edge.target == node_id:
|
||||
neighbors.append(edge.source)
|
||||
for neighbor in neighbors:
|
||||
if neighbor not in distances:
|
||||
distances[neighbor] = hop
|
||||
next_frontier.append(neighbor)
|
||||
frontier = next_frontier
|
||||
|
||||
by_id = graph.node_by_id()
|
||||
candidates: list[RetrievalCandidate] = []
|
||||
for node_id, distance in distances.items():
|
||||
node = by_id[node_id]
|
||||
if phases and node.phase.value not in phases:
|
||||
continue
|
||||
if kinds and node.kind not in kinds:
|
||||
continue
|
||||
candidates.append(score_candidate(node, distance=distance, explicit_priority=node_id in seed_node_ids))
|
||||
return tuple(sorted(candidates, key=lambda item: (-item.score, item.distance, item.node_id)))
|
||||
|
||||
|
||||
def score_candidate(node: MemoryNode, *, distance: int, explicit_priority: bool = False, policy_allowed: bool = True) -> RetrievalCandidate:
|
||||
score = 100 - (distance * 10)
|
||||
reasons = [f"graph_distance:{distance}"]
|
||||
if explicit_priority:
|
||||
score += 50
|
||||
reasons.append("explicit_priority")
|
||||
if node.phase.value in {"stabilized", "rigid"}:
|
||||
score += 10
|
||||
reasons.append(f"phase:{node.phase.value}")
|
||||
if node.lifecycle.value == "active":
|
||||
score += 5
|
||||
reasons.append("lifecycle:active")
|
||||
if node.confidence is not None:
|
||||
score += int(node.confidence * 10)
|
||||
reasons.append("confidence")
|
||||
if node.source_spans:
|
||||
score += 5
|
||||
reasons.append("source_backed")
|
||||
if node.freshness.get("status") == "stale":
|
||||
score -= 20
|
||||
reasons.append("freshness:stale")
|
||||
if not policy_allowed:
|
||||
score -= 100
|
||||
reasons.append("policy:denied")
|
||||
return RetrievalCandidate(node.node_id, distance, score, tuple(reasons))
|
||||
|
||||
|
||||
def select_event_path(
|
||||
events: tuple[MemoryEvent, ...],
|
||||
path: MemoryPath,
|
||||
*,
|
||||
max_events: int,
|
||||
include_inactive: bool = False,
|
||||
) -> tuple[str, ...]:
|
||||
if path.state != MemoryPathState.ACTIVE and not include_inactive:
|
||||
return ()
|
||||
available = {event.event_id for event in events}
|
||||
return tuple(event_id for event_id in path.event_ids if event_id in available)[:max_events]
|
||||
|
||||
|
||||
def plan_neighborhood_activation(
|
||||
graph: MemoryGraph,
|
||||
*,
|
||||
seed_node_ids: tuple[str, ...],
|
||||
max_hops: int,
|
||||
max_items: int,
|
||||
max_tokens: int,
|
||||
profile_id: str | None = None,
|
||||
estimator: TokenEstimator | None = None,
|
||||
) -> tuple[ActivationPlan, tuple[RetrievalCandidate, ...]]:
|
||||
estimator = estimator or WordCountTokenEstimator()
|
||||
candidates = retrieve_graph_neighborhood(graph, seed_node_ids=seed_node_ids, max_hops=max_hops)
|
||||
priority = tuple(candidate.node_id for candidate in candidates)
|
||||
plan = plan_activation(
|
||||
graph,
|
||||
max_items=max_items,
|
||||
max_tokens=max_tokens,
|
||||
profile_id=profile_id,
|
||||
priority_node_ids=priority,
|
||||
)
|
||||
# Preserve the estimator contract for callers without changing the current
|
||||
# activation plan wire shape.
|
||||
plan.selection.setdefault("metadata", {})["estimator"] = estimator.__class__.__name__
|
||||
return plan, candidates
|
||||
|
||||
|
||||
def activation_quality_report(
|
||||
plan: ActivationPlan,
|
||||
*,
|
||||
expected_node_ids: tuple[str, ...] = (),
|
||||
policy_denied_node_ids: tuple[str, ...] = (),
|
||||
) -> dict:
|
||||
selected = set(plan.selected_node_ids)
|
||||
omitted_ids = {str(item.get("id")) for item in plan.omitted}
|
||||
expected = set(expected_node_ids)
|
||||
selected_expected = sorted(selected & expected)
|
||||
omitted_required = sorted((expected - selected) & omitted_ids)
|
||||
selected_metadata = plan.selection.get("metadata", {}).get("selected_items", {})
|
||||
return {
|
||||
"selected_expected_nodes": selected_expected,
|
||||
"omitted_required_nodes": omitted_required,
|
||||
"policy_denied_required_nodes": sorted(expected & set(policy_denied_node_ids)),
|
||||
"token_budget_utilization": round(plan.token_estimate / plan.max_tokens, 4) if plan.max_tokens else 0,
|
||||
"stale_item_activation_count": sum(
|
||||
1 for node_id in plan.selected_node_ids if selected_metadata.get(node_id, {}).get("freshness", {}).get("status") == "stale"
|
||||
),
|
||||
"provenance_coverage": _coverage(plan.selected_node_ids, selected_metadata, "provenance"),
|
||||
"source_span_coverage": _coverage(plan.selected_node_ids, selected_metadata, "source_spans"),
|
||||
"explanation_coverage": _coverage(plan.selected_node_ids, selected_metadata, "reason_selected"),
|
||||
}
|
||||
|
||||
|
||||
def _coverage(node_ids: tuple[str, ...], metadata: dict, key: str) -> float:
|
||||
if not node_ids:
|
||||
return 0.0
|
||||
covered = sum(1 for node_id in node_ids if metadata.get(node_id, {}).get(key))
|
||||
return round(covered / len(node_ids), 4)
|
||||
@@ -15,6 +15,7 @@ from .adapters import (
|
||||
NoopContextPackageCompiler,
|
||||
RecordingAuditSink,
|
||||
)
|
||||
from .bridge import MARKITECT_PACKAGE_REQUEST_SCHEMA, package_request_from_selection, package_response_envelope
|
||||
from .contracts import ContractIngressResult, graph_from_markitect, profile_from_markitect
|
||||
from .lifecycle import plan_compaction, plan_refresh, plan_retention
|
||||
from .models import (
|
||||
@@ -35,7 +36,7 @@ from .ports import AuditSink, ContextPackageCompiler, MemoryEventLog, MemoryGrap
|
||||
from .utils import compact_dict, stable_digest, to_plain
|
||||
|
||||
RUNTIME_ENVELOPE_SCHEMA = "phase_memory.runtime.envelope.v1"
|
||||
PACKAGE_REQUEST_SCHEMA = "phase_memory.package_request.v1"
|
||||
PACKAGE_REQUEST_SCHEMA = MARKITECT_PACKAGE_REQUEST_SCHEMA
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -208,7 +209,7 @@ class PhaseMemoryRuntime:
|
||||
valid=True,
|
||||
diagnostics=(),
|
||||
source_ref=source_ref,
|
||||
data={"package_request": request, "package_response": response},
|
||||
data={"package_request": request, "package_response": package_response_envelope(response, request_id=request["id"])},
|
||||
)
|
||||
|
||||
def export_graph(self, *, graph_id: str = "local", source_ref: str = "local-store") -> dict[str, Any]:
|
||||
@@ -301,14 +302,7 @@ class PhaseMemoryRuntime:
|
||||
)
|
||||
|
||||
def package_request(self, selection: dict[str, Any]) -> dict[str, Any]:
|
||||
request_id = f"package-request:{stable_digest(selection)}"
|
||||
return {
|
||||
"schema_version": PACKAGE_REQUEST_SCHEMA,
|
||||
"id": request_id,
|
||||
"selection": dict(selection),
|
||||
"compiler": self.package_compiler.__class__.__name__,
|
||||
"dry_run": True,
|
||||
}
|
||||
return package_request_from_selection(selection, compiler=self.package_compiler.__class__.__name__)
|
||||
|
||||
def _contract_envelope(
|
||||
self,
|
||||
|
||||
16
tests/fixtures/activation-quality-report.json
vendored
Normal file
16
tests/fixtures/activation-quality-report.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"explanation_coverage": 1.0,
|
||||
"omitted_required_nodes": [
|
||||
"artifact.profile"
|
||||
],
|
||||
"policy_denied_required_nodes": [
|
||||
"artifact.profile"
|
||||
],
|
||||
"provenance_coverage": 0.0,
|
||||
"selected_expected_nodes": [
|
||||
"decision.boundary"
|
||||
],
|
||||
"source_span_coverage": 1.0,
|
||||
"stale_item_activation_count": 0,
|
||||
"token_budget_utilization": 0.45
|
||||
}
|
||||
18
tests/fixtures/markitect-invalid-graph.json
vendored
Normal file
18
tests/fixtures/markitect-invalid-graph.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"schema_version": "markitect.memory.graph.v1",
|
||||
"id": "invalid-graph",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "node.a",
|
||||
"kind": "decision"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "edge.bad",
|
||||
"kind": "depends_on",
|
||||
"source": "node.a",
|
||||
"target": "missing"
|
||||
}
|
||||
]
|
||||
}
|
||||
4
tests/fixtures/markitect-invalid-profile.json
vendored
Normal file
4
tests/fixtures/markitect-invalid-profile.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"schema_version": "markitect.memory.profile.v99",
|
||||
"title": "Invalid Profile Without Id"
|
||||
}
|
||||
8
tests/fixtures/markitect-package-response.json
vendored
Normal file
8
tests/fixtures/markitect-package-response.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"package_id": "package:activation-fixture",
|
||||
"diagnostics": [],
|
||||
"item_count": 2,
|
||||
"metadata": {
|
||||
"compiled_by": "markitect-fixture"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"event.restart:max_tokens",
|
||||
"risk.durable-write:max_tokens"
|
||||
],
|
||||
"package_request_id": "package-request:854b8a9e0f9a",
|
||||
"package_request_id": "markitect-package-request:60c0ec4c172b",
|
||||
"plan_id": "activation:6e0ba40234cd",
|
||||
"selected_event_ids": [
|
||||
"event.activation"
|
||||
@@ -19,7 +19,7 @@
|
||||
"activation_omitted_items"
|
||||
],
|
||||
"operation": "graph.activation.plan",
|
||||
"operation_id": "op:826d3b06fa5b",
|
||||
"operation_id": "op:85705b61958d",
|
||||
"schema_version": "phase_memory.runtime.envelope.v1",
|
||||
"subject": {
|
||||
"id": "phase-memory-fixture-graph",
|
||||
|
||||
92
tests/test_markitect_bridge.py
Normal file
92
tests/test_markitect_bridge.py
Normal file
@@ -0,0 +1,92 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from phase_memory.bridge import (
|
||||
MARKITECT_PACKAGE_REQUEST_SCHEMA,
|
||||
MARKITECT_PACKAGE_RESPONSE_SCHEMA,
|
||||
LocalMarkitectValidator,
|
||||
OptionalMarkitectValidator,
|
||||
package_response_envelope,
|
||||
)
|
||||
from phase_memory.contracts import graph_from_markitect
|
||||
from phase_memory.runtime import PhaseMemoryRuntime
|
||||
|
||||
|
||||
FIXTURES = Path(__file__).parent / "fixtures"
|
||||
|
||||
|
||||
def _load(name: str):
|
||||
return json.loads((FIXTURES / name).read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def test_local_markitect_validator_reports_contract_diagnostics() -> None:
|
||||
validator = LocalMarkitectValidator()
|
||||
|
||||
invalid_profile = validator.validate_profile(_load("markitect-invalid-profile.json"))
|
||||
invalid_graph = validator.validate_graph(_load("markitect-invalid-graph.json"))
|
||||
|
||||
assert not invalid_profile.valid
|
||||
assert [diagnostic.code for diagnostic in invalid_profile.diagnostics] == ["unsupported_profile_schema", "missing_profile_id"]
|
||||
assert not invalid_graph.valid
|
||||
assert [diagnostic.code for diagnostic in invalid_graph.diagnostics] == ["missing_edge_target"]
|
||||
|
||||
|
||||
def test_optional_validator_falls_back_to_local_boundary() -> None:
|
||||
validator = OptionalMarkitectValidator()
|
||||
|
||||
result = validator.validate_selection({"schema_version": "markitect.memory.selection.v1", "id": "selection.a"})
|
||||
|
||||
assert result.valid
|
||||
assert result.subject_kind == "memory_selection"
|
||||
|
||||
|
||||
def test_activation_preserves_metadata_for_package_request() -> None:
|
||||
graph_data = _load("memory-graph.json")
|
||||
graph_data["nodes"][0]["confidence"] = 0.95
|
||||
graph_data["nodes"][0]["policy"] = {"labels": ["project-local"], "trust_zone": "local"}
|
||||
graph_data["nodes"][0]["provenance"] = [{"source": "architecture"}]
|
||||
graph = graph_from_markitect(graph_data).value
|
||||
runtime = PhaseMemoryRuntime()
|
||||
|
||||
envelope = runtime.plan_activation(
|
||||
graph.to_dict(),
|
||||
max_items=1,
|
||||
max_tokens=20,
|
||||
profile_id="phase-memory-fixture-profile",
|
||||
priority_node_ids=("decision.boundary",),
|
||||
)
|
||||
|
||||
request = envelope["data"]["package_request"]
|
||||
item = request["provenance"]["selected_items"]["decision.boundary"]
|
||||
assert request["schema_version"] == MARKITECT_PACKAGE_REQUEST_SCHEMA
|
||||
assert request["selection_id"] == envelope["data"]["activation_plan"]["plan_id"]
|
||||
assert request["graph_id"] == "phase-memory-fixture-graph"
|
||||
assert request["profile_id"] == "phase-memory-fixture-profile"
|
||||
assert request["selected_nodes"] == ["decision.boundary"]
|
||||
assert item["source_spans"] == [{"path": "docs/architecture.md", "line_start": 1}]
|
||||
assert item["provenance"] == [{"source": "architecture"}]
|
||||
assert item["confidence"] == 0.95
|
||||
assert item["policy"]["labels"] == ["project-local"]
|
||||
assert item["reason_selected"] == "priority"
|
||||
|
||||
|
||||
def test_package_response_envelope_keeps_markitect_response_opaque() -> None:
|
||||
response = _load("markitect-package-response.json")
|
||||
|
||||
envelope = package_response_envelope(response, request_id="request.a")
|
||||
|
||||
assert envelope["schema_version"] == MARKITECT_PACKAGE_RESPONSE_SCHEMA
|
||||
assert envelope["request_id"] == "request.a"
|
||||
assert envelope["package_ref"] == "package:activation-fixture"
|
||||
assert envelope["response"] == response
|
||||
|
||||
|
||||
def test_runtime_compile_package_uses_bridge_response_envelope() -> None:
|
||||
runtime = PhaseMemoryRuntime()
|
||||
selection = {"schema_version": "markitect.memory.selection.v1", "id": "selection.a", "nodes": ["node.a"], "events": []}
|
||||
|
||||
envelope = runtime.compile_package(selection)
|
||||
|
||||
assert envelope["data"]["package_request"]["schema_version"] == MARKITECT_PACKAGE_REQUEST_SCHEMA
|
||||
assert envelope["data"]["package_response"]["schema_version"] == MARKITECT_PACKAGE_RESPONSE_SCHEMA
|
||||
assert envelope["data"]["package_response"]["package_ref"] == "package:selection.a"
|
||||
95
tests/test_retrieval_quality.py
Normal file
95
tests/test_retrieval_quality.py
Normal file
@@ -0,0 +1,95 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from phase_memory.contracts import graph_from_markitect
|
||||
from phase_memory.models import MemoryEvent, MemoryPath, MemoryPathState
|
||||
from phase_memory.retrieval import (
|
||||
WordCountTokenEstimator,
|
||||
activation_quality_report,
|
||||
plan_neighborhood_activation,
|
||||
retrieve_graph_neighborhood,
|
||||
select_event_path,
|
||||
)
|
||||
|
||||
|
||||
FIXTURES = Path(__file__).parent / "fixtures"
|
||||
|
||||
|
||||
def _graph():
|
||||
return graph_from_markitect(json.loads((FIXTURES / "memory-graph.json").read_text(encoding="utf-8"))).value
|
||||
|
||||
|
||||
def test_retrieve_graph_neighborhood_is_stable_and_filterable() -> None:
|
||||
candidates = retrieve_graph_neighborhood(
|
||||
_graph(),
|
||||
seed_node_ids=("decision.boundary",),
|
||||
max_hops=1,
|
||||
edge_kinds=("governs",),
|
||||
direction="out",
|
||||
)
|
||||
|
||||
assert [candidate.node_id for candidate in candidates] == ["decision.boundary", "artifact.profile"]
|
||||
assert candidates[0].reasons[:2] == ("graph_distance:0", "explicit_priority")
|
||||
|
||||
|
||||
def test_event_path_selection_respects_path_state_and_budget() -> None:
|
||||
events = (
|
||||
MemoryEvent("event.a", "user_turn"),
|
||||
MemoryEvent("event.b", "agent_turn"),
|
||||
MemoryEvent("event.c", "tool_call"),
|
||||
)
|
||||
active = MemoryPath("path.active", event_ids=("event.a", "event.b", "event.c"))
|
||||
abandoned = MemoryPath("path.abandoned", event_ids=("event.a",), state=MemoryPathState.ABANDONED)
|
||||
|
||||
assert select_event_path(events, active, max_events=2) == ("event.a", "event.b")
|
||||
assert select_event_path(events, abandoned, max_events=2) == ()
|
||||
assert select_event_path(events, abandoned, max_events=2, include_inactive=True) == ("event.a",)
|
||||
|
||||
|
||||
def test_neighborhood_activation_uses_retrieval_order_and_estimator_label() -> None:
|
||||
plan, candidates = plan_neighborhood_activation(
|
||||
_graph(),
|
||||
seed_node_ids=("decision.boundary",),
|
||||
max_hops=1,
|
||||
max_items=2,
|
||||
max_tokens=40,
|
||||
profile_id="phase-memory-fixture-profile",
|
||||
)
|
||||
|
||||
assert [candidate.node_id for candidate in candidates][:2] == ["decision.boundary", "artifact.profile"]
|
||||
assert plan.selected_node_ids[:2] == ("decision.boundary", "artifact.profile")
|
||||
assert plan.selection["metadata"]["estimator"] == "WordCountTokenEstimator"
|
||||
|
||||
|
||||
def test_token_estimator_accounts_for_nodes_and_events() -> None:
|
||||
graph = _graph()
|
||||
estimator = WordCountTokenEstimator()
|
||||
|
||||
assert estimator.estimate_node(graph.nodes[0]) == 9
|
||||
assert estimator.estimate_event(graph.events[0]) >= 2
|
||||
|
||||
|
||||
def test_activation_quality_report_is_deterministic() -> None:
|
||||
plan, _ = plan_neighborhood_activation(
|
||||
_graph(),
|
||||
seed_node_ids=("decision.boundary",),
|
||||
max_hops=1,
|
||||
max_items=1,
|
||||
max_tokens=20,
|
||||
profile_id="phase-memory-fixture-profile",
|
||||
)
|
||||
|
||||
report = activation_quality_report(
|
||||
plan,
|
||||
expected_node_ids=("decision.boundary", "artifact.profile"),
|
||||
policy_denied_node_ids=("artifact.profile",),
|
||||
)
|
||||
|
||||
expected = json.loads((FIXTURES / "activation-quality-report.json").read_text(encoding="utf-8"))
|
||||
assert report["selected_expected_nodes"] == ["decision.boundary"]
|
||||
assert report["omitted_required_nodes"] == ["artifact.profile"]
|
||||
assert report["policy_denied_required_nodes"] == ["artifact.profile"]
|
||||
assert report["token_budget_utilization"] == 0.45
|
||||
assert report["source_span_coverage"] == 1.0
|
||||
assert report["explanation_coverage"] == 1.0
|
||||
assert report == expected
|
||||
@@ -79,4 +79,5 @@ def test_runtime_compile_package_wraps_compiler_response() -> None:
|
||||
|
||||
assert envelope["operation"] == "package.compile"
|
||||
assert envelope["data"]["package_request"]["selection"] == selection
|
||||
assert envelope["data"]["package_response"]["package_id"] == "package:selection.a"
|
||||
assert envelope["data"]["package_response"]["package_ref"] == "package:selection.a"
|
||||
assert envelope["data"]["package_response"]["response"]["package_id"] == "package:selection.a"
|
||||
|
||||
@@ -44,30 +44,30 @@ not what adjacent repositories may already provide.
|
||||
|
||||
## Current Baseline - 2026-05-18
|
||||
|
||||
Overall maturity: **3.1 / 5**
|
||||
Overall maturity: **3.7 / 5**
|
||||
|
||||
The repo has crossed from intent-only into a working deterministic library
|
||||
foundation, a usable local runtime facade, a CLI, a file-backed local
|
||||
workspace, and first-slice policy/review/audit gates. It is not yet an
|
||||
interop-complete runtime because richer Markitect package bridge, activation
|
||||
quality, and service contracts remain ahead.
|
||||
workspace, first-slice policy/review/audit gates, and a concrete Markitect
|
||||
package bridge, and deterministic activation quality helpers. It is not yet
|
||||
service-ready.
|
||||
|
||||
| Dimension | Current | Target | Evidence | Needed Next |
|
||||
| --- | ---: | ---: | --- | --- |
|
||||
| Intent and boundaries | 4.0 | 5.0 | `INTENT.md`, `SCOPE.md`, `README.md`, architecture doc, PMEM-WP-0001 closure | Keep boundaries current as runtime behavior expands. |
|
||||
| Package foundation | 3.0 | 4.0 | Python package, exports, runtime facade, CLI entrypoint, dependency-light tests | Add local persistence and richer adapter configuration. |
|
||||
| Profile contract ingress | 2.5 | 4.0 | Markitect-compatible profile loading, diagnostics, runtime envelopes | Add validation adapter boundary and compatibility fixture catalog. |
|
||||
| Graph/event contract ingress | 3.0 | 4.0 | Graph loading, edge endpoint diagnostics, event model, JSONL event log, export, repair diagnostics | Add richer policy-aware import/export checks. |
|
||||
| Phase domain model | 3.0 | 4.0 | Phases, memory kinds, lifecycle states, actions, explicit path records | Add transition rule profiles and review records. |
|
||||
| Profile execution planning | 3.0 | 4.0 | Adapter plan, capabilities, policy gates, fallback behavior, CLI output, snapshot fixture | Add profile-driven runtime configuration and compatibility validation. |
|
||||
| Lifecycle planning | 3.0 | 4.0 | Transition, retention, refresh, compaction dry-run plans, review-gated local apply | Add profile-driven rule evaluation and full review records. |
|
||||
| Activation planning | 2.5 | 5.0 | Budgeted selection, Markitect-compatible selection output, package request envelope, CLI output | Add graph neighborhoods, event paths, ranking, metadata preservation, metrics. |
|
||||
| Package foundation | 3.0 | 4.0 | Python package, exports, runtime facade, CLI entrypoint, dependency-light tests | Add runtime configuration, service contracts, and adapter conformance. |
|
||||
| Profile contract ingress | 2.5 | 4.0 | Markitect-compatible profile loading, diagnostics, runtime envelopes | Add profile-driven runtime configuration and richer compatibility coverage. |
|
||||
| Graph/event contract ingress | 3.0 | 4.0 | Graph loading, edge endpoint diagnostics, event model, JSONL event log, export, repair diagnostics | Add service-level import/export contracts and adapter conformance. |
|
||||
| Phase domain model | 3.0 | 4.0 | Phases, memory kinds, lifecycle states, actions, explicit path records | Add profile-driven transition rule evaluation and migration semantics. |
|
||||
| Profile execution planning | 3.0 | 4.0 | Adapter plan, capabilities, policy gates, fallback behavior, CLI output, snapshot fixture | Add runtime configuration model and service-readiness diagnostics. |
|
||||
| Lifecycle planning | 3.0 | 4.0 | Transition, retention, refresh, compaction dry-run plans, review-gated local apply | Add profile-driven rule evaluation and service apply contracts. |
|
||||
| Activation planning | 3.8 | 5.0 | Budgeted selection, Markitect-compatible selection output, package request envelope, graph neighborhoods, event paths, ranking, metadata preservation, metrics | Add semantic-index adapters and broader evaluation corpora. |
|
||||
| Local persistence | 3.0 | 4.0 | Versioned local workspace, file-backed graph store, JSONL event log, JSONL audit sink | Add migration/repair utilities and stronger durability semantics. |
|
||||
| Policy and audit | 3.2 | 5.0 | Operation points, policy gateway checks, audit schema, review records, redaction, activation denials | Add external policy adapters and richer audit retention behavior. |
|
||||
| Observability and diagnostics | 2.5 | 4.0 | Planner diagnostics, runtime diagnostics, event log corruption checks, repair diagnostics, policy denial diagnostics | Add health envelopes and adapter status diagnostics. |
|
||||
| Markitect interop | 1.5 | 4.0 | Compatible schema constants and selection handoff | Add package bridge envelopes, optional validation/compiler adapters. |
|
||||
| Kontextual/Infospace interop | 1.0 | 4.0 | Boundaries documented and small derived fixtures | Add delegation envelope design and evaluation fixture reports. |
|
||||
| Testing and evaluation | 3.2 | 4.0 | 36 deterministic tests over planners, adapters, runtime envelopes, CLI, snapshots, file-store round trips, apply denial, review records, audit schema, and policy redaction | Add activation metrics. |
|
||||
| Markitect interop | 3.5 | 4.0 | Compatible contract ingress, optional validation boundary, enriched selection metadata, package request/response envelopes | Add live optional Markitect compiler adapter when available. |
|
||||
| Kontextual/Infospace interop | 1.5 | 4.0 | Boundaries documented, small derived fixtures, activation quality report fixture | Add Kontextual delegation envelopes and broader Infospace evaluation fixture reports. |
|
||||
| Testing and evaluation | 3.7 | 4.0 | 46 deterministic tests over planners, adapters, runtime envelopes, CLI, snapshots, file-store round trips, apply denial, review records, audit schema, policy redaction, Markitect bridge fixtures, retrieval, and activation metrics | Add broader evaluation corpora. |
|
||||
| Service readiness | 0.5 | 4.0 | Runtime ports exist | Add service contracts, config, health checks, adapter conformance tests. |
|
||||
| Developer experience | 3.3 | 4.0 | README quick start, package map, runtime facade docs, CLI examples, local persistence guide | Add troubleshooting and richer examples. |
|
||||
|
||||
@@ -113,6 +113,26 @@ Remaining maturity blockers:
|
||||
- Activation quality metrics.
|
||||
- Service readiness and external adapter conformance.
|
||||
|
||||
## Progress Update - PMEM-WP-0006
|
||||
|
||||
Closed on 2026-05-18:
|
||||
|
||||
- Added deterministic graph-neighborhood retrieval.
|
||||
- Added event-path selection from structured memory paths.
|
||||
- Added candidate scoring and explanation reasons.
|
||||
- Added pluggable token estimator protocol with deterministic word-count
|
||||
default.
|
||||
- Added activation quality report metrics and fixture.
|
||||
- Documented retrieval and evaluation behavior.
|
||||
|
||||
Remaining maturity blockers:
|
||||
|
||||
- Service API contracts.
|
||||
- Runtime configuration model.
|
||||
- Health diagnostics.
|
||||
- External adapter conformance tests.
|
||||
- Kontextual delegation adapter design.
|
||||
|
||||
## Progress Update - PMEM-WP-0004
|
||||
|
||||
Closed on 2026-05-18:
|
||||
@@ -133,6 +153,25 @@ Remaining maturity blockers:
|
||||
- Activation ranking and evaluation metrics.
|
||||
- Service contracts, health diagnostics, and external adapter conformance.
|
||||
|
||||
## Progress Update - PMEM-WP-0005
|
||||
|
||||
Closed on 2026-05-18:
|
||||
|
||||
- Added Markitect package request and response envelopes.
|
||||
- Added local and optional Markitect validation adapter boundary.
|
||||
- Preserved selected item source spans, provenance, confidence, freshness,
|
||||
namespace, policy metadata, and selection reasons through activation.
|
||||
- Added compatibility fixtures and tests for invalid contracts, activation
|
||||
package requests, and opaque package responses.
|
||||
- Documented the Markitect boundary.
|
||||
|
||||
Remaining maturity blockers:
|
||||
|
||||
- Graph-neighborhood retrieval and event-path activation.
|
||||
- Ranking signals and token accounting.
|
||||
- Activation quality metrics and evaluation fixtures.
|
||||
- Service readiness and external adapter conformance.
|
||||
|
||||
## Score Movement Rules
|
||||
|
||||
A dimension should move up only when executable behavior and tests exist.
|
||||
|
||||
@@ -4,7 +4,7 @@ type: workplan
|
||||
title: "Markitect Package Bridge And Contract Interop"
|
||||
domain: markitect
|
||||
repo: phase-memory
|
||||
status: proposed
|
||||
status: finished
|
||||
owner: phase-memory
|
||||
topic_slug: markitect-interop
|
||||
planning_priority: P1
|
||||
@@ -49,11 +49,37 @@ delegation, and stable package request/response envelopes.
|
||||
- Do not require Markitect installation for the default test suite.
|
||||
- Do not turn Markitect into a hidden import dependency.
|
||||
|
||||
## Implementation Update - 2026-05-18
|
||||
|
||||
The Markitect bridge and contract interop slice is complete.
|
||||
|
||||
Implemented outputs:
|
||||
|
||||
- `phase_memory.bridge` defines package request and response envelopes,
|
||||
dependency-light local validation, and an optional Markitect validator
|
||||
adapter shell.
|
||||
- Activation selections now preserve selected-item source spans, provenance,
|
||||
confidence, freshness, namespace, policy metadata, and reason selected.
|
||||
- Runtime package requests use
|
||||
`phase_memory.markitect.package_request.v1` and keep selected node/event,
|
||||
budget, policy, provenance, compiler, and selection data explicit.
|
||||
- Package compiler responses are wrapped as
|
||||
`phase_memory.markitect.package_response.v1` while keeping Markitect internals
|
||||
opaque.
|
||||
- Interop fixtures cover valid profiles/graphs, invalid profiles/graphs,
|
||||
activation package request snapshots, and opaque package responses.
|
||||
- `docs/markitect-interop.md` documents ownership, validation boundaries,
|
||||
package request/response contracts, and fixture catalog.
|
||||
|
||||
Validation:
|
||||
|
||||
- `python3 -m pytest` -> 41 passed.
|
||||
|
||||
## T01 - Define compiler bridge envelopes
|
||||
|
||||
```task
|
||||
id: PMEM-WP-0005-T01
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "e51c4804-4938-443b-b02f-afa7bac0b846"
|
||||
```
|
||||
@@ -77,7 +103,7 @@ Output: typed helpers and JSON fixtures for package requests and responses.
|
||||
|
||||
```task
|
||||
id: PMEM-WP-0005-T02
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "5a1a8777-b971-4f1b-bf65-bd71918eabf6"
|
||||
```
|
||||
@@ -93,7 +119,7 @@ adapter design, and tests around fallback behavior.
|
||||
|
||||
```task
|
||||
id: PMEM-WP-0005-T03
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "012210c6-fc05-467e-9d36-7358c0e11abd"
|
||||
```
|
||||
@@ -116,7 +142,7 @@ to package request.
|
||||
|
||||
```task
|
||||
id: PMEM-WP-0005-T04
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "90ebf80e-1f7e-422c-879c-f4270f1e232e"
|
||||
```
|
||||
@@ -139,7 +165,7 @@ examples.
|
||||
|
||||
```task
|
||||
id: PMEM-WP-0005-T05
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "95b07795-6d8c-4473-a98a-5e48a3e6cca9"
|
||||
```
|
||||
@@ -158,7 +184,7 @@ Output: compatibility test suite.
|
||||
|
||||
```task
|
||||
id: PMEM-WP-0005-T06
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "21e4ffb5-e8dc-4b32-9008-97fb6ffb3726"
|
||||
```
|
||||
@@ -182,3 +208,25 @@ Output: interop architecture note.
|
||||
the core planner APIs.
|
||||
- Source spans, provenance, freshness, confidence, and policy metadata survive
|
||||
from graph ingress to package request.
|
||||
|
||||
## Closure Review - 2026-05-18
|
||||
|
||||
**Outcome:** All tasks completed.
|
||||
|
||||
### Completed
|
||||
|
||||
- PMEM-WP-0005-T01 - Define compiler bridge envelopes
|
||||
- PMEM-WP-0005-T02 - Add Markitect validation adapter boundary
|
||||
- PMEM-WP-0005-T03 - Preserve provenance and source spans through activation
|
||||
- PMEM-WP-0005-T04 - Add interop fixture catalog
|
||||
- PMEM-WP-0005-T05 - Add contract compatibility tests
|
||||
- PMEM-WP-0005-T06 - Document the Markitect boundary
|
||||
|
||||
### Cancelled
|
||||
|
||||
None.
|
||||
|
||||
### Carried Forward
|
||||
|
||||
Graph-neighborhood retrieval, event-path activation, ranking, and activation
|
||||
quality metrics remain in PMEM-WP-0006.
|
||||
|
||||
@@ -4,7 +4,7 @@ type: workplan
|
||||
title: "Retrieval, Activation Quality, And Evaluation"
|
||||
domain: markitect
|
||||
repo: phase-memory
|
||||
status: proposed
|
||||
status: finished
|
||||
owner: phase-memory
|
||||
topic_slug: activation-quality
|
||||
planning_priority: P2
|
||||
@@ -51,11 +51,37 @@ activation-memory intent.
|
||||
- Do not own benchmark dashboards that belong in `infospace-bench`.
|
||||
- Do not optimize for a single application domain.
|
||||
|
||||
## Implementation Update - 2026-05-18
|
||||
|
||||
The retrieval, activation quality, and evaluation slice is complete.
|
||||
|
||||
Implemented outputs:
|
||||
|
||||
- `phase_memory.retrieval` adds deterministic graph-neighborhood retrieval,
|
||||
candidate scoring, event-path selection, pluggable token estimator protocol,
|
||||
neighborhood activation planning, and activation quality reports.
|
||||
- Retrieval supports max hops, edge-kind filters, direction filters, phase
|
||||
filters, and memory kind filters.
|
||||
- Event-path activation selects bounded event windows from structured
|
||||
`MemoryPath` records and treats inactive paths as opt-in.
|
||||
- Ranking signals include explicit priority, graph distance, phase, lifecycle
|
||||
state, confidence, source-backed status, freshness, and policy allowance.
|
||||
- `WordCountTokenEstimator` provides deterministic local budget accounting.
|
||||
- `activation_quality_report` emits selected expected nodes, omitted required
|
||||
nodes, policy-denied required nodes, token budget utilization, stale item
|
||||
count, provenance coverage, source span coverage, and explanation coverage.
|
||||
- `docs/activation-quality.md` documents retrieval, event paths, scoring,
|
||||
estimator boundaries, and evaluation metrics.
|
||||
|
||||
Validation:
|
||||
|
||||
- `python3 -m pytest` -> 46 passed.
|
||||
|
||||
## T01 - Add deterministic graph-neighborhood retrieval
|
||||
|
||||
```task
|
||||
id: PMEM-WP-0006-T01
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "8ed0909f-9e8e-4d49-9312-dca267df29f5"
|
||||
```
|
||||
@@ -75,7 +101,7 @@ Output: retrieval planner and tests for stable graph-neighborhood selection.
|
||||
|
||||
```task
|
||||
id: PMEM-WP-0006-T02
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "5d48ba91-fef0-4d4f-a560-836abed1c527"
|
||||
```
|
||||
@@ -90,7 +116,7 @@ active, abandoned, and merged paths.
|
||||
|
||||
```task
|
||||
id: PMEM-WP-0006-T03
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "0f6340ef-f7bd-408b-b98e-6d90188c5969"
|
||||
```
|
||||
@@ -113,7 +139,7 @@ Output: scoring model, per-item selection reason, and omitted-item reason.
|
||||
|
||||
```task
|
||||
id: PMEM-WP-0006-T04
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "12d83382-a767-45a8-b7cc-8c3f6f3f4c37"
|
||||
```
|
||||
@@ -128,7 +154,7 @@ package budget pressure.
|
||||
|
||||
```task
|
||||
id: PMEM-WP-0006-T05
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "509e9417-3aa7-4899-aed5-20749372fe00"
|
||||
```
|
||||
@@ -148,7 +174,7 @@ Output: fixture set and expected activation plans.
|
||||
|
||||
```task
|
||||
id: PMEM-WP-0006-T06
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "477a896a-8013-42a5-b965-b1ccd2577fec"
|
||||
```
|
||||
@@ -170,7 +196,7 @@ Output: metrics helper and JSON report fixture.
|
||||
|
||||
```task
|
||||
id: PMEM-WP-0006-T07
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "551432e4-2551-49fa-b17b-f762853a6a50"
|
||||
```
|
||||
@@ -187,3 +213,26 @@ Output: activation planning guide and scorecard update.
|
||||
- Every selected and omitted item has a machine-readable reason.
|
||||
- Evaluation fixtures produce deterministic activation quality reports.
|
||||
- Optional semantic indexes remain behind the `SemanticIndex` port.
|
||||
|
||||
## Closure Review - 2026-05-18
|
||||
|
||||
**Outcome:** All tasks completed.
|
||||
|
||||
### Completed
|
||||
|
||||
- PMEM-WP-0006-T01 - Add deterministic graph-neighborhood retrieval
|
||||
- PMEM-WP-0006-T02 - Add event-path activation
|
||||
- PMEM-WP-0006-T03 - Add ranking signals and explanations
|
||||
- PMEM-WP-0006-T04 - Improve token and budget accounting
|
||||
- PMEM-WP-0006-T05 - Add evaluation fixture scenarios
|
||||
- PMEM-WP-0006-T06 - Add maturity metrics for activation quality
|
||||
- PMEM-WP-0006-T07 - Document retrieval and evaluation behavior
|
||||
|
||||
### Cancelled
|
||||
|
||||
None.
|
||||
|
||||
### Carried Forward
|
||||
|
||||
Service contracts, runtime configuration, health diagnostics, and external
|
||||
adapter conformance remain in PMEM-WP-0007.
|
||||
|
||||
Reference in New Issue
Block a user