amduat-api/README.md

9.2 KiB

amduat-api

amduat-api builds amduatd, a minimal HTTP server over a Unix domain socket that exposes Amduat substrate operations for a single ASL store root.

Build

cmake -S . -B build
cmake --build build -j

To build without the embedded UI:

cmake -S . -B build -DAMDUATD_ENABLE_UI=OFF

When the UI is enabled (default), /v1/ui serves the same embedded HTML as before.

Core dependency

This repo vendors the core implementation as a git submodule at vendor/amduat.

git submodule update --init --recursive

Quickstart (local)

Initialize a store:

./vendor/amduat/build/amduat-asl init --root .amduat-asl

Run the daemon (fs backend default):

./build/amduatd --root .amduat-asl --sock amduatd.sock

Run the daemon with the index-backed store:

./build/amduatd --root .amduat-asl --sock amduatd.sock --store-backend index

Note: /v1/fed/records requires the index backend.

Federation (dev)

Federation is opt-in and disabled by default. Enabling federation requires the index-backed store and explicit flags:

./build/amduatd --root .amduat-asl --sock amduatd.sock --store-backend index \
  --fed-enable --fed-transport unix --fed-unix-sock peer.sock \
  --fed-domain-id 1 --fed-registry-ref <registry_ref_hex>

Flags:

  • --fed-enable turns on the coordinator tick loop.
  • --fed-transport stub|unix selects transport (stub by default).
  • --fed-unix-sock PATH configures the unix transport socket path.
  • --fed-domain-id ID sets the local domain id.
  • --fed-registry-ref REF seeds the registry reference (hex ref).
  • --fed-require-space rejects /v1/fed/* requests that do not resolve a space.

X-Amduat-Space is honored for /v1/fed/* requests the same way as other endpoints. If --space is configured, unix transport requests will include the same X-Amduat-Space header when contacting peers.

Run the daemon with derivation indexing enabled:

./build/amduatd --root .amduat-asl --sock amduatd.sock --enable-derivation-index

To fail /v1/pel/run if the derivation index write fails:

./build/amduatd --root .amduat-asl --sock amduatd.sock --derivation-index-strict

Dev loop (build + restart):

./scripts/dev-restart.sh

Query store meta:

curl --unix-socket amduatd.sock http://localhost/v1/meta

Browser UI (use socat to forward the Unix socket):

socat TCP-LISTEN:8080,fork,reuseaddr UNIX-CONNECT:amduatd.sock

Then open http://localhost:8080/v1/ui (concept editor).

Discover the store-backed API contract ref:

curl --unix-socket amduatd.sock 'http://localhost/v1/contract?format=ref'

Fetch the contract bytes (JSON):

curl --unix-socket amduatd.sock http://localhost/v1/contract

Upload raw bytes:

curl --unix-socket amduatd.sock -X POST http://localhost/v1/artifacts \
  -H 'Content-Type: application/octet-stream' \
  --data-binary 'hello'

Download raw bytes:

curl --unix-socket amduatd.sock http://localhost/v1/artifacts/<ref>

Download artifact framing (ENC/ASL1-CORE v1):

curl --unix-socket amduatd.sock \
  'http://localhost/v1/artifacts/<ref>?format=artifact' --output artifact.bin

Run a PEL program from store-backed refs (default scheme_ref=dag):

curl --unix-socket amduatd.sock -X POST http://localhost/v1/pel/run \
  -H 'Content-Type: application/json' \
  -d '{"program_ref":"<program_ref>","input_refs":["<input_ref_0>"],"params_ref":"<params_ref>"}'

When derivation indexing is enabled, successful PEL runs record derivations under <root>/index/derivations/by_artifact/ keyed by output refs (plus result/trace/receipt refs).

Define a PEL/PROGRAM-DAG/1 program (store-backed):

curl --unix-socket amduatd.sock -X POST http://localhost/v1/pel/programs \
  -H 'Content-Type: application/json' \
  -d '{"nodes":[{"id":1,"op":{"name":"pel.bytes.concat","version":1},"inputs":[{"external":{"input_index":0}},{"external":{"input_index":1}}],"params_hex":""}],"roots":[{"node_id":1,"output_index":0}]}'

Create a named concept and publish a ref (so you can use the name instead of hex refs):

curl --unix-socket amduatd.sock -X POST http://localhost/v1/concepts \
  -H 'Content-Type: application/json' \
  -d '{"name":"hello","ref":"<program_ref>"}'

Publish a new version:

curl --unix-socket amduatd.sock -X POST http://localhost/v1/concepts/hello/publish \
  -H 'Content-Type: application/json' \
  -d '{"ref":"<program_ref_v2>"}'

Resolve the latest ref:

curl --unix-socket amduatd.sock http://localhost/v1/resolve/hello

Inspect concepts:

curl --unix-socket amduatd.sock http://localhost/v1/concepts
curl --unix-socket amduatd.sock http://localhost/v1/concepts/hello

Artifact info (length + type tag):

curl --unix-socket amduatd.sock 'http://localhost/v1/artifacts/<ref>?format=info'

Space selection

Requests can select a space via the X-Amduat-Space header:

curl --unix-socket amduatd.sock http://localhost/v1/concepts \
  -H 'X-Amduat-Space: demo'

Precedence rules:

  • X-Amduat-Space header (if present)
  • daemon --space default (if configured)
  • unscoped names (no space)

When capability tokens are used, the requested space must match the token's space (or the token must be unscoped), otherwise the request is rejected.

Space doctor

Check deterministic invariants for the effective space:

curl --unix-socket amduatd.sock http://localhost/v1/space/doctor
curl --unix-socket amduatd.sock http://localhost/v1/space/doctor \
  -H 'X-Amduat-Space: demo'

When the daemon uses the fs store backend, index-only checks are reported as "skipped"; the index backend runs them.

Current endpoints

  • GET /v1/meta{store_id, encoding_profile_id, hash_id, api_contract_ref}
  • GET /v1/contract → contract bytes (JSON) (+ X-Amduat-Contract-Ref header)
  • GET /v1/contract?format=ref{ref}
  • GET /v1/space/doctor → deterministic space health checks
  • GET /v1/ui → browser UI for authoring/running programs
  • GET /v1/fed/records?domain_id=...&from_logseq=...&limit=...{domain_id, snapshot_id, log_prefix, next_logseq, records[]} (published artifacts + tombstones + PER + TGK edges)
  • GET /v1/fed/artifacts/{ref} → raw bytes for federation resolve
  • GET /v1/fed/status{status, domain_id, registry_ref, last_tick_ms}
  • POST /v1/artifacts
    • raw bytes: Content-Type: application/octet-stream (+ optional X-Amduat-Type-Tag: 0x...)
    • artifact framing: Content-Type: application/vnd.amduat.asl.artifact+v1
  • GET|HEAD /v1/artifacts/{ref}
    • raw bytes default
    • artifact framing: ?format=artifact
  • POST /v1/concepts
    • request: {name, ref?} (name is lowercase; ref publishes an initial version)
    • response: {name, concept_ref}
  • POST /v1/concepts/{name}/publish → publishes a new version {ref}
  • GET /v1/concepts{concepts:[{name, concept_ref}]}
  • GET /v1/concepts/{name}{name, concept_ref, latest_ref, versions[]}
  • GET /v1/resolve/{name}{ref} (latest published)
  • POST /v1/pel/run
    • request: {program_ref, input_refs[], params_ref?, scheme_ref?} (program_ref/input_refs/params_ref accept hex refs or concept names; omit scheme_ref to use dag)
    • request receipt (optional): {receipt:{input_manifest_ref, environment_ref, evaluator_id, executor_ref, started_at, completed_at, sbom_ref?, parity_digest_hex?, executor_fingerprint_ref?, run_id_hex?, limits?, logs?, determinism_level?, rng_seed_hex?, signature_hex?}}
    • response: {result_ref, trace_ref?, receipt_ref?, output_refs[], status}
  • POST /v1/pel/programs
    • request: authoring JSON for PEL/PROGRAM-DAG/1 (kernel ops only; params_hex is raw hex bytes)
    • response: {program_ref}
  • POST /v1/context_frames

Receipt example (with v1.1 fields):

{
  "program_ref": "ab12...",
  "input_refs": ["cd34..."],
  "receipt": {
    "input_manifest_ref": "ef56...",
    "environment_ref": "7890...",
    "evaluator_id": "local-amduatd",
    "executor_ref": "1122...",
    "started_at": 1712345678,
    "completed_at": 1712345688,
    "executor_fingerprint_ref": "3344...",
    "run_id_hex": "deadbeef",
    "limits": {
      "cpu_ms": 12,
      "wall_ms": 20,
      "max_rss_kib": 1024,
      "io_reads": 1,
      "io_writes": 0
    },
    "logs": [
      {"kind": 1, "log_ref": "5566...", "sha256_hex": "aabbcc"}
    ],
    "determinism_level": 2,
    "rng_seed_hex": "010203",
    "signature_hex": "bead"
  }
}

Federation records example:

curl --unix-socket amduatd.sock \
  'http://localhost/v1/fed/records?domain_id=1&from_logseq=0&limit=256'
{
  "domain_id": 1,
  "snapshot_id": 42,
  "log_prefix": 1234,
  "next_logseq": 120,
  "records": [
    {
      "domain_id": 1,
      "type": 0,
      "ref": "ab12...",
      "logseq": 100,
      "snapshot_id": 42,
      "log_prefix": 1234,
      "visibility": 1,
      "has_source": false,
      "source_domain": 0
    }
  ]
}

Response example:

{
  "result_ref": "aa11...",
  "trace_ref": "bb22...",
  "receipt_ref": "cc33...",
  "output_refs": ["dd44..."],
  "status": "OK"
}

Notes

  • This is intentionally a local-first surface (Unix socket, no TLS). HTTPS can be added later without changing the semantics.