feat(memory): add agent safe APIs

This commit is contained in:
2026-05-15 09:51:18 +02:00
parent 153427ae99
commit a2f924df15
6 changed files with 709 additions and 1 deletions

View File

@@ -8,10 +8,13 @@ from kontextual_engine import (
LifecycleState,
MemoryCompactionRequest,
MemoryGraphImportResult,
MemoryNodeUpdateInstruction,
MemoryPackageExportRequest,
MemoryRefreshRequest,
MemoryRetentionRequest,
MemoryQueryRequest,
MemoryRuntimeService,
MemoryUpdateRequest,
OperationContext,
PolicyDecision,
ValidationError,
@@ -197,6 +200,89 @@ def test_memory_compaction_creates_summary_preserves_spans_and_retires_sources()
assert {node.lifecycle for node in source_nodes} == {LifecycleState.RETIRED}
def test_agent_safe_memory_update_plans_dry_run_and_requires_review_before_write() -> None:
repo = InMemoryMemoryGraphRepository()
service = MemoryRuntimeService(repo)
summary = service.import_markitect_graph(_graph_contract())
context = operation_context()
plan = service.plan_memory_update(
MemoryUpdateRequest(
graph_id=summary.graph_id,
instructions=(
MemoryNodeUpdateInstruction(
contract_node_id="claim.agent-safe-update",
kind="claim",
text="Agent-safe update is planned before durable write.",
source_spans=(
{
"path": "docs/memory-agent-safe.md",
"unit_kind": "section",
"selector": "sections[heading=Agent safe]",
"engine": "selector",
},
),
policy={"labels": ["public"]},
),
),
require_review=True,
reason="capture implementation decision",
),
context,
)
pending = service.apply_memory_update(plan, context)
assert plan.success is True
assert plan.dry_run is True
assert plan.review_required is True
assert plan.planned_updates[0].action == "create_node"
assert plan.planned_updates[0].source_explanation[0]["path"] == "docs/memory-agent-safe.md"
assert pending.success is False
assert pending.review_required is True
assert repo.list_memory_nodes(graph_id=summary.graph_id, kind="claim") == []
approved = service.apply_memory_update(plan, context, review_decision="approved")
assert approved.success is True
assert approved.appended_events[0].kind == "updated"
assert repo.list_memory_nodes(graph_id=summary.graph_id, kind="claim")[0].contract_node_id == (
"claim.agent-safe-update"
)
def test_memory_package_export_emits_markitect_context_package_inputs_without_denied_content() -> None:
repo = InMemoryMemoryGraphRepository()
service = MemoryRuntimeService(repo, policy_gateway=DenyInternalMemoryPolicy())
graph = _graph_contract()
graph["nodes"].append(
{
"id": "claim.internal-secret",
"kind": "claim",
"text": "secret memory text must not leak",
"policy": {"labels": ["internal"]},
}
)
summary = service.import_markitect_graph(graph)
export = service.export_context_package_inputs(
MemoryPackageExportRequest(
query=MemoryQueryRequest(graph_id=summary.graph_id, text_contains="memory"),
title="Memory Package Input",
intent="Export allowed runtime memories for Markitect packaging.",
namespace={"project": "kontextual-engine"},
budget={"max_items": 3},
),
operation_context(),
)
assert export.success is True
assert export.package_input["schema_version"] == "markitect.context-package.input.v1"
assert export.package_input["retrieval_recipes"][0]["kind"] == "memory-runtime-query"
assert export.package_input["items"][0]["source"]["path"] == "workplans/MKTT-WP-0016.md"
assert export.package_input["metadata"]["permission_filtered_count"] == 1
assert "secret memory text must not leak" not in str(export.to_dict())
assert export.audit_event is not None
assert export.audit_event.operation == "memory.package.export"
def test_memory_runtime_service_rejects_invalid_edge_contracts() -> None:
repo = InMemoryMemoryGraphRepository()
service = MemoryRuntimeService(repo)