Repository object · implementation

Cli

Accepted implementation in the public catalog.

Source path
src/epistemedia/cli.py
Media type
text/x-python
Object ID
em:implementation:sha256:f9ff59ed09843a962929e421fcbd553563cb81f0a1d03f490cdfa314d284ae4b
Content digest
24c4bbdface6cbd80f19e0a21924240459c3855885885dc3ba75dd8d353cdd15

Source content

from __future__ import annotations

import argparse

import datetime as dt

import http.server

import json

import os

import socketserver

import sys

import urllib.error

import urllib.parse

import urllib.request

from pathlib import Path

from typing import Any

from .case_library import load_featured_library

from .core import (

DEFAULT_API_URL,

DEFAULT_BASE_URL,

DEFAULT_MCP_URL,

VERSION,

PublicCatalog,

audit_public,

build_public,

discover_root,

envelope,

validate_repository,

)

from .featured import FEATURE_VIEWS

from .mission import load_mission

from .open_dockets import prepare_submission

from .research_kit import (

case_research_brief,

proposal_template,

protocol_document,

validate_proposal,

)

from .server import Gateway, MCPRequestError

def utc_now() -> str:

return (

dt.datetime.now(dt.UTC)

.replace(microsecond=0)

.isoformat()

.replace("+00:00", "Z")

)

def parser() -> argparse.ArgumentParser:

root = argparse.ArgumentParser(prog="epistemedia", description="Knowledge that can show its work.")

root.add_argument("--version", action="version", version=f"epistemedia {VERSION}")

root.add_argument("--root", type=Path, help="Repository root; inferred for local commands")

sub = root.add_subparsers(dest="command", required=True)

sub.add_parser("orient", help="Print the bounded agent orientation and current repository state")

sub.add_parser("validate", help="Validate accepted repository inputs")

build = sub.add_parser("build", help="Compile all public human and agent interfaces")

build.add_argument("--output", type=Path, default=Path("generated/public"))

build.add_argument("--base-url", default=DEFAULT_BASE_URL)

build.add_argument("--api-url", default=DEFAULT_API_URL)

build.add_argument("--mcp-url", default=DEFAULT_MCP_URL)

audit = sub.add_parser("audit", help="Audit a compiled PublicProjection")

audit.add_argument("--public", type=Path, default=Path("generated/public"))

serve = sub.add_parser("serve", help="Serve compiled static output")

serve.add_argument("--public", type=Path, default=Path("generated/public"))

serve.add_argument("--host", default="127.0.0.1")

serve.add_argument("--port", type=int, default=8000)

api = sub.add_parser("api-serve", help="Serve API and MCP through an ASGI server")

api.add_argument("--host", default="127.0.0.1")

api.add_argument("--port", type=int, default=8080)

search = sub.add_parser("search", help="Search a local realm or the public remote API")

search.add_argument("query")

search.add_argument("--limit", type=int, default=20)

search.add_argument("--remote", action="store_true")

search.add_argument("--api", default=DEFAULT_API_URL)

get = sub.add_parser("get", help="Get an exact object")

get.add_argument("id")

get.add_argument("--remote", action="store_true")

get.add_argument("--api", default=DEFAULT_API_URL)

mission = sub.add_parser("mission", help="Get the versioned public mission")

mission.add_argument("--remote", action="store_true")

mission.add_argument("--api", default=DEFAULT_API_URL)

project = sub.add_parser("project", help="Compile or retrieve a topic projection")

project.add_argument("slug")

project.add_argument("--lens", default="encyclopedia")

project.add_argument("--remote", action="store_true")

project.add_argument("--api", default=DEFAULT_API_URL)

dossier = sub.add_parser("dossier", help="Get an accepted How We Know dossier")

dossier.add_argument("slug")

dossier.add_argument("--policy", choices=FEATURE_VIEWS, default="encyclopedia")

dossier.add_argument("--remote", action="store_true")

dossier.add_argument("--api", default=DEFAULT_API_URL)

open_dockets = sub.add_parser("open-dockets", help="List or get reviewed open dockets")

open_docket_sub = open_dockets.add_subparsers(dest="open_docket_command", required=True)

open_docket_list = open_docket_sub.add_parser("list", help="List reviewed open dockets")

open_docket_list.add_argument("--remote", action="store_true")

open_docket_list.add_argument("--api", default=DEFAULT_API_URL)

open_docket_get = open_docket_sub.add_parser("get", help="Get one reviewed open docket")

open_docket_get.add_argument("slug")

open_docket_get.add_argument("--remote", action="store_true")

open_docket_get.add_argument("--api", default=DEFAULT_API_URL)

research = sub.add_parser("research", help="Prepare and validate non-admitting research proposals")

research_sub = research.add_subparsers(dest="research_command", required=True)

research_sub.add_parser("protocol", help="Print the public agent research protocol")

research_sub.add_parser("submission-guide", help="Print the autonomous GitHub submission guide")

prepare = research_sub.add_parser("prepare", help="Create a deterministic draft proposal scaffold")

prepare.add_argument("--question")

prepare.add_argument("--case", dest="case_slug")

prepare.add_argument("--cutoff", default="YYYY-MM-DD")

prepare.add_argument("--output", type=Path)

complete_research = research_sub.add_parser(

"complete", help="Stamp completion time and fail-closed validate one proposal JSON file"

)

complete_research.add_argument("bundle", type=Path)

validate_research = research_sub.add_parser("validate", help="Fail-closed validation of one proposal JSON file")

validate_research.add_argument("bundle", type=Path)

submit_research = research_sub.add_parser(

"submit", help="Prepare a deterministic GitHub draft-PR submission directory"

)

submit_research.add_argument("bundle", type=Path)

submit_research.add_argument("--trace", required=True, type=Path)

submit_research.add_argument("--agent-id", required=True)

submit_research.add_argument("--model-family", required=True)

submit_research.add_argument("--run-id", required=True)

submit_research.add_argument("--prompt-sha256", required=True)

repo = sub.add_parser("repo", help="Repository-native agent operations")

repo_sub = repo.add_subparsers(dest="repo_command", required=True)

repo_sub.add_parser("next", help="Show the best currently discoverable task contracts")

claim = repo_sub.add_parser("claim", help="Create an append-only local task-claim proposal")

claim.add_argument("task_id")

claim.add_argument("--agent", required=True)

receipt = repo_sub.add_parser("receipt", help="Record a local append-only run receipt proposal")

receipt.add_argument("task_id")

receipt.add_argument("--run", required=True)

receipt.add_argument("--command", dest="run_command", required=True)

mcp = sub.add_parser("mcp", help="MCP utilities")

mcp_sub = mcp.add_subparsers(dest="mcp_command", required=True)

mcp_sub.add_parser("serve", help="Run a newline-delimited stdio MCP adapter")

mcp_sub.add_parser("discover", help="Print server discovery metadata")

return root

def resolve_root(args: argparse.Namespace, required: bool = True) -> Path | None:

if args.root:

return args.root.resolve()

try:

return discover_root()

except FileNotFoundError:

if required:

raise

return None

def print_json(value: Any) -> None:

print(json.dumps(value, indent=2, sort_keys=True, ensure_ascii=False))

def remote_get(api: str, path: str, query: dict[str, Any] | None = None) -> Any:

url = api.rstrip("/") + "/" + path.lstrip("/")

if query:

url += "?" + urllib.parse.urlencode(query)

request = urllib.request.Request(url, headers={"accept": "application/json", "user-agent": f"epistemedia-cli/{VERSION}"})

try:

with urllib.request.urlopen(request, timeout=30) as response:

return json.load(response)

except urllib.error.HTTPError as exc:

detail = exc.read().decode("utf-8", errors="replace")

raise SystemExit(f"remote API returned {exc.code}: {detail}") from exc

except urllib.error.URLError as exc:

raise SystemExit(f"remote API unavailable: {exc.reason}") from exc

def orient(root: Path) -> int:

catalog = PublicCatalog.build(root)

tasks = [obj for obj in catalog.objects if obj.kind == "task"]

print("Epistemedia agent orientation")

print("============================")

print(f"Repository: {root}")

print(f"Accepted commit: {catalog.commit}")

print(f"Public catalog: {catalog.catalog_id}")

print(f"Evidence frontier: {catalog.frontier}")

print(f"Public objects: {len(catalog.objects)}")

print(f"Topics: {len(catalog.topics)}")

print(f"Task contracts visible: {len(tasks)}")

print()

print("Read order: AGENTS.md → selected task contract → execution plan → schemas/policies/tests.")

print("Rule: propose changes through Git; do not become the source of truth or your own approver.")

return 0

def next_tasks(root: Path) -> int:

catalog = PublicCatalog.build(root)

tasks = [obj for obj in catalog.objects if obj.kind == "task"]

if not tasks:

print("No public task contracts are currently discoverable.")

return 0

print_json({

"catalog_id": catalog.catalog_id,

"frontier": catalog.frontier,

"tasks": [obj.as_dict(include_text=False) for obj in tasks[:20]],

"instruction": "Read the immutable task contract and AGENTS.md before claiming work.",

})

return 0

def append_local_proposal(root: Path, kind: str, payload: dict[str, Any]) -> Path:

from .core import digest, utc_now

timestamp = utc_now()

record = {

"schema": f"https://epistemedia.com/schemas/{kind}-v1.json",

"kind": kind,

"recorded_at": timestamp,

**payload,

}

record["id"] = f"em:{kind}:sha256:{digest(record)}"

safe_time = timestamp.replace(":", "").replace("-", "")

path = root / "runs" / "proposals" / f"{safe_time}-{record['id'].split(':')[-1][:12]}.json"

path.parent.mkdir(parents=True, exist_ok=True)

path.write_text(json.dumps(record, indent=2, sort_keys=True) + "\n")

return path

def stdio_mcp(root: Path) -> int:

gateway = Gateway(root)

for line in sys.stdin:

line = line.strip()

if not line:

continue

request: Any = None

try:

request = json.loads(line)

if isinstance(request, dict) and "id" not in request:

if request.get("method") == "notifications/cancelled":

gateway.mcp_method(

"notifications/cancelled", request.get("params") or {}

)

continue

request_id, method, params = gateway.validate_mcp_request(request)

result = gateway.mcp_method(method, params)

if isinstance(result, dict):

result = gateway.decorate_mcp_result(result)

print(json.dumps({"jsonrpc": "2.0", "id": request_id, "result": result}, sort_keys=True), flush=True)

except json.JSONDecodeError:

print(json.dumps(gateway.rpc_error(None, -32700, "Parse error"), sort_keys=True), flush=True)

except MCPRequestError as exc:

request_id = request.get("id") if isinstance(request, dict) else None

print(

json.dumps(

gateway.rpc_error(request_id, exc.code, str(exc), exc.data),

sort_keys=True,

),

flush=True,

)

except KeyError as exc:

request_id = request.get("id") if isinstance(request, dict) else None

print(

json.dumps(

gateway.rpc_error(

request_id,

-32004,

"Not found",

{"id": exc.args[0] if exc.args else ""},

),

sort_keys=True,

),

flush=True,

)

except ValueError as exc:

request_id = request.get("id") if isinstance(request, dict) else None

print(

json.dumps(

gateway.rpc_error(request_id, -32602, str(exc)),

sort_keys=True,

),

flush=True,

)

except Exception:

request_id = request.get("id") if isinstance(request, dict) else None

print(

json.dumps(

gateway.rpc_error(request_id, -32603, "Internal error"),

sort_keys=True,

),

flush=True,

)

return 0

def main(argv: list[str] | None = None) -> int:

args = parser().parse_args(argv)

command = args.command

if command == "search" and args.remote:

print_json(remote_get(args.api, "search", {"q": args.query, "limit": args.limit}))

return 0

if command == "get" and args.remote:

print_json(remote_get(args.api, "objects/" + urllib.parse.quote(args.id, safe="")))

return 0

if command == "mission" and args.remote:

print_json(remote_get(args.api, "mission"))

return 0

if command == "project" and args.remote:

print_json(remote_get(args.api, "topics/" + urllib.parse.quote(args.slug, safe=""), {"lens": args.lens}))

return 0

if command == "dossier" and args.remote:

print_json(

remote_get(

args.api,

"dossiers/" + urllib.parse.quote(args.slug, safe=""),

{"policy": args.policy},

)

)

return 0

if command == "open-dockets" and args.remote:

path = "open-dockets"

if args.open_docket_command == "get":

path += "/" + urllib.parse.quote(args.slug, safe="")

print_json(remote_get(args.api, path))

return 0

root = resolve_root(args)

assert root is not None

if command == "orient":

return orient(root)

if command == "validate":

errors = validate_repository(root)

if errors:

print_json({"valid": False, "errors": errors})

return 1

catalog = PublicCatalog.build(root)

print_json({"valid": True, "catalog_id": catalog.catalog_id, "frontier": catalog.frontier, "objects": len(catalog.objects), "topics": len(catalog.topics)})

return 0

if command == "build":

output = args.output if args.output.is_absolute() else root / args.output

manifest = build_public(root, output, base_url=args.base_url, api_url=args.api_url, mcp_url=args.mcp_url)

print_json(manifest)

return 0

if command == "audit":

public = args.public if args.public.is_absolute() else root / args.public

findings = audit_public(root, public)

print_json({"ok": not findings, "findings": findings})

return 1 if findings else 0

if command == "serve":

public = args.public if args.public.is_absolute() else root / args.public

if not (public / "index.html").exists():

build_public(root, public)

os.chdir(public)

handler = http.server.SimpleHTTPRequestHandler

with socketserver.ThreadingTCPServer((args.host, args.port), handler) as server:

print(f"Serving {public} at http://{args.host}:{args.port}")

server.serve_forever()

return 0

if command == "api-serve":

try:

import uvicorn # type: ignore

except ImportError as exc:

raise SystemExit("Install the server extra: pip install 'epistemedia[server]'") from exc

os.environ["EPISTEMEDIA_ROOT"] = str(root)

uvicorn.run("epistemedia.server:app", host=args.host, port=args.port, reload=False)

return 0

if command == "search":

catalog = PublicCatalog.build(root)

print_json({"catalog_id": catalog.catalog_id, "frontier": catalog.frontier, "query": args.query, "results": catalog.search(args.query, args.limit)})

return 0

if command == "get":

catalog = PublicCatalog.build(root)

obj = catalog.object_map().get(args.id)

if not obj:

raise SystemExit(f"unknown object: {args.id}")

print_json({"catalog_id": catalog.catalog_id, "frontier": catalog.frontier, "data": obj.as_dict()})

return 0

if command == "mission":

catalog = PublicCatalog.build(root)

print_json(envelope(catalog, load_mission(root)))

return 0

if command == "project":

from .core import topic_projection

catalog = PublicCatalog.build(root)

topic = catalog.topic_map().get(args.slug)

if not topic:

raise SystemExit(f"unknown topic: {args.slug}")

print_json(topic_projection(catalog, topic, args.lens, DEFAULT_BASE_URL))

return 0

if command == "dossier":

catalog = PublicCatalog.build(root)

library = load_featured_library(root)

try:

dossier = library.get(args.slug) if library is not None else None

except KeyError:

dossier = None

if dossier is None:

raise SystemExit(f"unknown dossier: {args.slug}")

print_json(envelope(catalog, dossier.projection(args.policy)))

return 0

if command == "open-dockets":

from .open_dockets import load_open_dockets

catalog = PublicCatalog.build(root)

dockets, errors = load_open_dockets(root)

if errors:

raise SystemExit("; ".join(errors))

if args.open_docket_command == "list":

print_json(

envelope(

catalog,

[docket.projection(DEFAULT_BASE_URL) for docket in dockets],

)

)

return 0

docket = next((item for item in dockets if item.slug == args.slug), None)

if docket is None:

raise SystemExit(f"unknown open docket: {args.slug}")

print_json(envelope(catalog, docket.projection(DEFAULT_BASE_URL)))

return 0

if command == "research":

if args.research_command == "protocol":

print_json(protocol_document(DEFAULT_BASE_URL))

return 0

if args.research_command == "submission-guide":

from .open_dockets import submission_guide

print_json(submission_guide(DEFAULT_BASE_URL))

return 0

if args.research_command == "prepare":

question = args.question

brief = None

if args.case_slug:

library = load_featured_library(root)

try:

selected = library.get(args.case_slug) if library is not None else None

except KeyError:

selected = None

if selected is None:

raise SystemExit(f"unknown dossier: {args.case_slug}")

projection = selected.projection(selected.default_view)

brief = case_research_brief(projection, DEFAULT_BASE_URL)

question = question or projection["question"]

if not isinstance(question, str) or not question.strip():

raise SystemExit("research prepare requires --question or --case")

proposal = proposal_template(

question.strip(), cutoff=args.cutoff, case_slug=args.case_slug

)

proposal["runtime"]["started_at"] = utc_now()

output = {"proposal": proposal, "case_brief": brief}

if args.output:

destination = args.output if args.output.is_absolute() else root / args.output

destination.parent.mkdir(parents=True, exist_ok=True)

destination.write_text(

json.dumps(proposal, indent=2, sort_keys=True, ensure_ascii=False) + "\n",

encoding="utf-8",

)

print_json({"output": str(destination), **validate_proposal(proposal)})

else:

print_json(output)

return 0

if args.research_command == "complete":

bundle_path = args.bundle if args.bundle.is_absolute() else root / args.bundle

try:

bundle = json.loads(bundle_path.read_text(encoding="utf-8"))

runtime = bundle.get("runtime")

if not isinstance(runtime, dict):

raise ValueError("proposal runtime must be an object")

runtime["completed_at"] = utc_now()

bundle_path.write_text(

json.dumps(bundle, indent=2, sort_keys=True, ensure_ascii=False) + "\n",

encoding="utf-8",

)

except (OSError, json.JSONDecodeError, ValueError) as exc:

print_json(

{

"valid": False,

"errors": [str(exc)],

"submitted": False,

"admitted": False,

}

)

return 1

result = validate_proposal(bundle)

print_json({**result, "output": str(bundle_path)})

return 0 if result["valid"] else 1

if args.research_command == "validate":

bundle_path = args.bundle if args.bundle.is_absolute() else root / args.bundle

try:

bundle = json.loads(bundle_path.read_text(encoding="utf-8"))

except (OSError, json.JSONDecodeError) as exc:

print_json({"valid": False, "errors": [str(exc)], "submitted": False, "admitted": False})

return 1

result = validate_proposal(bundle)

print_json(result)

return 0 if result["valid"] else 1

if args.research_command == "submit":

bundle_path = args.bundle if args.bundle.is_absolute() else root / args.bundle

trace_path = args.trace if args.trace.is_absolute() else root / args.trace

try:

bundle = json.loads(bundle_path.read_text(encoding="utf-8"))

trace = json.loads(trace_path.read_text(encoding="utf-8"))

runtime = bundle.get("runtime")

if not isinstance(runtime, dict):

raise ValueError("proposal runtime must be an object")

runtime["completed_at"] = utc_now()

prepared = prepare_submission(

root,

bundle,

trace,

agent_id=args.agent_id,

model_family=args.model_family,

run_id=args.run_id,

prompt_sha256=args.prompt_sha256,

)

except (OSError, json.JSONDecodeError, ValueError) as exc:

print_json(

{

"valid": False,

"errors": [str(exc)],

"submitted": False,

"admitted": False,

}

)

return 1

directory = prepared["directory"]

print_json(

{

"valid": True,

"submitted": False,

"admitted": False,

"slug": prepared["slug"],

"proposal_id": prepared["proposal_id"],

"proposal_sha256": prepared["proposal_sha256"],

"directory": str(directory.relative_to(root)),

"next_steps": [

f"git switch -c submission/{prepared['slug']}",

f"git add {directory.relative_to(root)}",

f"git commit -m 'research: submit open docket {prepared['slug']}'",

f"git push -u origin submission/{prepared['slug']}",

(

"gh pr create --draft "

f"--title {json.dumps(prepared['pull_request_title'])} "

f"--body-file {prepared['pull_request_body'].relative_to(root)}"

),

],

"note": "Stop after opening the draft PR; do not review, merge, or publish it.",

}

)

return 0

if command == "repo":

if args.repo_command == "next":

return next_tasks(root)

if args.repo_command == "claim":

path = append_local_proposal(root, "task-claim", {"task_id": args.task_id, "agent_id": args.agent, "status": "proposed"})

print(path.relative_to(root))

return 0

if args.repo_command == "receipt":

path = append_local_proposal(root, "run-receipt", {"task_id": args.task_id, "run_id": args.run, "command": args.run_command, "status": "reported"})

print(path.relative_to(root))

return 0

if command == "mcp":

if args.mcp_command == "serve":

return stdio_mcp(root)

if args.mcp_command == "discover":

print_json(Gateway(root).mcp_method("server/discover", {}))

return 0

raise SystemExit(f"unhandled command: {command}")

if __name__ == "__main__":

raise SystemExit(main())

Build receipt

Reproduce this projection

Reproducible projection
Catalog
em:catalog:sha256:9bfc972213cba2cde167386103dc2c011ee74639fb7f0794c54120fbbdef1a5d
Frontier
em:frontier:sha256:f33be3eae4c75232d56750ef9a1aa79d96274ece3417d65a75c1391bf61a81bf
Accepted commit
f92846570180dfa4511263f8ba98ecd18f7772c9
Epistemic policy
commons-balanced-v0.1
Disclosure policy
public-noninterference-v0.1
Compiler
epistemedia/0.2.0