Glyph Attestation Flow

How attestations flow through meld compositions. The meld edge is both spatial grouping and reactive data pipeline: dragging glyphs together declares the subscription.

See AXIOMS.md for the attestation flow axioms.

The Model

Meld edge = reactive subscription

[ax: contact] → [py: enrich] → [prompt: summarize {{subject}}]
[se: contact] → [py: enrich] → [prompt: summarize {{subject}}]
[se: raw] → [se: filtered] → [py: process]

When the user melds these three glyphs, two subscriptions compile eagerly:

  1. ax→py / se→py: AX and SE are filters, always live. Their query becomes the subscription filter. Any new attestation matching that filter triggers py.
  2. py→prompt: Py is a producer. The subscription filter is actor == glyph:{py_glyph_id}. When py calls attest(), the resulting attestation triggers prompt.

Two edge types

Source glyphSubscription filterWhy
ax (filter)The AX glyph's query filter directlyAX is a pure filter — it doesn't create attestations, it selects them. The filter definition IS the subscription.
py / prompt (producer)actor == glyph:{upstream_id}Producers create new attestations tagged with their glyph ID. The edge watches for attestations from that specific glyph.

Execution flow

1. User melds [ax: contact] → [py: enrich] → [prompt: summarize]
   Subscriptions compile immediately:
     - ax→py: filter = {subjects: ["contact"]}
     - py→prompt: filter = {actor: "glyph:{py_id}"}

2. Attestation enters the system matching "contact"
   (via CLI, another glyph, API — any source)
3. ax→py subscription fires
4. py glyph executes with that ONE attestation as `upstream`
5. py code runs, calls attest() with enriched data
   actor: glyph:{py_glyph_id}
6. py→prompt subscription fires
7. prompt glyph executes with that ONE attestation
8. {{subject}}, {{predicate}}, etc. resolve from the attestation
9. LLM runs, result attestation created
10. Attestation glyph appears below prompt

Each step is one attestation in, one execution, zero or more attestations out.

AX is always live

AX has no play button. It is always running — this is the current state. When ax→py melds, the subscription starts delivering immediately. No backfill step, no manual trigger. The edge cursor initializes at meld time; attestations matching the AX filter from that point forward flow through.

Existing attestations from before the meld are not retroactively delivered. The subscription is forward-looking from the moment of assembly. The edge cursor marks the boundary.

What the python glyph receives

No pending(). No query(). The attestation is injected as a variable into the execution context, like attest() is today:

# `upstream` is the single attestation that triggered this execution
# injected by the runtime, like attest() is

print(upstream.subjects)     # ["alice@example.com"]
print(upstream.predicates)   # ["contact"]
print(upstream.contexts)     # ["crm"]
print(upstream.attributes)   # {"phone": "555-1234", ...}

# Do work with it
enriched = lookup_something(upstream.subjects[0])

# Produce output attestation (triggers downstream if melded)
attest(
    subjects=upstream.subjects,
    predicates=["enriched"],
    contexts=["pipeline"],
    attributes={"original": upstream.id, "enriched_data": enriched}
)

When the py glyph is NOT in a meld (standalone), upstream is None. The glyph works as it does today — user writes code, clicks play, it runs.

What the prompt glyph receives

The prompt glyph has no user code. Variable resolution is automatic. The incoming attestation's fields map to {{template}} placeholders per the existing template syntax in ats/so/actions/prompt/doc.go:

Same delivery mechanism (one attestation triggers one execution), different interface (template interpolation vs code).

Cursor / dedup

The watcher engine's existing OnAttestationCreated hook fires once per new attestation. Since each attestation has a unique ASID and is immutable, the natural deduplication is: fire once per ASID per edge. The edge either has or hasn't seen a given ASID.

A (composition_id, edge_from, edge_to, last_processed_asid, last_processed_timestamp) table tracks what each edge has consumed. This is the per-edge analog of the prompt handler's TemporalCursor.

On system restart or composition reconstruction, the cursor ensures we don't re-fire for attestations already processed.

AX/SE are pure filters

AX doesn't create attestations. It queries existing ones. When AX is the root of a meld, its query filter becomes the subscription filter for the outgoing edge. The attestations that flow downstream are the original attestations from the store — not copies, not wrappers.

This means:

Open