generated from coulomb/repo-seed
feat(memory): define external adapter boundaries
This commit is contained in:
@@ -46,6 +46,10 @@ with `markitect-tool`.
|
||||
- `MemoryRuntimeService.export_runtime_envelope()` emits a portable runtime
|
||||
envelope containing graph nodes, edges, memory events, and audit traces with
|
||||
operation id, actor, policy decision, and filter metadata.
|
||||
- `MemoryRuntimeAdapterCapability`, `MemorySemanticIndex`,
|
||||
`MemoryAuditPublisher`, and `MemoryRuntimeRegistry` define optional adapter
|
||||
boundaries for vector/semantic indexes, durable audit sinks, remote registries,
|
||||
and sibling memory runtimes.
|
||||
|
||||
## Boundary
|
||||
|
||||
@@ -64,5 +68,24 @@ with `markitect-tool`.
|
||||
- retention, refresh, compaction, review gates, and audit behavior
|
||||
- agent-safe update plans and Markitect-compatible export envelopes
|
||||
|
||||
## External Adapter Boundaries
|
||||
|
||||
External graph databases should implement `MemoryGraphRepository`; they are
|
||||
storage adapters, not alternate memory domain models. Enterprise policy systems
|
||||
should implement `PolicyGateway`; policy decisions remain explicit inputs to
|
||||
runtime operations.
|
||||
|
||||
Optional advanced adapters use small dedicated ports:
|
||||
|
||||
- `MemorySemanticIndex` for vector or hybrid retrieval indexes.
|
||||
- `MemoryAuditPublisher` for durable event sinks, webhooks, or SIEM pipelines.
|
||||
- `MemoryRuntimeRegistry` for remote memory registries and sibling runtimes
|
||||
such as future phase-memory services.
|
||||
|
||||
Adapters advertise `MemoryRuntimeAdapterCapability` with adapter kind,
|
||||
supported operations, optional dependencies, and whether they are deterministic
|
||||
local adapters. Adapter payloads must be serializable dictionaries or core
|
||||
runtime records, so core tests can stay deterministic and dependency-free.
|
||||
|
||||
`infospace-bench` should consume these records and Markitect fixtures to measure
|
||||
retrieval quality, latency, budget pressure, and regression behavior.
|
||||
|
||||
@@ -116,7 +116,11 @@ from .ports import (
|
||||
BlobWriteResult,
|
||||
DirectorySourceConnector,
|
||||
FormatExtractor,
|
||||
MemoryAuditPublisher,
|
||||
MemoryGraphRepository,
|
||||
MemoryRuntimeAdapterCapability,
|
||||
MemoryRuntimeRegistry,
|
||||
MemorySemanticIndex,
|
||||
PolicyGateway,
|
||||
SourceConnector,
|
||||
)
|
||||
@@ -271,6 +275,7 @@ __all__ = [
|
||||
"MetadataSchemaAssignment",
|
||||
"MetadataValidationIssue",
|
||||
"MetadataValueType",
|
||||
"MemoryAuditPublisher",
|
||||
"MemoryCompactionRequest",
|
||||
"MemoryEdgeRecord",
|
||||
"MemoryEventRecord",
|
||||
@@ -292,7 +297,10 @@ __all__ = [
|
||||
"MemoryRetentionRequest",
|
||||
"MemoryRuntimeExportRequest",
|
||||
"MemoryRuntimeExportResult",
|
||||
"MemoryRuntimeAdapterCapability",
|
||||
"MemoryRuntimeRegistry",
|
||||
"MemoryRuntimeService",
|
||||
"MemorySemanticIndex",
|
||||
"MemorySourceSpan",
|
||||
"MemoryUpdatePlan",
|
||||
"MemoryUpdateRequest",
|
||||
|
||||
@@ -9,7 +9,13 @@ from .blob_storage import (
|
||||
digest_storage_key,
|
||||
)
|
||||
from .ingestion import DirectorySourceConnector, FormatExtractor, SourceConnector
|
||||
from .memory import MemoryGraphRepository
|
||||
from .memory import (
|
||||
MemoryAuditPublisher,
|
||||
MemoryGraphRepository,
|
||||
MemoryRuntimeAdapterCapability,
|
||||
MemoryRuntimeRegistry,
|
||||
MemorySemanticIndex,
|
||||
)
|
||||
from .policy import AllowAllPolicyGateway, PolicyGateway
|
||||
from .repositories import AssetRegistryRepository
|
||||
|
||||
@@ -24,7 +30,11 @@ __all__ = [
|
||||
"digest_storage_key",
|
||||
"DirectorySourceConnector",
|
||||
"FormatExtractor",
|
||||
"MemoryAuditPublisher",
|
||||
"MemoryGraphRepository",
|
||||
"MemoryRuntimeAdapterCapability",
|
||||
"MemoryRuntimeRegistry",
|
||||
"MemorySemanticIndex",
|
||||
"PolicyGateway",
|
||||
"SourceConnector",
|
||||
]
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Protocol
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Protocol
|
||||
|
||||
from kontextual_engine.core import (
|
||||
AuditEvent,
|
||||
@@ -14,6 +15,30 @@ from kontextual_engine.core import (
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MemoryRuntimeAdapterCapability:
|
||||
adapter_name: str
|
||||
adapter_kind: str
|
||||
supported_operations: tuple[str, ...] = ()
|
||||
required_dependencies: tuple[str, ...] = ()
|
||||
deterministic_local: bool = False
|
||||
metadata: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
object.__setattr__(self, "supported_operations", tuple(self.supported_operations))
|
||||
object.__setattr__(self, "required_dependencies", tuple(self.required_dependencies))
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"adapter_name": self.adapter_name,
|
||||
"adapter_kind": self.adapter_kind,
|
||||
"supported_operations": list(self.supported_operations),
|
||||
"required_dependencies": list(self.required_dependencies),
|
||||
"deterministic_local": self.deterministic_local,
|
||||
"metadata": dict(self.metadata),
|
||||
}
|
||||
|
||||
|
||||
class MemoryGraphRepository(Protocol):
|
||||
def save_memory_profile(self, profile: MemoryProfileRecord) -> MemoryProfileRecord: ...
|
||||
def get_memory_profile(self, profile_id: str) -> MemoryProfileRecord: ...
|
||||
@@ -56,3 +81,38 @@ class MemoryGraphRepository(Protocol):
|
||||
correlation_id: str | None = None,
|
||||
operation: str | None = None,
|
||||
) -> list[AuditEvent]: ...
|
||||
|
||||
|
||||
class MemorySemanticIndex(Protocol):
|
||||
name: str
|
||||
|
||||
def capabilities(self) -> MemoryRuntimeAdapterCapability: ...
|
||||
|
||||
def upsert_memory_nodes(self, nodes: list[MemoryNodeRecord]) -> dict[str, Any]: ...
|
||||
|
||||
def query_memory_nodes(
|
||||
self,
|
||||
*,
|
||||
graph_id: str,
|
||||
query: str,
|
||||
limit: int = 10,
|
||||
filters: dict[str, Any] | None = None,
|
||||
) -> list[dict[str, Any]]: ...
|
||||
|
||||
|
||||
class MemoryAuditPublisher(Protocol):
|
||||
name: str
|
||||
|
||||
def capabilities(self) -> MemoryRuntimeAdapterCapability: ...
|
||||
|
||||
def publish_memory_audit_event(self, event: AuditEvent) -> dict[str, Any]: ...
|
||||
|
||||
|
||||
class MemoryRuntimeRegistry(Protocol):
|
||||
name: str
|
||||
|
||||
def capabilities(self) -> MemoryRuntimeAdapterCapability: ...
|
||||
|
||||
def publish_runtime_envelope(self, envelope: dict[str, Any]) -> dict[str, Any]: ...
|
||||
|
||||
def fetch_runtime_envelope(self, reference: str) -> dict[str, Any]: ...
|
||||
|
||||
140
tests/test_memory_runtime_adapter_boundaries.py
Normal file
140
tests/test_memory_runtime_adapter_boundaries.py
Normal file
@@ -0,0 +1,140 @@
|
||||
from kontextual_engine import (
|
||||
Actor,
|
||||
ActorType,
|
||||
AuditEvent,
|
||||
AuditOutcome,
|
||||
MemoryAuditPublisher,
|
||||
MemoryRuntimeAdapterCapability,
|
||||
MemoryRuntimeRegistry,
|
||||
MemorySemanticIndex,
|
||||
OperationContext,
|
||||
)
|
||||
|
||||
|
||||
def test_memory_runtime_adapter_capability_serializes_dependency_boundary() -> None:
|
||||
capability = MemoryRuntimeAdapterCapability(
|
||||
adapter_name="local-semantic-index",
|
||||
adapter_kind="semantic_index",
|
||||
supported_operations=("upsert_memory_nodes", "query_memory_nodes"),
|
||||
required_dependencies=("numpy",),
|
||||
deterministic_local=True,
|
||||
metadata={"ownership": "optional-adapter"},
|
||||
)
|
||||
|
||||
assert capability.to_dict() == {
|
||||
"adapter_name": "local-semantic-index",
|
||||
"adapter_kind": "semantic_index",
|
||||
"supported_operations": ["upsert_memory_nodes", "query_memory_nodes"],
|
||||
"required_dependencies": ["numpy"],
|
||||
"deterministic_local": True,
|
||||
"metadata": {"ownership": "optional-adapter"},
|
||||
}
|
||||
|
||||
|
||||
def test_optional_memory_adapter_ports_exchange_serializable_payloads() -> None:
|
||||
semantic_index: MemorySemanticIndex = FakeSemanticIndex()
|
||||
audit_publisher: MemoryAuditPublisher = CapturingAuditPublisher()
|
||||
registry: MemoryRuntimeRegistry = InMemoryRuntimeRegistry()
|
||||
context = OperationContext.create(
|
||||
Actor.create(ActorType.AI_AGENT, actor_id="agent-adapter-test"),
|
||||
correlation_id="corr-adapter-boundary",
|
||||
)
|
||||
audit_event = AuditEvent.from_context(
|
||||
"memory.runtime.export",
|
||||
"memory-graph:memgraph-test",
|
||||
AuditOutcome.SUCCESS,
|
||||
context,
|
||||
details={"operation_id": "memexport-test"},
|
||||
)
|
||||
|
||||
upsert_result = semantic_index.upsert_memory_nodes([])
|
||||
query_result = semantic_index.query_memory_nodes(graph_id="memgraph-test", query="contract boundary")
|
||||
audit_result = audit_publisher.publish_memory_audit_event(audit_event)
|
||||
registry_result = registry.publish_runtime_envelope(
|
||||
{
|
||||
"schema_version": "kontextual.memory.runtime-export.v1",
|
||||
"graph_id": "memgraph-test",
|
||||
"nodes": [],
|
||||
"audit_events": [audit_event.to_dict()],
|
||||
}
|
||||
)
|
||||
fetched = registry.fetch_runtime_envelope(registry_result["reference"])
|
||||
|
||||
assert semantic_index.capabilities().adapter_kind == "semantic_index"
|
||||
assert upsert_result == {"upserted": 0}
|
||||
assert query_result[0]["graph_id"] == "memgraph-test"
|
||||
assert audit_result["event_id"] == audit_event.event_id
|
||||
assert registry_result["reference"] == "memory-registry://memgraph-test"
|
||||
assert fetched["audit_events"][0]["operation"] == "memory.runtime.export"
|
||||
|
||||
|
||||
class FakeSemanticIndex:
|
||||
name = "fake-semantic-index"
|
||||
|
||||
def capabilities(self) -> MemoryRuntimeAdapterCapability:
|
||||
return MemoryRuntimeAdapterCapability(
|
||||
adapter_name=self.name,
|
||||
adapter_kind="semantic_index",
|
||||
supported_operations=("upsert_memory_nodes", "query_memory_nodes"),
|
||||
deterministic_local=True,
|
||||
)
|
||||
|
||||
def upsert_memory_nodes(self, nodes: list) -> dict:
|
||||
return {"upserted": len(nodes)}
|
||||
|
||||
def query_memory_nodes(
|
||||
self,
|
||||
*,
|
||||
graph_id: str,
|
||||
query: str,
|
||||
limit: int = 10,
|
||||
filters: dict | None = None,
|
||||
) -> list[dict]:
|
||||
return [
|
||||
{
|
||||
"graph_id": graph_id,
|
||||
"node_id": "memnode-test",
|
||||
"score": 1.0,
|
||||
"query": query,
|
||||
"filters": filters or {},
|
||||
"limit": limit,
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
class CapturingAuditPublisher:
|
||||
name = "capturing-audit-publisher"
|
||||
|
||||
def capabilities(self) -> MemoryRuntimeAdapterCapability:
|
||||
return MemoryRuntimeAdapterCapability(
|
||||
adapter_name=self.name,
|
||||
adapter_kind="audit_sink",
|
||||
supported_operations=("publish_memory_audit_event",),
|
||||
deterministic_local=True,
|
||||
)
|
||||
|
||||
def publish_memory_audit_event(self, event: AuditEvent) -> dict:
|
||||
return {"published": True, "event_id": event.event_id, "operation": event.operation}
|
||||
|
||||
|
||||
class InMemoryRuntimeRegistry:
|
||||
name = "in-memory-runtime-registry"
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.envelopes: dict[str, dict] = {}
|
||||
|
||||
def capabilities(self) -> MemoryRuntimeAdapterCapability:
|
||||
return MemoryRuntimeAdapterCapability(
|
||||
adapter_name=self.name,
|
||||
adapter_kind="runtime_registry",
|
||||
supported_operations=("publish_runtime_envelope", "fetch_runtime_envelope"),
|
||||
deterministic_local=True,
|
||||
)
|
||||
|
||||
def publish_runtime_envelope(self, envelope: dict) -> dict:
|
||||
reference = f"memory-registry://{envelope['graph_id']}"
|
||||
self.envelopes[reference] = dict(envelope)
|
||||
return {"reference": reference, "graph_id": envelope["graph_id"]}
|
||||
|
||||
def fetch_runtime_envelope(self, reference: str) -> dict:
|
||||
return self.envelopes[reference]
|
||||
@@ -4,7 +4,7 @@ type: workplan
|
||||
title: "Agentic Memory Graph Runtime And Operational State"
|
||||
domain: markitect
|
||||
repo: kontextual-engine
|
||||
status: active
|
||||
status: completed
|
||||
owner: codex
|
||||
topic_slug: markitect
|
||||
planning_priority: medium
|
||||
@@ -104,6 +104,11 @@ and operation. Runtime export envelopes include graph nodes, edges, memory
|
||||
events, audit traces, operation id, actor metadata, policy decisions, and
|
||||
filter metadata for portable inspection or handoff.
|
||||
|
||||
The external adapter boundary slice is implemented. Optional semantic indexes,
|
||||
audit sinks, and runtime registries are defined as ports with capability
|
||||
metadata, while graph databases continue to implement `MemoryGraphRepository`
|
||||
and enterprise policy systems continue to implement `PolicyGateway`.
|
||||
|
||||
## P17.1 - Import and map Markitect memory contracts
|
||||
|
||||
```task
|
||||
@@ -242,7 +247,7 @@ Output: audit model, observability events, export fixtures, and tests.
|
||||
|
||||
```task
|
||||
id: KONT-WP-0017-T007
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "c3cf79d8-389c-4583-a02d-303d0ce75cc1"
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user