memory graph and services

This commit is contained in:
2026-05-15 01:35:58 +02:00
parent 8f732fc868
commit 9d31dcf2af
11 changed files with 1665 additions and 9 deletions

View File

@@ -70,11 +70,18 @@ from markitect_tool.memory import (
LocalContextPackageRegistry,
MemoryNamespace,
activate_context_package,
compile_memory_graph_selection_to_context_package,
create_context_package_from_index,
create_context_package_from_manifest,
create_context_package_from_sources,
explain_context_package,
load_memory_graph_file,
load_memory_graph_selection_file,
load_memory_profile_file,
plan_memory_profile,
refresh_context_package,
validate_memory_graph,
validate_memory_profile,
)
from markitect_tool.ops import IncludeError, compose_files, resolve_includes, transform_markdown
from markitect_tool.processor import ProcessorContext, run_fenced_processors
@@ -2012,6 +2019,166 @@ def context_list(root: Path, output_format: str) -> None:
_emit_context_package_list({"count": len(packages), "packages": packages}, output_format)
@main.group("memory")
def memory_group() -> None:
"""Validate memory contracts and compile graph context packages."""
@memory_group.group("blueprint")
def memory_blueprint_group() -> None:
"""Validate and plan memory blueprint/profile contracts."""
@memory_blueprint_group.command("validate")
@click.argument("profile_file", type=click.Path(exists=True, dir_okay=False, path_type=Path))
@click.option(
"--format",
"output_format",
type=click.Choice(["json", "yaml", "text"], case_sensitive=False),
default="text",
show_default=True,
)
def memory_blueprint_validate(profile_file: Path, output_format: str) -> None:
"""Validate a memory profile/blueprint contract."""
try:
profile = load_memory_profile_file(profile_file)
result = validate_memory_profile(profile, path=profile_file)
except ContextPackageError as exc:
raise click.ClickException(str(exc)) from exc
_emit_memory_validation(result.to_dict(), output_format)
if not result.valid:
raise click.ClickException("Memory blueprint validation failed.")
@memory_blueprint_group.command("plan")
@click.argument("profile_file", type=click.Path(exists=True, dir_okay=False, path_type=Path))
@click.option(
"--format",
"output_format",
type=click.Choice(["json", "yaml", "text"], case_sensitive=False),
default="text",
show_default=True,
)
def memory_blueprint_plan(profile_file: Path, output_format: str) -> None:
"""Explain runtime responsibilities implied by a memory profile."""
try:
profile = load_memory_profile_file(profile_file)
result = validate_memory_profile(profile, path=profile_file)
if not result.valid:
_emit_memory_validation(result.to_dict(), output_format)
raise click.ClickException("Memory blueprint validation failed.")
plan = plan_memory_profile(profile)
except ContextPackageError as exc:
raise click.ClickException(str(exc)) from exc
_emit_memory_profile_plan(plan, output_format)
@memory_group.group("graph")
def memory_graph_group() -> None:
"""Validate memory graphs and compile graph selections."""
@memory_graph_group.command("validate")
@click.argument("graph_file", type=click.Path(exists=True, dir_okay=False, path_type=Path))
@click.option(
"--format",
"output_format",
type=click.Choice(["json", "yaml", "text"], case_sensitive=False),
default="text",
show_default=True,
)
def memory_graph_validate(graph_file: Path, output_format: str) -> None:
"""Validate a memory graph contract."""
try:
graph = load_memory_graph_file(graph_file)
result = validate_memory_graph(graph, path=graph_file)
except ContextPackageError as exc:
raise click.ClickException(str(exc)) from exc
_emit_memory_validation(result.to_dict(), output_format)
if not result.valid:
raise click.ClickException("Memory graph validation failed.")
@memory_graph_group.command("pack")
@click.argument("selection_file", type=click.Path(exists=True, dir_okay=False, path_type=Path))
@click.option(
"--graph",
"graph_file",
type=click.Path(exists=True, dir_okay=False, path_type=Path),
help="Override the graph path declared in the selection file.",
)
@click.option(
"--profile",
"profile_file",
type=click.Path(exists=True, dir_okay=False, path_type=Path),
help="Override the profile path declared in the selection file.",
)
@click.option(
"--root",
type=click.Path(exists=True, file_okay=False, path_type=Path),
default=Path("."),
show_default=True,
help="Root used for the local context registry when saving.",
)
@click.option("--output", type=click.Path(dir_okay=False, path_type=Path), help="Write package YAML to this file.")
@click.option("--save", is_flag=True, help="Save the compiled package to the local context registry.")
@click.option(
"--format",
"output_format",
type=click.Choice(["json", "yaml", "text"], case_sensitive=False),
default="text",
show_default=True,
)
def memory_graph_pack(
selection_file: Path,
graph_file: Path | None,
profile_file: Path | None,
root: Path,
output: Path | None,
save: bool,
output_format: str,
) -> None:
"""Compile a memory graph selection into a context package."""
try:
selection = load_memory_graph_selection_file(selection_file)
graph_path = graph_file or _resolve_memory_contract_path(selection_file, selection.graph, "graph")
profile_path = profile_file
if profile_path is None and selection.profile:
profile_path = _resolve_memory_contract_path(selection_file, selection.profile, "profile")
graph = load_memory_graph_file(graph_path)
profile = load_memory_profile_file(profile_path) if profile_path else None
graph_result = validate_memory_graph(graph, path=graph_path)
if not graph_result.valid:
_emit_memory_validation(graph_result.to_dict(), output_format)
raise click.ClickException("Memory graph validation failed.")
if profile:
profile_result = validate_memory_profile(profile, path=profile_path)
if not profile_result.valid:
_emit_memory_validation(profile_result.to_dict(), output_format)
raise click.ClickException("Memory blueprint validation failed.")
package = compile_memory_graph_selection_to_context_package(graph, selection, profile=profile)
except ContextPackageError as exc:
raise click.ClickException(str(exc)) from exc
registry_path = None
if save:
registry_path = LocalContextPackageRegistry(root).save(package)
if output:
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(yaml.safe_dump(package.to_dict(), sort_keys=False), encoding="utf-8")
_emit_context_package(
package.to_dict()
| {
"registry_path": str(registry_path) if registry_path else None,
"output_path": str(output) if output else None,
},
output_format,
)
@main.group()
def workflow() -> None:
"""Inspect, plan, and run declarative Markdown workflows."""
@@ -2800,6 +2967,67 @@ def _emit_context_package_list(data: dict, output_format: str) -> None:
click.echo(f"- {package['id']} {package.get('title', '')}")
def _emit_memory_validation(data: dict, output_format: str) -> None:
if output_format == "json":
click.echo(json.dumps(data, indent=2, ensure_ascii=False))
elif output_format == "yaml":
click.echo(yaml.safe_dump(data, sort_keys=False))
else:
click.echo("valid" if data.get("valid") else "invalid")
click.echo(f"subject: {data.get('subject_kind')} {data.get('subject_id') or ''}".rstrip())
metadata = data.get("metadata", {})
if metadata.get("nodes") is not None:
click.echo(
"graph: "
f"nodes={metadata.get('nodes', 0)} "
f"edges={metadata.get('edges', 0)} "
f"events={metadata.get('events', 0)}"
)
if metadata.get("memory_kinds"):
click.echo("memory_kinds: " + ", ".join(metadata["memory_kinds"]))
for diagnostic in data.get("diagnostics", []):
click.echo(f"! [{diagnostic['severity']}] {diagnostic['code']}: {diagnostic['message']}")
def _emit_memory_profile_plan(data: dict, output_format: str) -> None:
if output_format == "json":
click.echo(json.dumps(data, indent=2, ensure_ascii=False))
elif output_format == "yaml":
click.echo(yaml.safe_dump(data, sort_keys=False))
else:
profile = data.get("profile", {})
click.echo(f"profile: {profile.get('id', '')}")
if profile.get("title"):
click.echo(f"title: {profile['title']}")
click.echo("services_launched_by_markitect_tool: false")
if data.get("activation"):
activation = data["activation"]
click.echo(
"activation_budget: "
f"max_items={activation.get('max_items')} "
f"max_tokens={activation.get('max_tokens')}"
)
click.echo("memory_kinds:")
for plan in data.get("memory_kinds", []):
store = f" store={plan.get('store')}" if plan.get("store") else ""
click.echo(f"- {plan.get('kind')}{store}")
runtime = data.get("runtime_boundary", {})
if runtime:
click.echo("runtime_boundary:")
for owner in ("markitect_tool", "kontextual_engine", "infospace_bench"):
if runtime.get(owner):
click.echo(f"- {owner}: " + "; ".join(runtime[owner]))
def _resolve_memory_contract_path(base_file: Path, raw_path: str | None, label: str) -> Path:
if not raw_path:
raise ContextPackageError(f"Memory graph selection must declare a {label} path or use --{label}.")
candidate = Path(raw_path)
if candidate.is_absolute():
return candidate
return base_file.parent / candidate
def _emit_policy_summary(policy_data: dict) -> None:
click.echo(
"policy: "