748 lines
30 KiB
Markdown
748 lines
30 KiB
Markdown
# 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**.
|
|
|
|
## App Developer Handoff
|
|
|
|
For a compact, implementation-focused guide for external app teams, use:
|
|
|
|
- `docs/v2-app-developer-guide.md`
|
|
- `registry/amduatd-api-contract.v2.json` (machine-readable contract)
|
|
|
|
## Build
|
|
|
|
```sh
|
|
cmake -S . -B build
|
|
cmake --build build -j
|
|
```
|
|
|
|
To build without the embedded UI:
|
|
|
|
```sh
|
|
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`.
|
|
|
|
```sh
|
|
git submodule update --init --recursive
|
|
```
|
|
|
|
## Quickstart (local)
|
|
|
|
Initialize a store:
|
|
|
|
```sh
|
|
./vendor/amduat/build/amduat-asl init --root .amduat-asl
|
|
```
|
|
|
|
Run the daemon (fs backend default):
|
|
|
|
```sh
|
|
./build/amduatd --root .amduat-asl --sock amduatd.sock
|
|
```
|
|
|
|
Run the daemon with the index-backed store:
|
|
|
|
```sh
|
|
./build/amduatd --root .amduat-asl --sock amduatd.sock --store-backend index
|
|
```
|
|
|
|
Note: `/v1/fed/records` and `/v1/fed/push/plan` require the index backend.
|
|
|
|
## Federation (dev)
|
|
|
|
Federation is opt-in and disabled by default. Enabling federation requires the
|
|
index-backed store and explicit flags:
|
|
|
|
```sh
|
|
./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.
|
|
|
|
### Federation cursors
|
|
|
|
Federation cursors track deterministic, auditable sync checkpoints per
|
|
`(space, peer)`. Cursor heads are stored as ASL pointers that reference CAS
|
|
records (`fed/cursor` schema). A peer key should be a stable identifier for the
|
|
remote (for example a federation registry domain id rendered as a decimal
|
|
string).
|
|
|
|
Push cursors are separate from pull cursors and live under
|
|
`fed/push_cursor/<peer>/head` (space scoped).
|
|
|
|
To avoid cursor collisions across multiple mounts to the same peer, pass
|
|
`remote_space_id=<space_id>` on cursor-aware endpoints. When provided, cursor
|
|
heads use `fed/cursor/<peer>/<remote_space_id>/head` and
|
|
`fed/push_cursor/<peer>/<remote_space_id>/head`. When omitted, the legacy v1
|
|
cursor names remain in effect for backward compatibility.
|
|
|
|
Read the current cursor for a peer:
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock \
|
|
'http://localhost/v1/fed/cursor?peer=domain-2' \
|
|
-H 'X-Amduat-Space: demo'
|
|
```
|
|
|
|
Scoped to a specific remote space:
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock \
|
|
'http://localhost/v1/fed/cursor?peer=domain-2&remote_space_id=beta' \
|
|
-H 'X-Amduat-Space: demo'
|
|
```
|
|
|
|
Write a cursor update (CAS-safe; include `expected_ref` to enforce; omitting it
|
|
only succeeds when the cursor is absent):
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock -X POST \
|
|
'http://localhost/v1/fed/cursor?peer=domain-2' \
|
|
-H 'Content-Type: application/json' \
|
|
-H 'X-Amduat-Space: demo' \
|
|
-d '{"last_logseq":123,"last_record_hash":"<ref>","expected_ref":"<ref>"}'
|
|
```
|
|
|
|
Cursor values are intended to drive incremental log/index scanning when that
|
|
infrastructure is available; the cursor endpoints themselves do not require the
|
|
index backend.
|
|
|
|
### Federation pull plan
|
|
|
|
You can ask the daemon to compute a read-only plan of which remote records would
|
|
be pulled from a peer given the current cursor:
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock \
|
|
'http://localhost/v1/fed/pull/plan?peer=2&limit=128' \
|
|
-H 'X-Amduat-Space: demo'
|
|
```
|
|
|
|
The plan does not write artifacts, records, or cursors. It is deterministic and
|
|
returns only identifiers (logseq/ref), plus the next cursor candidate if the
|
|
plan were applied successfully.
|
|
Append `&remote_space_id=<space_id>` to use mount-specific cursor keying.
|
|
|
|
Apply a bounded batch of remote records (advances the cursor only after
|
|
success):
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock -X POST \
|
|
'http://localhost/v1/fed/pull?peer=2&limit=128' \
|
|
-H 'X-Amduat-Space: demo'
|
|
```
|
|
|
|
`/v1/fed/pull` requires the index backend and will not advance the cursor on
|
|
partial failure.
|
|
Use `remote_space_id=<space_id>` to scope the cursor to a mount.
|
|
|
|
### Federation push plan (sender dry run)
|
|
|
|
Compute a read-only plan of what would be sent to a peer from the local log
|
|
since the push cursor (does not advance the cursor):
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock \
|
|
'http://localhost/v1/fed/push/plan?peer=2&limit=128' \
|
|
-H 'X-Amduat-Space: demo'
|
|
```
|
|
|
|
`/v1/fed/push/plan` requires the index backend and uses a push cursor separate
|
|
from the pull cursor.
|
|
Append `&remote_space_id=<space_id>` to use mount-specific cursor keying.
|
|
|
|
### Federation push (sender apply)
|
|
|
|
Send local records to a peer (advances the push cursor only after all records
|
|
apply successfully on the peer):
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock -X POST \
|
|
'http://localhost/v1/fed/push?peer=2&limit=128' \
|
|
-H 'X-Amduat-Space: demo'
|
|
```
|
|
|
|
`/v1/fed/push` uses `/v1/fed/ingest` on the peer and only advances the push
|
|
cursor after the batch completes. It requires the index backend.
|
|
Use `remote_space_id=<space_id>` to scope the cursor to a mount.
|
|
|
|
### Federation sync until caught up
|
|
|
|
Use the bounded helpers to repeatedly apply pull/push until no work remains,
|
|
with a hard `max_rounds` cap to keep requests bounded:
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock -X POST \
|
|
'http://localhost/v1/fed/pull/until?peer=2&limit=128&max_rounds=10' \
|
|
-H 'X-Amduat-Space: demo'
|
|
```
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock -X POST \
|
|
'http://localhost/v1/fed/push/until?peer=2&limit=128&max_rounds=10' \
|
|
-H 'X-Amduat-Space: demo'
|
|
```
|
|
|
|
### Federation ingest (receiver)
|
|
|
|
`/v1/fed/ingest` applies a single incoming record (push receiver). The request
|
|
is space-scoped via `X-Amduat-Space` and requires federation to be enabled;
|
|
otherwise the daemon responds with 503.
|
|
|
|
For artifact, per, and tgk_edge records, send raw bytes and provide metadata via
|
|
query params:
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock -X POST \
|
|
'http://localhost/v1/fed/ingest?record_type=artifact&ref=<ref>' \
|
|
-H 'Content-Type: application/octet-stream' \
|
|
-H 'X-Amduat-Space: demo' \
|
|
--data-binary 'payload'
|
|
```
|
|
|
|
For tombstones, send a small JSON payload:
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock -X POST \
|
|
'http://localhost/v1/fed/ingest' \
|
|
-H 'Content-Type: application/json' \
|
|
-H 'X-Amduat-Space: demo' \
|
|
-d '{"record_type":"tombstone","ref":"<ref>"}'
|
|
```
|
|
|
|
Notes:
|
|
|
|
- Record types: `artifact`, `per`, `tgk_edge`, `tombstone`.
|
|
- Size limit: 8 MiB per request.
|
|
- Tombstones use deterministic defaults: `scope=0`, `reason_code=0`.
|
|
|
|
Run the daemon with derivation indexing enabled:
|
|
|
|
```sh
|
|
./build/amduatd --root .amduat-asl --sock amduatd.sock --enable-derivation-index
|
|
```
|
|
|
|
## Space roots (GC)
|
|
|
|
`/v1/space/roots` enumerates the pointer heads that must be treated as GC roots
|
|
for the effective space, including federation cursor heads.
|
|
|
|
GC root sets MUST include federation cursors to avoid trimming artifacts still
|
|
reachable via replication state. Use the roots listing to build your root set
|
|
before running GC tooling.
|
|
|
|
Example:
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock \
|
|
'http://localhost/v1/space/roots' \
|
|
-H 'X-Amduat-Space: demo'
|
|
```
|
|
|
|
## Space sync status
|
|
|
|
`/v1/space/sync/status` is a read-only summary of federation readiness and
|
|
per-peer cursor positions for the effective space. Peers are discovered from
|
|
cursor head pointers (pull and push) and returned in deterministic order.
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock \
|
|
'http://localhost/v1/space/sync/status' \
|
|
-H 'X-Amduat-Space: demo'
|
|
```
|
|
|
|
The response groups cursor status per peer and per remote space id:
|
|
`peers:[{peer_key, remotes:[{remote_space_id, pull_cursor, push_cursor}]}]`.
|
|
`remote_space_id` is `null` for legacy v1 cursor heads.
|
|
|
|
## Space manifest
|
|
|
|
`/v1/space/manifest` returns the space manifest rooted at the deterministic
|
|
pointer head (`manifest/head` or `space/<space_id>/manifest/head`). The manifest
|
|
is stored in CAS as a record and returned with its ref plus a decoded,
|
|
deterministic JSON payload. If no manifest head is present, the endpoint
|
|
returns a 404.
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock \
|
|
'http://localhost/v1/space/manifest' \
|
|
-H 'X-Amduat-Space: demo'
|
|
```
|
|
|
|
Create the manifest (only if no head exists yet):
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock \
|
|
-X PUT 'http://localhost/v1/space/manifest' \
|
|
-H 'Content-Type: application/json' \
|
|
-H 'X-Amduat-Space: demo' \
|
|
--data-binary '{"version":1,"mounts":[]}'
|
|
```
|
|
|
|
Update with optimistic concurrency:
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock \
|
|
-X PUT 'http://localhost/v1/space/manifest' \
|
|
-H 'Content-Type: application/json' \
|
|
-H 'X-Amduat-Space: demo' \
|
|
-H 'If-Match: <manifest_ref>' \
|
|
--data-binary @manifest.json
|
|
```
|
|
|
|
`If-Match` can be replaced with `?expected_ref=<manifest_ref>` if needed.
|
|
|
|
## Space mount resolution
|
|
|
|
`/v1/space/mounts/resolve` returns a deterministic, local-only view of the
|
|
space manifest mounts with their local pull cursor state. It performs no
|
|
network I/O and does not mutate storage. Track mounts indicate intent; syncing
|
|
remains a separate concern.
|
|
If no manifest head is present, the endpoint returns a 404.
|
|
Track mounts report `local_tracking.cursor_namespace` (`v2` when using
|
|
`remote_space_id`-keyed cursors).
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock \
|
|
'http://localhost/v1/space/mounts/resolve' \
|
|
-H 'X-Amduat-Space: demo'
|
|
```
|
|
|
|
## Space workspace snapshot
|
|
|
|
`/v1/space/workspace` returns a deterministic, read-only snapshot for the
|
|
effective space. It aggregates the manifest, mount resolution, per-mount cursor
|
|
status, store backend metadata, federation flags, and store capabilities
|
|
(`capabilities.supported_ops`) into one JSON response. It performs no network
|
|
I/O and does not mutate storage.
|
|
|
|
This is a local snapshot that complements:
|
|
- `/v1/space/manifest` (manifest root + canonical manifest)
|
|
- `/v1/space/mounts/resolve` (resolved mounts + local tracking)
|
|
- `/v1/space/sync/status` (peer-wide cursor status)
|
|
- `/v1/space/mounts/sync/until` (active sync for track mounts)
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock \
|
|
'http://localhost/v1/space/workspace' \
|
|
-H 'X-Amduat-Space: demo'
|
|
```
|
|
|
|
## Workspace UI
|
|
|
|
`/workspace` serves a minimal, human-facing page that consumes
|
|
`/v1/space/workspace` and `/v1/space/mounts/sync/until`, plus read-only
|
|
health panels for `/v1/space/doctor`, `/v1/space/roots`,
|
|
`/v1/space/sync/status`, `/v1/space/mounts/resolve`, and
|
|
`/v1/space/manifest`. It is a convenience view for inspection and manual sync
|
|
control, not a stable API. For programmatic use, call the `/v1/*` endpoints
|
|
directly.
|
|
|
|
## Space mounts sync (track mounts)
|
|
|
|
`/v1/space/mounts/sync/until` runs the federation pull/until loop for every
|
|
`track` mount in the current manifest using v2 cursor keying
|
|
(`remote_space_id = mount.space_id`). It is bounded by `limit`, `max_rounds`,
|
|
and `max_mounts`, returns per-mount status, and continues after errors.
|
|
Requires federation enabled and the index backend. If no manifest head is
|
|
present, the endpoint returns a 404.
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock -X POST \
|
|
'http://localhost/v1/space/mounts/sync/until?limit=128&max_rounds=10&max_mounts=32' \
|
|
-H 'X-Amduat-Space: demo'
|
|
```
|
|
|
|
To fail `/v1/pel/run` if the derivation index write fails:
|
|
|
|
```sh
|
|
./build/amduatd --root .amduat-asl --sock amduatd.sock --derivation-index-strict
|
|
```
|
|
|
|
Dev loop (build + restart):
|
|
|
|
```sh
|
|
./scripts/dev-restart.sh
|
|
```
|
|
|
|
## Federation smoke test
|
|
|
|
Run the end-to-end federation smoke test (starts two local daemons, verifies
|
|
pull replication A→B and push replication B→A, and checks cursors):
|
|
|
|
```sh
|
|
./scripts/test_fed_smoke.sh
|
|
```
|
|
|
|
The test requires the index backend and either `curl` with `--unix-socket`
|
|
support or the built-in `build/amduatd_http_unix` helper.
|
|
|
|
Query store meta:
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock http://localhost/v1/meta
|
|
```
|
|
|
|
Browser UI (use `socat` to forward the Unix socket):
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock 'http://localhost/v1/contract?format=ref'
|
|
```
|
|
|
|
Fetch the contract bytes (JSON):
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock http://localhost/v1/contract
|
|
```
|
|
|
|
Upload raw bytes:
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock -X POST http://localhost/v1/artifacts \
|
|
-H 'Content-Type: application/octet-stream' \
|
|
--data-binary 'hello'
|
|
```
|
|
|
|
Download raw bytes:
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock http://localhost/v1/artifacts/<ref>
|
|
```
|
|
|
|
Download artifact framing (`ENC/ASL1-CORE v1`):
|
|
|
|
```sh
|
|
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`):
|
|
|
|
```sh
|
|
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>"}'
|
|
```
|
|
|
|
Run a v2 PEL execute request with inline artifact ingest:
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock -X POST http://localhost/v2/pel/execute \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{
|
|
"scheme_ref":"dag",
|
|
"program_ref":"<program_ref>",
|
|
"inputs":{
|
|
"refs":["<input_ref_0>"],
|
|
"inline_artifacts":[{"body_hex":"48656c6c6f2c20763221","type_tag":"0x00000000"}]
|
|
},
|
|
"receipt":{
|
|
"input_manifest_ref":"<manifest_ref>",
|
|
"environment_ref":"<env_ref>",
|
|
"evaluator_id":"local-amduatd",
|
|
"executor_ref":"<executor_ref>",
|
|
"started_at":1731000000,
|
|
"completed_at":1731000001
|
|
}
|
|
}'
|
|
```
|
|
|
|
The v2 execute response returns `run_ref` (and `result_ref` alias),
|
|
`receipt_ref`, `stored_input_refs[]`, `output_refs[]`, and `status`.
|
|
|
|
Simplified async v2 operations (PEL-backed under the hood):
|
|
|
|
```sh
|
|
# put (returns job_id)
|
|
curl --unix-socket amduatd.sock -X POST http://localhost/v2/ops/put \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"body_hex":"48656c6c6f","type_tag":"0x00000000"}'
|
|
|
|
# concat (returns job_id)
|
|
curl --unix-socket amduatd.sock -X POST http://localhost/v2/ops/concat \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"left_ref":"<ref_a>","right_ref":"<ref_b>"}'
|
|
|
|
# slice (returns job_id)
|
|
curl --unix-socket amduatd.sock -X POST http://localhost/v2/ops/slice \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{"ref":"<ref>","offset":1,"length":3}'
|
|
|
|
# poll job
|
|
curl --unix-socket amduatd.sock http://localhost/v2/jobs/1
|
|
|
|
# get bytes
|
|
curl --unix-socket amduatd.sock http://localhost/v2/get/<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):
|
|
|
|
```sh
|
|
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):
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock http://localhost/v1/resolve/hello
|
|
```
|
|
|
|
Inspect concepts:
|
|
|
|
```sh
|
|
curl --unix-socket amduatd.sock http://localhost/v1/concepts
|
|
curl --unix-socket amduatd.sock http://localhost/v1/concepts/hello
|
|
```
|
|
|
|
Artifact info (length + type tag):
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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/space/manifest` → `{effective_space, manifest_ref, manifest}`
|
|
- `PUT /v1/space/manifest` → `{effective_space, manifest_ref, updated, previous_ref?, manifest}`
|
|
- `GET /v1/space/mounts/resolve` → `{effective_space, manifest_ref, mounts}`
|
|
- `GET /v1/space/workspace` → `{effective_space, store_backend, federation, capabilities, manifest_ref, manifest, mounts}`
|
|
- `POST /v1/space/mounts/sync/until?limit=...&max_rounds=...&max_mounts=...` → `{effective_space, manifest_ref, limit, max_rounds, max_mounts, mounts_total, mounts_synced, ok, results}`
|
|
- `GET /v1/space/sync/status` → `{effective_space, store_backend, federation, peers}`
|
|
- `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/cursor?peer=...&remote_space_id=...` → `{peer_key, space_id, last_logseq, last_record_hash, ref}` (`remote_space_id` optional)
|
|
- `POST /v1/fed/cursor?peer=...&remote_space_id=...` → `{ref}` (CAS update; `expected_ref` in body; `remote_space_id` optional)
|
|
- `GET /v1/fed/pull/plan?peer=...&limit=...&remote_space_id=...` → `{peer, effective_space, cursor, remote_scan, records, next_cursor_candidate, ...}`
|
|
- `GET /v1/fed/push/plan?peer=...&limit=...&remote_space_id=...` → `{peer, domain_id, effective_space, cursor, scan, records, required_artifacts, next_cursor_candidate}`
|
|
- `POST /v1/fed/pull?peer=...&limit=...&remote_space_id=...` → `{peer, effective_space, cursor_before, plan_summary, applied, cursor_after, errors}`
|
|
- `POST /v1/fed/push?peer=...&limit=...&remote_space_id=...` → `{peer, domain_id, effective_space, cursor_before, plan_summary, sent, cursor_after, errors}`
|
|
- `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 /v2/pel/execute` (first v2 slice)
|
|
- request: `{program_ref, scheme_ref?, params_ref?, inputs:{refs[], inline_artifacts:[{body_hex, type_tag?}]}, receipt:{input_manifest_ref, environment_ref, evaluator_id, executor_ref, started_at, completed_at}}`
|
|
- response: `{run_ref, result_ref, trace_ref?, receipt_ref, stored_input_refs[], output_refs[], status}`
|
|
- `POST /v2/ops/put` → async enqueue `{job_id, status}`
|
|
- request: `{body_hex, type_tag?}`
|
|
- `POST /v2/ops/concat` → async enqueue `{job_id, status}`
|
|
- request: `{left_ref, right_ref}` (refs or concept names)
|
|
- `POST /v2/ops/slice` → async enqueue `{job_id, status}`
|
|
- request: `{ref, offset, length}` (`ref` accepts ref or concept name)
|
|
- `GET /v2/jobs/{id}` → `{job_id, kind, status, created_at_ms, started_at_ms, completed_at_ms, result_ref, error}`
|
|
- `GET /v2/get/{ref}` → artifact bytes (alias to `/v1/artifacts/{ref}`)
|
|
- `GET /v2/healthz` → liveness probe `{ok, status, time_ms}`
|
|
- `GET /v2/readyz` → readiness probe `{ok, status, components:{graph_index, federation}}` (`503` when not ready)
|
|
- `GET /v2/metrics` → Prometheus text metrics (`text/plain; version=0.0.4`)
|
|
- `POST /v2/graph/nodes` → create/seed graph node via concept `{name, ref?}`
|
|
- `POST /v2/graph/nodes/{name}/versions` → publish node version `{ref, metadata_ref?|provenance?}` (`metadata_ref` and `provenance` are mutually exclusive)
|
|
- `POST /v2/graph/nodes/{name}/versions/tombstone` → tombstone a previously published node version `{ref, metadata_ref?|provenance?}` (`metadata_ref` and `provenance` are mutually exclusive; append-only)
|
|
- `GET /v2/graph/nodes/{name}/versions?as_of=&include_tombstoned=` → get node versions (same shape as node read; defaults hide tombstoned versions from `latest_ref` and `versions[]`; set `include_tombstoned=true` to include them)
|
|
- `GET /v2/graph/nodes/{name}/neighbors?dir=&predicate=&limit=&cursor=&as_of=&provenance_ref=&include_tombstoned=&expand_names=&expand_artifacts=` → neighbor scan (paged, snapshot-bounded; tombstoned edges excluded by default unless `include_tombstoned=true`; optional provenance filter; add names/latest refs via expansion flags)
|
|
- `GET /v2/graph/search?name_prefix=&limit=&cursor=&as_of=` → search nodes by name prefix (paged, snapshot-bounded)
|
|
- `GET /v2/graph/paths?from=&to=&max_depth=&predicate=&as_of=&k=&expand_names=&expand_artifacts=&include_tombstoned=&provenance_ref=&max_fanout=&max_result_bytes=&include_stats=` → shortest directed path query (bounded BFS, snapshot-bounded, returns up to `k` paths with hop metadata and optional names/latest refs; tombstoned edges excluded by default unless `include_tombstoned=true`; optional provenance filter; supports fanout/response-size guards and optional stats)
|
|
- `GET /v2/graph/subgraph?roots[]=&max_depth=&max_fanout=&predicates[]=&dir=&as_of=&include_versions=&include_tombstoned=&limit_nodes=&limit_edges=&cursor=&provenance_ref=&max_result_bytes=&include_stats=` → bounded multi-hop subgraph retrieval from roots (snapshot-bounded, paged by opaque `next_cursor`; tombstoned edges excluded by default unless `include_tombstoned=true`; optional provenance filter; supports fanout/response-size guards and optional stats; returns `{nodes[], edges[], frontier?[], next_cursor, truncated}`)
|
|
- `POST /v2/graph/edges` → append graph edge `{subject, predicate, object, metadata_ref?|provenance?}` (`metadata_ref` and `provenance` are mutually exclusive)
|
|
- `POST /v2/graph/edges/tombstone` → append tombstone for an existing edge `{edge_ref, metadata_ref?|provenance?}` (append-only correction/retraction; `metadata_ref` and `provenance` are mutually exclusive)
|
|
- `POST /v2/graph/batch` → apply mixed graph mutations `{idempotency_key?, mode?, nodes?, versions?, edges?}` (versions/edges items support `metadata_ref?|provenance?`; mode: `fail_fast|continue_on_error`; deterministic replay for repeated `idempotency_key` + identical payload; returns `{ok, applied, results[]}` with per-item status/error)
|
|
- `POST /v2/graph/query` → unified graph query `{where?, predicates?, direction?, include_versions?, include_tombstoned?, include_stats?, max_result_bytes?, as_of?, limit?, cursor?}` returning `{nodes[], edges[], paging, stats?}` (`nodes[].versions[]` included when `include_versions=true`; `where.provenance_ref` filters edges linked via `ms.has_provenance`; tombstoned edges are excluded as-of snapshot unless `include_tombstoned=true`)
|
|
- `POST /v2/graph/retrieve` → agent-oriented bounded retrieval `{roots[], goal_predicates?[], max_depth?, as_of?, provenance_min_confidence?, include_versions?, include_tombstoned?, max_fanout?, limit_nodes?, limit_edges?, max_result_bytes?}` returning `{nodes[], edges[], explanations[], truncated, stats}` (multi-hop traversal from roots with optional predicate targeting and provenance-confidence gating; each explanation includes edge inclusion reasons and traversal depth)
|
|
- `POST /v2/graph/export` → paged graph envelope export `{as_of?, cursor?, limit?, predicates?[], roots?[], include_tombstoned?, max_result_bytes?}` returning `{items[], next_cursor, has_more, snapshot_as_of, stats}` (`items[]` preserve edge refs/order plus predicate, tombstone flag, and attached `metadata_ref` when present)
|
|
- `POST /v2/graph/import` → ordered graph envelope replay `{mode?, items[]}` (`mode=fail_fast|continue_on_error`) returning `{ok, applied, results[]}` with per-item `index/status/code/error/edge_ref`
|
|
- `GET /v2/graph/schema/predicates` → current graph governance policy `{mode, provenance_mode, predicates[]}` where `mode` is `strict|warn|off` and `provenance_mode` is `optional|required`
|
|
- `POST /v2/graph/schema/predicates` → update graph governance `{mode?, provenance_mode?, predicates?[]}` (`predicates[]` entries accept `{predicate_ref|predicate, domain?, range?}`; predicate validation mode is enforced for edge writes in `POST /v2/graph/edges`, batch edge items, and graph import edge items; `provenance_mode=required` enforces provenance attachment (`metadata_ref` or `provenance`) for version/edge/tombstone writes; policy is persisted per space root)
|
|
- `GET /v2/graph/stats` → graph/index health summary `{edges_total, aliases_total, index{...}, tombstones{edges, ratio}}`
|
|
- `GET /v2/graph/capabilities` → graph feature/version negotiation payload `{contract, graph{version, features, limits, modes}, runtime{...}}`
|
|
- `GET /v2/graph/changes?since_cursor=&since_as_of=&limit=&wait_ms=&event_types[]=&predicates[]=&roots[]=` → incremental appended graph events with strict cursor replay window (returns `410` when cursor falls outside retained window), resumable cursor tokens, optional long-poll (`wait_ms`), and event/predicate/root filters; response `{events[], next_cursor, has_more}`
|
|
- `GET /v2/graph/edges?subject=&predicate=&object=&dir=&limit=&cursor=&as_of=&expand_names=&expand_artifacts=&provenance_ref=&include_tombstoned=&max_result_bytes=&include_stats=` → filtered edge scan with paging (`dir=any|outgoing|incoming`, snapshot-bounded; tombstoned edges excluded by default unless `include_tombstoned=true`; optional provenance filter; add names/latest refs via expansion flags; supports response-size guards and optional stats)
|
|
- graph paging cursors are opaque tokens (`g1_*`); legacy numeric cursors are still accepted
|
|
- `GET /v2/graph/nodes/{name}?as_of=&include_tombstoned=` → `{name, concept_ref, latest_ref, versions[], outgoing[], incoming[]}` (`latest_ref` is resolved from visible versions at `as_of`; tombstoned versions excluded by default)
|
|
- `GET /v2/graph/history/{name}?as_of=&include_tombstoned=` → event timeline for node versions/edges (snapshot-bounded; `latest_ref` and tombstoned fact events both respect same visibility rules)
|
|
|
|
Graph client helper examples for agent flows are in `scripts/graph_client_helpers.sh`:
|
|
- `batch-ingest` (idempotent batch write helper)
|
|
- `sync-once` (incremental changes cursor step)
|
|
- `subgraph` (bounded retrieval helper)
|
|
- `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`
|
|
|
|
UI (human-facing, not an API contract):
|
|
- `GET /workspace` → minimal workspace snapshot + sync controls (uses `/v1/space/workspace`)
|
|
|
|
Receipt example (with v1.1 fields):
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```bash
|
|
curl --unix-socket amduatd.sock \
|
|
'http://localhost/v1/fed/records?domain_id=1&from_logseq=0&limit=256'
|
|
```
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```json
|
|
{
|
|
"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.
|