238 lines
6.9 KiB
Markdown
238 lines
6.9 KiB
Markdown
|
|
# Amduat v2 App Developer Guide
|
||
|
|
|
||
|
|
This is the compact handoff guide for building an application against `amduatd` v2.
|
||
|
|
|
||
|
|
For machine-readable contracts, see `registry/amduatd-api-contract.v2.json`.
|
||
|
|
|
||
|
|
## 1) Runtime Model
|
||
|
|
|
||
|
|
- One daemon instance serves one ASL store root.
|
||
|
|
- Transport is local Unix socket HTTP.
|
||
|
|
- Auth is currently filesystem/socket permission based.
|
||
|
|
- All graph APIs are under `/v2/graph/*`.
|
||
|
|
|
||
|
|
Minimal local run:
|
||
|
|
|
||
|
|
```sh
|
||
|
|
./vendor/amduat/build/amduat-asl init --root .amduat-asl
|
||
|
|
./build/amduatd --root .amduat-asl --sock amduatd.sock --store-backend index
|
||
|
|
```
|
||
|
|
|
||
|
|
## 2) Request Conventions
|
||
|
|
|
||
|
|
- Use `X-Amduat-Space: <space_id>` for app isolation.
|
||
|
|
- Treat graph cursors as opaque tokens (`g1_*`), do not parse.
|
||
|
|
- Use `as_of` for snapshot-consistent reads.
|
||
|
|
- Use `include_tombstoned=true` only when you explicitly want retracted facts.
|
||
|
|
|
||
|
|
## 3) Core App Flows
|
||
|
|
|
||
|
|
### A. High-throughput ingest
|
||
|
|
|
||
|
|
Use `POST /v2/graph/batch` with:
|
||
|
|
|
||
|
|
- `idempotency_key` for deterministic retries
|
||
|
|
- `mode=continue_on_error` for partial-apply behavior
|
||
|
|
- per-item `metadata_ref` or `provenance` for trust/debug
|
||
|
|
|
||
|
|
Expect response:
|
||
|
|
|
||
|
|
- `ok` (overall)
|
||
|
|
- `applied` aggregate counts
|
||
|
|
- `results[]` with `{kind,index,status,code,error}`
|
||
|
|
|
||
|
|
### B. Multi-hop retrieval for agents
|
||
|
|
|
||
|
|
Primary endpoints:
|
||
|
|
|
||
|
|
- `GET /v2/graph/subgraph`
|
||
|
|
- `POST /v2/graph/retrieve`
|
||
|
|
- `POST /v2/graph/query` for declarative filtering
|
||
|
|
|
||
|
|
Use:
|
||
|
|
|
||
|
|
- `as_of` for stable reasoning snapshots
|
||
|
|
- `max_depth`, `max_fanout`, `limit_nodes`, `limit_edges`, `max_result_bytes`
|
||
|
|
- provenance filters where needed (`provenance_ref` / `provenance_min_confidence`)
|
||
|
|
|
||
|
|
### C. Incremental sync loop
|
||
|
|
|
||
|
|
Use `GET /v2/graph/changes`:
|
||
|
|
|
||
|
|
- Start with `since_cursor` (or bootstrap with `since_as_of`)
|
||
|
|
- Persist returned `next_cursor` after successful processing
|
||
|
|
- Handle `410` as replay-window expiry (full resync required)
|
||
|
|
- Optional long poll: `wait_ms`
|
||
|
|
|
||
|
|
### D. Fact correction
|
||
|
|
|
||
|
|
- Edge retraction: `POST /v2/graph/edges/tombstone`
|
||
|
|
- Node-version retraction: `POST /v2/graph/nodes/{name}/versions/tombstone`
|
||
|
|
|
||
|
|
Reads default to exclude tombstoned facts on retrieval surfaces unless `include_tombstoned=true`.
|
||
|
|
|
||
|
|
## 4) Endpoint Map (what to use when)
|
||
|
|
|
||
|
|
- Write node: `POST /v2/graph/nodes`
|
||
|
|
- Write version: `POST /v2/graph/nodes/{name}/versions`
|
||
|
|
- Write edge: `POST /v2/graph/edges`
|
||
|
|
- Batch write: `POST /v2/graph/batch`
|
||
|
|
- Point-ish read: `GET /v2/graph/nodes/{name}`
|
||
|
|
- Edge scan: `GET /v2/graph/edges`
|
||
|
|
- Neighbor scan: `GET /v2/graph/nodes/{name}/neighbors`
|
||
|
|
- Path lookup: `GET /v2/graph/paths`
|
||
|
|
- Subgraph: `GET /v2/graph/subgraph`
|
||
|
|
- Declarative query: `POST /v2/graph/query`
|
||
|
|
- Agent retrieval: `POST /v2/graph/retrieve`
|
||
|
|
- Changes feed: `GET /v2/graph/changes`
|
||
|
|
- Export: `POST /v2/graph/export`
|
||
|
|
- Import: `POST /v2/graph/import`
|
||
|
|
- Predicate policy: `GET/POST /v2/graph/schema/predicates`
|
||
|
|
- Health/readiness/metrics: `GET /v2/healthz`, `GET /v2/readyz`, `GET /v2/metrics`
|
||
|
|
- Graph runtime/capability: `GET /v2/graph/stats`, `GET /v2/graph/capabilities`
|
||
|
|
|
||
|
|
## 5) Provenance and Policy
|
||
|
|
|
||
|
|
Provenance object fields for writes:
|
||
|
|
|
||
|
|
- required: `source_uri`, `extractor`, `observed_at`, `ingested_at`, `trace_id`
|
||
|
|
- optional: `confidence`, `license`
|
||
|
|
|
||
|
|
Policy endpoint:
|
||
|
|
|
||
|
|
- `POST /v2/graph/schema/predicates`
|
||
|
|
|
||
|
|
Key modes:
|
||
|
|
|
||
|
|
- predicate validation: `strict|warn|off`
|
||
|
|
- provenance enforcement: `optional|required`
|
||
|
|
|
||
|
|
## 6) Error Handling and Retry Rules
|
||
|
|
|
||
|
|
- Retry-safe writes: only retries with same `idempotency_key` and identical payload.
|
||
|
|
- Validation failures: `400` or `422` (do not blind-retry).
|
||
|
|
- Not found for references/nodes: `404`.
|
||
|
|
- Cursor window expired: `410` on `/changes` (rebootstrap sync state).
|
||
|
|
- Result guard triggered: `422` (`max_result_bytes` or traversal/search limits).
|
||
|
|
- Internal errors: `500` (retry with backoff).
|
||
|
|
|
||
|
|
## 7) Performance and Safety Defaults
|
||
|
|
|
||
|
|
Recommended client defaults:
|
||
|
|
|
||
|
|
- Set explicit `limit` on scans.
|
||
|
|
- Always pass `max_result_bytes` on large retrieval requests.
|
||
|
|
- Keep `max_depth` conservative (start with 2-4).
|
||
|
|
- Enable `include_stats=true` in development to monitor scanned/returned counts and selected plan.
|
||
|
|
- Call `/v2/graph/capabilities` once at startup for feature/limit negotiation.
|
||
|
|
|
||
|
|
## 8) Minimal Startup Checklist (for external app)
|
||
|
|
|
||
|
|
1. Probe `GET /v2/readyz`.
|
||
|
|
2. Read `GET /v2/graph/capabilities`.
|
||
|
|
3. Configure schema/provenance policy (`POST /v2/graph/schema/predicates`) if your app owns policy.
|
||
|
|
4. Start ingest path (`/v2/graph/batch` idempotent).
|
||
|
|
5. Start change-consumer loop (`/v2/graph/changes`).
|
||
|
|
6. Serve retrieval via `/v2/graph/retrieve` and `/v2/graph/subgraph`.
|
||
|
|
7. Monitor `/v2/metrics` and `/v2/graph/stats`.
|
||
|
|
|
||
|
|
## 9) Useful Local Helpers
|
||
|
|
|
||
|
|
- `scripts/graph_client_helpers.sh` contains practical shell helpers for:
|
||
|
|
- idempotent batch ingest
|
||
|
|
- one-step changes sync
|
||
|
|
- subgraph retrieval
|
||
|
|
|
||
|
|
For integration tests/examples:
|
||
|
|
|
||
|
|
- `scripts/test_graph_queries.sh`
|
||
|
|
- `scripts/test_graph_contract.sh`
|
||
|
|
|
||
|
|
## 10) Copy/Paste Integration Skeleton
|
||
|
|
|
||
|
|
Set local defaults:
|
||
|
|
|
||
|
|
```sh
|
||
|
|
SOCK="amduatd.sock"
|
||
|
|
SPACE="app1"
|
||
|
|
BASE="http://localhost"
|
||
|
|
```
|
||
|
|
|
||
|
|
Startup probes:
|
||
|
|
|
||
|
|
```sh
|
||
|
|
curl --unix-socket "${SOCK}" -sS "${BASE}/v2/readyz"
|
||
|
|
curl --unix-socket "${SOCK}" -sS "${BASE}/v2/graph/capabilities"
|
||
|
|
```
|
||
|
|
|
||
|
|
Idempotent batch ingest:
|
||
|
|
|
||
|
|
```sh
|
||
|
|
curl --unix-socket "${SOCK}" -sS -X POST "${BASE}/v2/graph/batch" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-H "X-Amduat-Space: ${SPACE}" \
|
||
|
|
-d '{
|
||
|
|
"idempotency_key":"app1-batch-0001",
|
||
|
|
"mode":"continue_on_error",
|
||
|
|
"nodes":[{"name":"doc:1"}],
|
||
|
|
"edges":[
|
||
|
|
{"subject":"doc:1","predicate":"ms.within_domain","object":"topic:alpha",
|
||
|
|
"provenance":{"source_uri":"urn:app:seed","extractor":"app-loader","observed_at":1,"ingested_at":2,"trace_id":"trace-1"}}
|
||
|
|
]
|
||
|
|
}'
|
||
|
|
```
|
||
|
|
|
||
|
|
Incremental changes loop (bash skeleton):
|
||
|
|
|
||
|
|
```sh
|
||
|
|
cursor=""
|
||
|
|
while true; do
|
||
|
|
if [ -n "${cursor}" ]; then
|
||
|
|
path="/v2/graph/changes?since_cursor=${cursor}&limit=200&wait_ms=15000"
|
||
|
|
else
|
||
|
|
path="/v2/graph/changes?limit=200&wait_ms=15000"
|
||
|
|
fi
|
||
|
|
|
||
|
|
resp="$(curl --unix-socket "${SOCK}" -sS "${BASE}${path}" -H "X-Amduat-Space: ${SPACE}")" || break
|
||
|
|
|
||
|
|
# TODO: parse and process resp.events[] in your app.
|
||
|
|
next="$(printf '%s\n' "${resp}" | sed -n 's/.*"next_cursor":"\([^"]*\)".*/\1/p')"
|
||
|
|
[ -n "${next}" ] && cursor="${next}"
|
||
|
|
done
|
||
|
|
```
|
||
|
|
|
||
|
|
Agent retrieval call:
|
||
|
|
|
||
|
|
```sh
|
||
|
|
curl --unix-socket "${SOCK}" -sS -X POST "${BASE}/v2/graph/retrieve" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-H "X-Amduat-Space: ${SPACE}" \
|
||
|
|
-d '{
|
||
|
|
"roots":["doc:1"],
|
||
|
|
"goal_predicates":["ms.within_domain"],
|
||
|
|
"max_depth":2,
|
||
|
|
"max_fanout":1024,
|
||
|
|
"limit_nodes":200,
|
||
|
|
"limit_edges":400,
|
||
|
|
"max_result_bytes":1048576
|
||
|
|
}'
|
||
|
|
```
|
||
|
|
|
||
|
|
Subgraph snapshot read:
|
||
|
|
|
||
|
|
```sh
|
||
|
|
curl --unix-socket "${SOCK}" -sS \
|
||
|
|
"${BASE}/v2/graph/subgraph?roots[]=doc:1&max_depth=2&dir=outgoing&limit_nodes=200&limit_edges=400&include_stats=true&max_result_bytes=1048576" \
|
||
|
|
-H "X-Amduat-Space: ${SPACE}"
|
||
|
|
```
|
||
|
|
|
||
|
|
Edge correction (tombstone):
|
||
|
|
|
||
|
|
```sh
|
||
|
|
EDGE_REF="<edge_ref_to_retract>"
|
||
|
|
curl --unix-socket "${SOCK}" -sS -X POST "${BASE}/v2/graph/edges/tombstone" \
|
||
|
|
-H "Content-Type: application/json" \
|
||
|
|
-H "X-Amduat-Space: ${SPACE}" \
|
||
|
|
-d "{\"edge_ref\":\"${EDGE_REF}\"}"
|
||
|
|
```
|