From 1f96fec5325288b84ba6ad0a1c7ebda2bcbda50f Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 5 Apr 2026 18:27:56 -0400 Subject: [PATCH 01/12] docs(agentplane): add repo README and execution-plane scope --- README.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..ed1fb89 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# agentplane + +Agentplane is the tenant-side control and execution plane for local-first and hybrid agents. + +This repository is not the local supervisor and it is not the canonical wire-spec repository. Instead, it is the remote control-plane and worker-plane complement to the device-local runtime. + +## What already exists here + +The current repository already contains useful runtime artifact scaffolds and local-state conventions: + +- `schemas/session-artifact.schema.v0.1.json` +- `schemas/promotion-artifact.schema.v0.1.json` +- `schemas/reversal-artifact.schema.v0.1.json` +- `schemas/bundle.schema.patch.json` +- `state/pointers/.keep` +- `.gitignore` rules for local `artifacts/` and machine-local pointer state + +Those files tell us two important things: + +1. Agentplane already assumes evidence-bearing runtime artifacts. +2. Agentplane already assumes machine-local pointer state should not be committed. + +## Repository role + +Agentplane owns the **tenant-side** parts of the first local-hybrid slice: + +- gateway and ingress policy handoff for remote-eligible tasks +- capability resolution from logical capability ID to worker binding +- worker runtime envelopes for remote execution +- promotion and reversal semantics for future side-effecting flows +- tenant-side evidence handoff hooks + +Agentplane does **not** own: + +- the local supervisor runtime (`sociosphere`) +- the canonical deterministic transport and fixtures (`TriTRPC`) +- the shared cross-repo contract canon (`socioprophet-standards-storage`) + +## Planned layout + +- `docs/` — architecture notes, slice definitions, repo map +- `gateway/` — tenant ingress and policy-gated dispatch adapters +- `capability-registry/` — logical capability descriptors and bindings +- `worker-runtime/` — tenant execution wrappers and runtime contracts +- `schemas/` — artifact schemas and patch fragments used by runtime flows + +## Current implementation stance + +The first slice is deliberately narrow: + +- local-first planning and retrieval +- optional tenant execution only after policy approval +- typed capability resolution +- evidence append and replay/cairn materialization +- no public-provider egress by default +- no generic multi-agent prompt soup + +See `docs/local_hybrid_slice_v0.md` for the execution slice and `docs/repository_map.md` for cross-repo boundaries. From 3104100b98cab009bc28cffdd0d5708681e513cb Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 5 Apr 2026 18:34:32 -0400 Subject: [PATCH 02/12] docs(agentplane): define local-hybrid slice v0 --- docs/local_hybrid_slice_v0.md | 98 +++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 docs/local_hybrid_slice_v0.md diff --git a/docs/local_hybrid_slice_v0.md b/docs/local_hybrid_slice_v0.md new file mode 100644 index 0000000..3b93865 --- /dev/null +++ b/docs/local_hybrid_slice_v0.md @@ -0,0 +1,98 @@ +# Local-Hybrid Slice v0 + +## Purpose + +This document freezes the first end-to-end execution slice for Agentplane. + +The slice is intentionally narrow. It exists to prove the architecture, not to implement every capability class at once. + +## Scope + +The first slice is: + +- a local-first request enters the device-local supervisor +- local retrieval and local task planning run first +- policy decides whether any remote execution is permitted +- Agentplane resolves a remote capability when policy allows it +- a tenant worker executes the bound capability +- evidence is appended +- a replay/cairn handle is materialized + +## Seven-method lifecycle + +1. `supervisor.v1.Session/Open` +2. `supervisor.v1.Task/Plan` +3. `policy.v1.Decision/Evaluate` +4. `control.v1.Capability/Resolve` +5. `worker.v1.Capability/Execute` +6. `evidence.v1.Event/Append` +7. `replay.v1.Cairn/Materialize` + +Agentplane owns the tenant-side responsibilities for steps 4 and 5 directly, and may mirror or participate in 3 and 6 where tenant policy and evidence relays are required. + +## What Agentplane already has + +The repo already contains artifact schema scaffolds for: + +- session artifacts +- promotion artifacts +- reversal artifacts +- bundle spec patch fields for runtime behavior + +These are useful because they establish the repo as a runtime artifact plane rather than only a conceptual architecture bucket. + +## What Agentplane must add next + +### Gateway + +The gateway is the tenant ingress for remote-eligible work. It should: + +- accept already-classified and policy-scoped work from the local supervisor +- validate capability binding requests +- reject out-of-policy egress or side-effect requests +- emit tenant-side evidence handoff events + +### Capability registry + +The capability registry maps a logical capability ID to an execution binding. A binding should minimally describe: + +- capability instance ID +- worker endpoint +- supported execution lanes +- timeout and context limits +- side-effect posture +- required credentials or scopes + +### Worker runtime + +The worker runtime wraps the remote execution contract. It should: + +- execute only typed capability payloads +- run with scoped credentials +- record input and output digests +- emit provenance metadata suitable for evidence append + +## Relation to existing schemas + +The existing artifact schemas are not wasted work. They align with the future execution lifecycle as follows: + +- `session-artifact.schema.v0.1.json` supports session-level receipts and replay references +- `promotion-artifact.schema.v0.1.json` supports later promotion/review flows for side-effecting actions +- `reversal-artifact.schema.v0.1.json` supports rollback/reversal for promoted changes +- `bundle.schema.patch.json` already introduces runtime-oriented fields such as `sessionPolicyRef`, `skillRefs`, `memoryNamespace`, `worktreeStrategy`, `rolloutFlags`, `telemetrySink`, and `receiptSchemaVersion` + +## Non-goals for v0 + +- generalized autonomous multi-agent swarms +- unconstrained public-provider model egress +- long-lived secret material inside workers +- untyped prompt-only worker contracts +- cloud-first session authority + +## Immediate follow-on work + +1. Add gateway scaffolding. +2. Add capability-registry scaffolding. +3. Add worker-runtime scaffolding. +4. Add examples that bind a single capability such as `summarize.abstractive.v1`. +5. Align shared schemas and fixtures with `TriTRPC` and `socioprophet-standards-storage`. From 37b8a3c7a271f0746e82ea4408beb42c93a0772b Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 5 Apr 2026 18:36:36 -0400 Subject: [PATCH 03/12] docs(agentplane): add repository map and boundaries --- docs/repository_map.md | 66 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 docs/repository_map.md diff --git a/docs/repository_map.md b/docs/repository_map.md new file mode 100644 index 0000000..ca7c88c --- /dev/null +++ b/docs/repository_map.md @@ -0,0 +1,66 @@ +# Agentplane Repository Map + +## Cross-repo ownership + +### Agentplane + +Tenant-side control and execution responsibilities: + +- gateway and remote ingress +- capability resolution and binding +- tenant worker runtime wrappers +- promotion and reversal runtime artifacts +- tenant-side evidence relay hooks + +### Sociosphere + +Device-local orchestration responsibilities: + +- local supervisor +- local planning, retrieval, and execution precedence +- deterministic multi-repo orchestration + +### TriTRPC + +Deterministic transport and fixture responsibilities: + +- method and envelope canon +- fixture vectors +- verification and repack invariants +- cross-language interoperability surface + +### socioprophet-standards-storage + +Shared contracts and measurement responsibilities: + +- shared schemas +- benchmark definitions +- storage and interface standards +- governance and portability measurements + +## Internal layout for Agentplane + +### `schemas/` +Runtime artifact schemas and patch fragments. + +### `docs/` +Architecture notes and slice definitions. + +### `gateway/` +Tenant ingress for remote-eligible work. + +### `capability-registry/` +Logical capability descriptors and runtime bindings. + +### `worker-runtime/` +Tenant worker execution wrappers and contract adapters. + +## First-slice sequence boundary + +- `supervisor.v1.Session/Open` — local +- `supervisor.v1.Task/Plan` — local +- `policy.v1.Decision/Evaluate` — local first, tenant mirror optional +- `control.v1.Capability/Resolve` — tenant +- `worker.v1.Capability/Execute` — tenant +- `evidence.v1.Event/Append` — shared with local precedence +- `replay.v1.Cairn/Materialize` — local first From 17a90ee44ddea9da697966d31db33b786fd071f6 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 5 Apr 2026 18:38:02 -0400 Subject: [PATCH 04/12] docs(agentplane): seed gateway placeholder --- gateway/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 gateway/README.md diff --git a/gateway/README.md b/gateway/README.md new file mode 100644 index 0000000..3e72d78 --- /dev/null +++ b/gateway/README.md @@ -0,0 +1,12 @@ +# gateway + +Tenant ingress for policy-scoped remote work. + +Initial responsibilities: + +- accept remote-eligible work only after policy approval upstream +- validate capability binding requests +- reject side-effecting or out-of-policy execution attempts +- emit tenant-side evidence relay events + +This directory is a scaffold for the first local-hybrid slice and should stay narrow until the typed execution path is implemented. From 224ac5b61233af9aac13c6e0c68910af6b6a2c9a Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 5 Apr 2026 18:39:04 -0400 Subject: [PATCH 05/12] docs(agentplane): seed capability registry placeholder --- capability-registry/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 capability-registry/README.md diff --git a/capability-registry/README.md b/capability-registry/README.md new file mode 100644 index 0000000..77673f6 --- /dev/null +++ b/capability-registry/README.md @@ -0,0 +1,12 @@ +# capability-registry + +Logical capability descriptors and runtime bindings live here. + +Initial responsibilities: + +- map capability IDs to execution bindings +- record execution-lane constraints +- record timeout and context limits +- record side-effect posture and credential scope requirements + +The first concrete example to support is a narrow capability such as `summarize.abstractive.v1`. From b705507bd9c9ec4544979e55346f3f4ec6052679 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 5 Apr 2026 18:39:32 -0400 Subject: [PATCH 06/12] docs(agentplane): seed worker runtime placeholder --- worker-runtime/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 worker-runtime/README.md diff --git a/worker-runtime/README.md b/worker-runtime/README.md new file mode 100644 index 0000000..25ee1ab --- /dev/null +++ b/worker-runtime/README.md @@ -0,0 +1,12 @@ +# worker-runtime + +Tenant worker execution wrappers and contract adapters live here. + +Initial responsibilities: + +- accept only typed capability payloads +- run with scoped credentials +- record input and output digests +- emit provenance metadata for evidence append + +This directory should remain tightly bounded to the first local-hybrid slice until the typed execution path is verified end to end. From bb9ec70792f1bcf23d783648992f98f8099bdc0e Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 5 Apr 2026 19:17:05 -0400 Subject: [PATCH 07/12] feat(agentplane): add capability binding example for local hybrid slice --- .../examples/summarize.abstractive.v1.json | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 capability-registry/examples/summarize.abstractive.v1.json diff --git a/capability-registry/examples/summarize.abstractive.v1.json b/capability-registry/examples/summarize.abstractive.v1.json new file mode 100644 index 0000000..cc60494 --- /dev/null +++ b/capability-registry/examples/summarize.abstractive.v1.json @@ -0,0 +1,33 @@ +{ + "capabilityId": "summarize.abstractive.v1", + "version": "1.0.0", + "kind": "analysis", + "description": "Deterministic stub binding for abstractive summarization with risk extraction.", + "inputSchemaRef": "org.socioprophet.capabilities.v1.SummarizeInput", + "outputSchemaRef": "org.socioprophet.capabilities.v1.SummarizeOutput", + "execution": { + "supportedLanes": ["local", "tenant"], + "defaultLane": "local", + "requiresGpu": false, + "maxContextBytes": 16384, + "timeoutSeconds": 60 + }, + "trust": { + "egressDefault": "deny", + "sideEffectsDefault": "deny", + "dataLabelsAllowed": ["public", "internal", "tenant_confidential"], + "dataLabelsDenied": ["regulated_export_controlled"] + }, + "policyHooks": { + "preExec": ["policy.v1.Decision/Evaluate"], + "postExec": ["evidence.v1.Event/Append"] + }, + "binding": { + "capabilityInstanceId": "capinst.summarize.abstractive.v1.stub", + "executionLane": "tenant", + "workerEndpoint": "tritrpc://tenant/summarize-01", + "workerContract": "worker.v1.Capability/Execute", + "credentialScope": "task-scoped", + "bindingTtlSeconds": 120 + } +} From d3cc9ac25f6afa67d18ae907d993a4896d5ee3b3 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 5 Apr 2026 19:17:41 -0400 Subject: [PATCH 08/12] feat(agentplane): add capability resolution stub --- capability-registry/resolve_binding_stub.py | 53 +++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 capability-registry/resolve_binding_stub.py diff --git a/capability-registry/resolve_binding_stub.py b/capability-registry/resolve_binding_stub.py new file mode 100644 index 0000000..5fe8b54 --- /dev/null +++ b/capability-registry/resolve_binding_stub.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +"""Minimal capability resolution stub for the first local-hybrid slice. + +This script resolves a logical capability descriptor into a runtime binding. +It intentionally uses only the Python standard library so it can run in a bare +repository checkout. +""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path +from typing import Any + + +def load_descriptor(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + +def resolve_binding(descriptor: dict[str, Any], requested_lane: str | None = None) -> dict[str, Any]: + execution = descriptor.get("execution", {}) + binding = descriptor.get("binding", {}) + supported_lanes = execution.get("supportedLanes", []) + lane = requested_lane or binding.get("executionLane") or execution.get("defaultLane") + if lane not in supported_lanes: + raise ValueError(f"unsupported lane: {lane!r}; supported={supported_lanes!r}") + return { + "resolved": True, + "binding": { + "capabilityInstanceId": binding["capabilityInstanceId"], + "executionLane": lane, + "workerEndpoint": binding["workerEndpoint"], + "workerContract": binding["workerContract"], + "credentialScope": binding["credentialScope"], + "bindingTtlSeconds": binding["bindingTtlSeconds"], + }, + } + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("descriptor", type=Path) + parser.add_argument("--lane", default=None) + args = parser.parse_args() + descriptor = load_descriptor(args.descriptor) + result = resolve_binding(descriptor, requested_lane=args.lane) + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From cfb0fd99ad0feac1cc096ec2854ac292504f6095 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 5 Apr 2026 19:18:12 -0400 Subject: [PATCH 09/12] feat(agentplane): add tenant gateway dispatch stub --- gateway/dispatch_stub.py | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 gateway/dispatch_stub.py diff --git a/gateway/dispatch_stub.py b/gateway/dispatch_stub.py new file mode 100644 index 0000000..a73c2da --- /dev/null +++ b/gateway/dispatch_stub.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +"""Minimal tenant gateway dispatch stub for the first local-hybrid slice.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path +from typing import Any + + +def load_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + +def build_dispatch(task: dict[str, Any], decision: dict[str, Any], binding: dict[str, Any]) -> dict[str, Any]: + if not decision.get("allow", False): + raise ValueError("policy decision denies remote dispatch") + transforms = decision.get("requiredTransformations", []) + payload = { + "taskId": task["taskId"], + "capabilityInstanceId": binding["binding"]["capabilityInstanceId"], + "workerEndpoint": binding["binding"]["workerEndpoint"], + "executionLane": binding["binding"]["executionLane"], + "requiredTransformations": transforms, + "input": task["input"], + } + payload_bytes = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8") + payload["dispatchDigest"] = "sha256:" + hashlib.sha256(payload_bytes).hexdigest() + return payload + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("task", type=Path) + parser.add_argument("decision", type=Path) + parser.add_argument("binding", type=Path) + args = parser.parse_args() + dispatch = build_dispatch(load_json(args.task), load_json(args.decision), load_json(args.binding)) + print(json.dumps(dispatch, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From e1dd77495e721537dc21de22874c155613b232eb Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Sun, 5 Apr 2026 19:19:46 -0400 Subject: [PATCH 10/12] feat(agentplane): add worker runtime execute stub --- worker-runtime/execute_stub.py | 64 ++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 worker-runtime/execute_stub.py diff --git a/worker-runtime/execute_stub.py b/worker-runtime/execute_stub.py new file mode 100644 index 0000000..fb6f902 --- /dev/null +++ b/worker-runtime/execute_stub.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +"""Deterministic worker execution stub for the first local-hybrid slice.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path +from typing import Any + + +def load_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + +def summarize_chunks(chunks: list[dict[str, Any]]) -> str: + texts = [str(chunk.get("text", "")).strip() for chunk in chunks] + joined = " ".join(part for part in texts if part) + compact = " ".join(joined.split()) + return compact[:280] + + +def execute(request: dict[str, Any]) -> dict[str, Any]: + chunks = request.get("input", {}).get("context", {}).get("chunks", []) + summary = summarize_chunks(chunks) + input_digest = "sha256:" + hashlib.sha256( + json.dumps(request, sort_keys=True, separators=(",", ":")).encode("utf-8") + ).hexdigest() + output = { + "summary": summary, + "risks": [ + {"id": "r1", "text": "Replay semantics incomplete."}, + {"id": "r2", "text": "Tenant failover remains unspecified in the stub path."} + ] + } + output_digest = "sha256:" + hashlib.sha256( + json.dumps(output, sort_keys=True, separators=(",", ":")).encode("utf-8") + ).hexdigest() + return { + "taskId": request["taskId"], + "status": "completed", + "output": output, + "provenance": { + "workerId": "worker:summarize-01", + "modelId": "model:deterministic-stub-01", + "toolchain": ["agentplane.worker-runtime.execute_stub"], + "inputDigest": input_digest, + "outputDigest": output_digest + } + } + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("request", type=Path) + args = parser.parse_args() + result = execute(load_json(args.request)) + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 9e10a1cbc784a48b2fa91fc381b11a95e1b052bb Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Mon, 6 Apr 2026 23:44:53 -0400 Subject: [PATCH 11/12] feat(agentplane): add evidence append stub for local hybrid slice --- evidence/append_event_stub.py | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 evidence/append_event_stub.py diff --git a/evidence/append_event_stub.py b/evidence/append_event_stub.py new file mode 100644 index 0000000..7d3d567 --- /dev/null +++ b/evidence/append_event_stub.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +"""Deterministic evidence append stub for the first local-hybrid slice.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path +from typing import Any + + +def load_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + +def canonical_bytes(value: Any) -> bytes: + return json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8") + + +def append_event(payload: dict[str, Any]) -> dict[str, Any]: + event = payload.get("event", payload) + digest = hashlib.sha256(canonical_bytes(event)).hexdigest() + journal_offset = int(digest[:12], 16) + return { + "appended": True, + "journalOffset": journal_offset, + "evidenceDigest": f"sha256:{digest}", + } + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("payload", type=Path) + args = parser.parse_args() + result = append_event(load_json(args.payload)) + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 138f751713ff666353484f410f01feaaae7eaa7c Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Mon, 6 Apr 2026 23:46:23 -0400 Subject: [PATCH 12/12] feat(agentplane): add cairn materialization stub for local hybrid slice --- replay/materialize_cairn_stub.py | 48 ++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 replay/materialize_cairn_stub.py diff --git a/replay/materialize_cairn_stub.py b/replay/materialize_cairn_stub.py new file mode 100644 index 0000000..e0928b1 --- /dev/null +++ b/replay/materialize_cairn_stub.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +"""Deterministic replay/cairn materialization stub for the first local-hybrid slice.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path +from typing import Any + + +def load_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + +def canonical_bytes(value: Any) -> bytes: + return json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8") + + +def materialize(payload: dict[str, Any]) -> dict[str, Any]: + digest = hashlib.sha256(canonical_bytes(payload)).hexdigest() + task_id = payload["taskId"] + journal_offset = payload["journalOffset"] + artifact_id = f"artifact:{digest[:16]}" + return { + "cairnId": f"sha256:{digest}", + "replayHandle": f"cairn://{task_id}/{journal_offset}", + "artifacts": [ + { + "artifactId": artifact_id, + "digest": f"sha256:{hashlib.sha256((task_id + str(journal_offset)).encode('utf-8')).hexdigest()}" + } + ] + } + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("payload", type=Path) + args = parser.parse_args() + result = materialize(load_json(args.payload)) + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())