[Draft] Fungible Agent Tokens (FAT) — a minimal standard for AI agents as on-chain economic entities

Fungible Agent Tokens (FAT) Protocol

This is a condensed presentation for readability. The full specification text lives at Fungible Agent Tokens (FAT) Protocol - HackMD — section references below (§2, §6.2, §7, …) point into its numbering.

Abstract

FAT (Fungible Agent Tokens) is an equity standard that defines AI agents as on-chain economic entities. A FAT Agent is not a container that holds funds — it is an autonomous actor: it issues Shares representing equity in its own economic activity, acts autonomously within a boundary set for it, and leaves a tamper-evident reasoning record for every action it takes. The protocol unifies these three layers in a single interface:

  • Autonomous Execution — the agent acts on-chain through its Executor, interacting with external protocols and deploying its own funds;
  • Equity Issuance — the agent issues fungible Shares representing a claim on its economic performance, entered and exited at a standard exchange rate;
  • Reasoning Attestation — off-chain metadata and per-action reasoning records anchor the agent’s identity, strategy, model, and the rationale behind every decision to its on-chain identity.

With this, any agent can be born, capitalized, and self-operating as an independent economic entity — a composable on-chain primitive for the entire ecosystem.

Concretely, FAT Agents are smart contracts that

  • accept capital contributions denominated in a single Accept Token (an immutable ERC-20 chosen at deployment) through a two-phase request-then-claim flow, and issue fungible Shares against them,
  • allow Holders to exit by requesting redemption and later claiming the Accept Token for their escrowed Shares, which are burned at settlement,
  • expose a canonical on-chain exchange rate between Shares and the Accept Token,
  • carry a mutable Agent URI pointing to off-chain metadata (name, description, image, etc.),
  • allow a designated off-chain Executor to call third-party protocols on behalf of the Agent via a low-level dispatch primitive, subject to a DELEGATECALL prohibition and an Owner-set isInScope scope gate, and
  • decide on mints and redemptions through reasoned settlement (settleMint / settleRedeem) — admitting, pricing, or declining each contribution and exit is a deliberate act of the agent itself — and attach a tamper-evident reasoning record (reasoningHash + reasoningURI) to every on-chain agent action.

Here, the Fungible Agent Tokens are the fungible Shares that represent economic participation in the Agent — not the Agent’s off-chain identity, model, or behavior. Those are not excluded from the standard; they are simply not represented as fungible Shares: the attestation layer anchors them on-chain through the Agent URI and reasoningHash / reasoningURI.

The standard fixes these interface surfaces but deliberately leaves to the implementer: the exact share-pricing formula, the fee structure, share transferability, the ownership-transfer mechanism, whether and how the Agent can be paused, whether and how Holders realize returns beyond what the exchange rate reflects, and — importantly — any restriction on what the Executor may call through execute. Policy is separated from interface.

Motivation

AI agents increasingly act on-chain — trading, staking, lending, managing capital — yet there is no standard that makes an agent itself an on-chain economic entity: one that can be capitalized, self-operating, and composed by the ecosystem, with its behavior and reasoning legible to anyone. Representing pooled capital as fungible shares is already well understood; what has no standard is the agent part — how it acts on external protocols, and how the off-chain reasoning behind those actions is anchored on-chain.

What FAT defines is not a container for funds — it is how an on-chain economic entity comes into existence. A FAT Agent holds a persistent on-chain identity, commands its own capital, acts autonomously within a constitutional boundary set by its Owner, and signs a verifiable record for every action it takes. Buying shares is not hiring a tool that executes on your behalf — it is investing in the agent’s own economic activity: participating in its growth and sharing in its outcomes. Share accounting is therefore only one of three layers — equity issuance — alongside the agent’s autonomous execution and the off-chain attestation of its identity and reasoning. For this class of system to compose and to be auditable, the behavior layer needs a standard: a single interface that tooling, indexers, wallets, and auditors can rely on regardless of the specific economic model the Agent chooses.

This standard makes the agent itself first-class. Its reaction (reasoned settlement, so mint and redeem are the Agent’s own deliberate acts rather than passive bookkeeping), its reasoning (a tamper-evident, indexable record bound to every on-chain action), and its bounded spending (an Owner-set Scope the Agent cannot widen) are standardized surfaces in their own right, so that what the Agent decides and may do is as legible and auditable as what it holds.

Beyond behavior, a tokenized Agent also needs:

  • A fixed unit of account — the single Accept Token, chosen at deployment and immutable thereafter — so that share pricing and redemption are unambiguous.
  • A standard exit path — the requestRedeem / redeem flow — so a Holder can exit any Agent through one interface that portfolio tooling can rely on.
  • A discoverable metadata pointer — the Agent URI, analogous to ERC-721 tokenURI — so explorers and marketplaces can render an Agent’s identity without per-project integration.

Because settlement may be asynchronous — the user requests first and claims once the Agent is ready — Shares are minted and redeemed through a two-phase request-then-claim lifecycle rather than a single synchronous call.

This specification defines all of the above as a minimal, composable standard.

Interface

All compliant Agents MUST implement the following Solidity interface. Function signatures, parameter order, and return types are normative.

// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.20;

interface IAgent {
    // ---------- Immutable configuration ----------

    /// @notice The single ERC-20 accepted for share purchase and paid out on redemption.
    /// @dev    MUST return the same address for the lifetime of the Agent.
    function acceptToken() external view returns (address);

    /// @notice Address of the ERC-20 Share token.
    /// @dev    MAY return `address(this)` if the Agent contract itself is the ERC-20.
    function shareToken() external view returns (address);

    // ---------- Share minting (two-phase) ----------

    /// @notice Phase 1. Deposit `amount` of the Accept Token, adding to the caller's pending mint balance.
    /// @dev    MUST pull exactly `amount` via ERC-20 `transferFrom`; MUST NOT accept native ether.
    ///         The share count is determined later, at settlement. MUST emit `MintRequested`.
    /// @param amount  The amount of Accept Token to deposit (in its smallest unit).
    function requestMint(uint256 amount) external;

    /// @notice Phase 2. Claim ALL currently-claimable shares for `user`. Permissionless: any caller MAY
    ///         trigger the claim, but the shares are always minted to `user` (the owed party), so the
    ///         caller only pays gas and gains nothing.
    /// @dev    MUST mint `user`'s entire claimable-share balance to `user`, zero that balance,
    ///         and emit `SharesMinted`. The settlement price is accepted implicitly (no slippage
    ///         guard; the share count was already fixed at settlement — see §6.3).
    /// @param  user    The owed party whose claimable shares are minted (to itself). Pass your own address to self-claim.
    /// @return shares  The number of shares minted to `user` (may be 0 if nothing is claimable).
    function mint(address user) external returns (uint256 shares);

    /// @notice Agent reaction. Settle `requester`'s pending mint request, with reasoning attached.
    /// @dev    Executor only. Settles some, all, or none of `requester`'s pendingAssets into
    ///         claimableShares; the amount/accept/price is computed by the implementation inside.
    ///         MUST emit `Settled`. `reasoningHash`/`reasoningURI` per §6.2.
    /// @param  requester      The request to settle.
    /// @param  reasoningHash  keccak256 of the bytes at `reasoningURI`; MUST be non-zero.
    /// @param  reasoningURI   Resolvable pointer to the reasoning record; MUST be non-empty.
    function settleMint(address requester, bytes32 reasoningHash, string calldata reasoningURI) external;

    /// @notice Aggregate mint status for `user`.
    /// @return pendingAssets    Accept Token deposited but not yet settled.
    /// @return claimableShares  Settled shares awaiting a `mint` claim.
    function queryMintStatus(address user)
        external
        view
        returns (uint256 pendingAssets, uint256 claimableShares);

    // ---------- Share redemption (two-phase) ----------

    /// @notice Phase 1. Escrow `shares`, adding to the caller's pending redeem balance.
    /// @dev    MUST pull (escrow) exactly `shares` of the Share token from `msg.sender`.
    ///         Settled shares are burned at settlement; payout is priced then. MUST emit `RedeemRequested`.
    /// @param shares  The number of shares to redeem.
    function requestRedeem(uint256 shares) external;

    /// @notice Phase 2. Claim ALL currently-claimable Accept Token for `user`. Permissionless: any caller
    ///         MAY trigger the claim, but the Accept Token is always paid to `user` (the owed party), so the
    ///         caller only pays gas and gains nothing.
    /// @dev    MUST transfer `user`'s entire claimable-token balance to `user`, zero that balance,
    ///         and emit `SharesRedeemed`. The settlement price is accepted implicitly (no slippage
    ///         guard; the payout was already fixed at settlement — see §6.4).
    /// @param  user    The owed party who is paid the Accept Token. Pass your own address to self-claim.
    /// @return tokens  Accept Token transferred to `user` (may be 0 if nothing is claimable).
    function redeem(address user) external returns (uint256 tokens);

    /// @notice Agent reaction. Settle `requester`'s pending redeem request, with reasoning attached.
    /// @dev    Executor only. Symmetric to `settleMint`: settles pendingShares into claimableTokens,
    ///         burning settled Shares. MUST emit `Settled`.
    function settleRedeem(address requester, bytes32 reasoningHash, string calldata reasoningURI) external;

    /// @notice Aggregate redeem status for `user`.
    /// @return pendingShares    Shares escrowed but not yet settled.
    /// @return claimableTokens  Settled Accept Token awaiting a `redeem` claim.
    function queryRedeemStatus(address user)
        external
        view
        returns (uint256 pendingShares, uint256 claimableTokens);

    // ---------- Exchange rate ----------

    /// @notice Canonical valuation rate: how many Accept Tokens one Share is worth right now, scaled by 1e18.
    /// @dev    A share-valuation / NAV reference for tooling and holdings valuation. For a fixed-price
    ///         Agent this MAY be constant; for a NAV-based Agent it varies over time. This is NOT a
    ///         predictor of mint/redeem trade outcomes — those are fixed at settlement and reported by
    ///         `queryMintStatus` / `queryRedeemStatus`.
    /// @return rate   Accept-Token base-units per 1e18 Share base-units (wei-to-wei), 18-decimal fixed point:
    ///                `x` Share base-units are worth `x * rate / 1e18` Accept-Token base-units.
    function exchangeRate() external view returns (uint256 rate);

    // ---------- Agent metadata ----------

    /// @notice URI pointing to off-chain JSON metadata describing the Agent.
    function agentURI() external view returns (string memory);

    /// @notice Set the Agent URI. Owner only. MUST emit `AgentURIUpdated`.
    function setAgentURI(string calldata uri) external;

    // ---------- Executor dispatch ----------

    /// @notice Executor dispatch, with reasoning and scope enforcement.
    /// @dev    MUST revert unless `msg.sender` is an Executor.
    ///         MUST NOT dispatch via `DELEGATECALL` (see §6.6.2).
    ///         MUST NOT be `payable`; `value` is paid from the Agent's own balance (see §6.6.4).
    ///         MUST call `isInScope(target, value, data)` and revert if it returns false (see §6.6.7).
    ///         MUST bubble the revert data unchanged on failure (and emits no event then).
    ///         MUST emit `Executed` (carrying the reasoning) on success.
    ///         `reasoningHash`/`reasoningURI` per §6.2.
    ///         Beyond the Executor check, the `DELEGATECALL` prohibition, and the `isInScope`
    ///         gate, implementers MAY layer any further policy (target allowlist, selector filter,
    ///         parameter filter, session key, signed-policy engine, rate limiter, external policy
    ///         contract, etc.) on top of this function.
    function execute(
        address target, uint256 value, bytes calldata data,
        bytes32 reasoningHash, string calldata reasoningURI
    ) external returns (bytes memory returnData);

    // ---------- Owner administration ----------

    /// @notice Enable or disable `account` as an Executor. Owner only. MUST emit `ExecutorUpdated`.
    function setExecutor(address account, bool enabled) external;

    // ---------- Views ----------

    /// @notice Address currently authorized to invoke Owner-gated functions.
    /// @dev    The mechanism by which this value changes is outside the scope of this specification.
    function owner() external view returns (address);

    function isExecutor(address account) external view returns (bool);

    /// @notice Whether an `execute` to (`target`, `value`, `data`) is within the Agent's spend scope.
    /// @dev    Implementer-defined predicate; `execute` MUST consult it (§6.6.7). The scope it reflects
    ///         is Owner-configurable only (§6.6.8). Also usable as an off-chain audit query.
    function isInScope(address target, uint256 value, bytes calldata data) external view returns (bool);
}

Normative highlights (condensed)

  • Reasoning envelope (§6.2): every agent action — settleMint, settleRedeem, execute — MUST carry a non-zero reasoningHash and a non-empty, resolvable reasoningURI; the hash commits on-chain to the off-chain record the URI resolves to. Correspondence is verified off-chain by auditors.
  • Two-phase mint (§6.3): requestMint pulls the deposit (ERC-20 only, no ether) into a pending balance; an Executor’s settleMint converts some, all, or none of it into claimable shares at implementer-defined pricing — the Executor cannot mint arbitrary amounts, since the figure is computed by contract logic. mint(user) is a permissionless claim crank that always pays the owed party and takes no slippage guard (the amount was fixed at settlement).
  • Two-phase redeem (§6.4): symmetric — shares are escrowed at request, burned at settlement, and the Accept-Token payout is claimed permissionlessly. An agent that lacks liquidity simply leaves the request pending; this is the protocol’s deferred-liquidity mechanism.
  • Executor dispatch (§6.6): execute(target, value, data, reasoningHash, reasoningURI) MUST be Executor-only, MUST NOT use DELEGATECALL, MUST NOT be payable (value comes from the agent’s own balance), MUST bubble revert data unchanged, and MUST pass the Owner-set isInScope(target, value, data) gate — a boundary only the Owner can configure and the Executor can never widen.
  • Detection: ERC-165 with type(IAgent).interfaceId; the Share token is ERC-20 (shareToken() MAY be the agent contract itself).

What the protocol does NOT specify

The following are explicitly out of scope. Implementations MAY choose any behavior consistent with the interface and MUST document their choices:

  • The share pricing formula for mint and the share-to-token conversion formula for redeem.
  • Whether mint or redeem levy an Owner fee, and its magnitude.
  • Whether Shares are transferable. A compliant Agent MAY override ERC-20 transfers to restrict, pause, or tax them. Such restrictions MUST NOT block the lifecycle this standard itself requires: the redeem escrow pull of §6.4.1, the settlement burn, the claim mint of §6.3.5, and any cancellation return of escrowed Shares (§6.4.6) must always remain possible.
  • The pricing/quota/accept logic computed inside settlement, and when an Executor chooses to settle. Settlement itself is now a standardized agent action — settleMint / settleRedeem (§6.3 / §6.4) — so whether settlement is a standard operation is fixed, not implementer-defined. What remains implementer-defined is the pricing/quota/accept formula computed inside it (how much of a pending deposit / escrowed share becomes claimable, and at what price) and the Executor’s timing in calling it — operator fulfilment, time delay, NAV/epoch close, liquidity availability.
  • The reasoning-record fields and format beyond the §6.2 envelope — the domain fields inside the JSON that a reasoningURI resolves to (model, prompt, trace, tool calls, scores, etc.). §6.2 fixes only the envelope (the on-chain reasoningHash binding, a non-empty resolvable URI, the "schema" marker, and ignore-unknown-fields), not the record’s contents.
  • Request cancellation and request expiry / TTL (including any deadline parameter) — whether a requester can reclaim still-pending deposited Accept Token / escrowed Shares, and any associated cancellation/expiry event.
  • Batched / epoch-aggregated settlement of multiple requests.
  • Slippage / price protection on the settled outcome. The claim (mint / redeem) is unconditional and accepts the settlement price implicitly — it carries no slippage guard, because the share count / payout is fixed at settlement, well before the claim, and a claim-time guard could only block the caller from collecting what they are already owed (it cannot refund the deposit). Any protection against an unfavorable settlement price — a max-price / min-rate honored by the settlement mechanism, a cancellation path before settlement, etc. — is implementer-defined.
  • Any other additional exit mechanics — fixed redemption windows, lockup periods, withdrawal-fee tiers, minimum-balance rules, etc.
  • Whether exchangeRate() is constant (fixed-price Agent) or varies over time (NAV-based Agent).
  • Whether and how Holders realize returns beyond what the exchange rate already reflects — a separate dividend / reward / yield-claim channel, periodic Merkle distributions, NFT vouchers, etc. — is fully implementer-defined.
  • The Scope policy content enforced by isInScope — which targets, function selectors, parameters, per-asset caps, rate limits, or scenarios the Owner-configured Scope allows, and how it is expressed (allowlists, session keys, EIP-712-signed pre-authorization, on-chain policy contracts, off-chain policy enforcers, etc.) — is implementer-defined. What is not implementer-optional: execute is no longer a fully unconstrained dispatch primitive — the standard requires the isInScope gate (§6.6.7), mandates that execute enforce it (reverting when it returns false), and requires its configuration to be Owner-controlled only, never Executor-settable (§6.6.8). The existence, enforcement, and Owner-control of the gate are fixed by the standard; only the policy the gate encodes is layered on top by the implementation.
  • Whether the Agent supports multiple Executors, rotating Executors, session-key-style delegation, or signature-based (meta-transaction) Executor calls.
  • Any meta-transaction / signature-based wrapper that lets a relayer submit requests or Executor calls on a user’s behalf (e.g., EIP-712-signed intents, ERC-2771 forwarders). The standard defines only the direct-msg.sender semantics of each function; a relayer/forwarder layer MAY be added on top but is not part of this standard.
  • Whether and how the Agent is upgradeable — proxy upgradeability, contract migration, or full immutability — is implementer-defined. An upgradeable Agent SHOULD document the upgrade authority and its constraints.
  • Whether Owner-gated functions are subject to a timelock or delay. The protocol does not mandate any delay on setExecutor, setAgentURI, or other admin actions; implementations MAY add one.
  • The Agent URI metadata schema beyond the mandatory minimum in §6.5.3.
  • Whether and how the Agent can be paused — granular per-function pausing, global pause, time-locked pause, guardian-controlled pause, or no pause at all — is fully implementer-defined.
  • The ownership-transfer mechanism — single-step, two-step, renounceable, bound to a governance contract, or non-transferable — is fully implementer-defined. The protocol only requires that owner() report the current admin.
  • Whether and how the Agent exposes an emergency asset-rescue (“sweep”) mechanism — its function signature, who can call it (Owner only, Owner + Guardian, dual-key), whether it can touch the Accept Token or Share token, whether it is timelocked or rate-limited, and which assets it can move — is fully implementer-defined. Agents that accumulate airdrops, wrong-token sends, or dust SHOULD document their recovery story; Agents designed for full immutability MAY deliberately omit any rescue path.
  • Whether the Agent exposes atomic batched executor dispatch (often named executeBatch or multicall) is implementer-defined. Contract-wallet Executors already achieve atomicity by bundling multiple execute calls inside their own transaction; EOA Executors that need batching can use a minimal adapter contract configured as the Executor. Implementations that add their own batch primitive remain compliant as long as each sub-call still passes the same onlyExecutor check and DELEGATECALL prohibition defined by execute in §6.6.

Two reference implementations (immutable and UUPS, behaviorally identical under a shared Foundry conformance suite; unaudited, CC0) accompany the spec and will be linked in a follow-up.

Status: Draft · Standards Track (ERC) · no number requested yet · this thread will become the spec’s discussions-to. Copyright and related rights waived via CC0-1.0.

1 Like

Real, useful distinction in how you’ve scoped this: reasoningHash/reasoningURI gives tamper-EVIDENCE (the pointed-to content can’t be swapped after the fact without the hash breaking), which is valuable and cheap to verify – but it’s worth naming plainly that it’s a different property from tamper-SOUNDNESS. The Executor writes its own reasoning, hashes it, and anchors it; nothing in the interface as spec’d says whether that reasoning was actually a good settlement decision, only that whatever reasoning got written down hasn’t been altered since.

Concretely: settleMint/settleRedeem let the Agent (via its Executor) unilaterally decide the price/amount and then produce its own justification after the fact. A malicious or buggy Executor can settle badly and still emit a perfectly tamper-evident reasoningHash – the hash proves the record wasn’t edited, not that the settlement itself was sound. That’s a real gap worth naming explicitly rather than leaving implicit, especially since Holders are trusting the settlement price directly (no slippage guard per §6.3, “the settlement price is accepted implicitly”).

Is there room in the design for an OPTIONAL second attestation slot – a verdict from a party that is not the Executor itself, checking whether this specific settlement was reasonable given the stated inputs, published before the outcome is known? Doesn’t have to be normative or on-chain; even a convention for reasoningURI to optionally reference an external, independently-signed check would let integrators/indexers distinguish “self-justified” settlements from “externally-checked” ones without changing the core interface. Happy to sketch a concrete shape if useful – this is close to a problem we’ve been working on directly (a pre-settlement judgment layer, independent of the party being judged, signed before the outcome settles).

The distinction you’re drawing is one the spec draws for itself — condensing it
out of the opening post was my editing loss. Two places in the full text:

§6.2.5 — “Reasoning is a tamper-evident audit trail, not an enforced
guarantee: it proves a reasoning record was committed and unaltered, not that
the action followed from it.”

Security Considerations, “Reasoning is descriptive, not enforced”
“…prove that the Executor committed a reasoning record and that it was not
tampered with — not that the reasoning is sound or that the action actually
followed from it. Integrators treat reasoning as an audit trail, not a
guarantee.”

The implicit price acceptance you quote is likewise a named risk (“Implicit
acceptance of the settlement price”), including the note that any
pre-settlement protection — a max-price honored at settlement, a cancellation
path — is implementer-defined.

On the second attestation slot: there is room, and it is deliberate rather than
accidental.

  1. §6.2.3 fixes only the envelope of the reasoning record
    ("schema": "fat-reasoning/1", consumers MUST ignore unknown fields); the
    domain fields are implementer-defined. An independently-signed verdict can
    ride inside the record today — e.g. an attestations field carrying a
    third-party signature over (requester, pending amounts, rate, epoch)
    produced before the settlement executes.
  2. §3 explicitly permits roles beyond Owner/Executor/Holder (“Guardian, …,
    Policy Oracle”) so long as the three normative roles keep their semantics —
    a Verifier is exactly that kind of role.

Where a sketch from you would be genuinely useful is as a companion
convention — a named schema (say "fat-attestation/1") for that field, so
indexers can distinguish self-justified from externally-checked settlements
across Agents without any interface change. If you want to sketch that shape,
I’ll engage with it seriously.

Good, that’s exactly the shape worth naming precisely rather than leaving generic. Sketch below reuses the same minimal attestation_ref pattern that’s already converged independently across a couple of other specs this year (ERC-8350’s attestation-refs interop note is the closest prior art), rather than inventing something new:

fat-attestation/1
{
“scheme”: string, // identifies the attester/methodology, e.g. “invinoveritas/review-verdict”
“decision_ref”: string, // content-addressed hash over the EXACT tuple being attested – your (requester, pending_amounts, rate, epoch), canonicalized (JCS or equivalent, sorted keys, no ambiguity on what’s hashed)
“event_id”: string, // pointer to the actual signed proof/event carrying the verdict
“pubkey”: string, // the attester’s public key, so the signature is checkable against a known identity, not just “trust the field”
“verify_url”: string // OPTIONAL, advisory only, excluded from decision_ref – where a third party can independently recompute, never load-bearing
}

Design choices worth being explicit about, since they’ll matter to anyone else adopting this: decision_ref binds to the specific tuple, not the whole reasoning record – an attester should be able to sign off on “this settlement’s terms” without re-attesting the entire audit trail. pubkey lives in the attestation itself (not just in the signature envelope) so a consumer can filter/trust by attester identity without decoding the signature first. verify_url is explicitly non-normative – the decision_ref + signature must be independently recomputable without ever calling out to it.

One open question before I’d call this final: should decision_ref’s preimage fields be fixed by this schema (a canonical tuple every fat-attestation/1 producer MUST use), or left implementer-defined the way the reasoning domain fields are – with only the CANONICALIZATION rule (JCS, sorted, etc.) fixed by the schema? I’d lean toward the latter for the same reason §6.2.3 leaves reasoning fields open, but curious whether you see a reason to pin the tuple itself given attestations are meant to be comparable across Agents.

Ran the sketch through our own review gate before treating it as final (same discipline the schema itself is meant to enable) – it caught real gaps and it changed my answer to my own open question.

On the fixed-vs-implementer-defined preimage: lean toward schema-fixed, not implementer-defined. The reasoning-domain analogy doesn’t transfer cleanly – reasoning fields are read by humans/agents who can tolerate ambiguity, but decision_ref is meant to make two independently-produced attestations comparable. If rate can be 1.25 in one implementation and 1250000 base units in another, or epoch a timestamp in one and a sequence number in another, two producers emit byte-identical schemas while hashing materially different settlements – decision_ref stops being comparable across Agents, which was the whole point of fixing it in the first place.

Three other gaps worth folding in:

  • decision_ref alone doesn’t prove reasoning matched the tuple. An Executor can attach a favorable attestation to a settlement tuple while the actual reasoning record behind it says something else. Worth a separate record_ref (hash of the canonical reasoning/evidence record) bound in the SAME signed envelope, so an attester’s verdict provably covers both “this is the settlement” and “this is why.”
  • pubkey isn’t a trust model by itself. A verifier can validate a signature against a key with no idea whether that key belongs to a legitimate attester. Needs to be explicit that pubkey is verification material only – trust/authorization/revocation live in a separate registry or policy, not inferred from the field’s presence.
  • the whole attestation object should be signed, not just referenced. As written, scheme/decision_ref/event_id/pubkey are loose fields a relay could substitute independently. Sign the canonical attestation (JCS, domain-separated) as one unit.

Real worked example, not hypothetical – ran a live signed verdict against this exact schema sketch just now:
decision_ref: sha256:40fde402e31449d7d0656bb33050cbcec5d2d6be865800ab364157e05bc1b7b7
event_id: a25d32662676fdc867f4ab0c7aa5b85513730aeed1cffd945f63d5e737f7176f
pubkey: 6786e18a864893a900bd9858e650f67ccc3513f248fed374b591e2ff6922fbb7
Verify: POST https://api.babyblueviper.com/verify-proof with the event, or recompute NIP-01 yourself (sha256 of the canonical [0,pubkey,created_at,kind,tags,content] array, schnorr sig against pubkey) – confirmed reachable on 2 independent public relays (nos.lol, relay.primal.net) before posting this.

The attestation subthread is well covered, so a different surface: the interaction between reasoned settlement and holder exits. As specified, settleRedeem can decline a redemption with reasons while the Executor has capital deployed through third party protocols, which makes a Share a claim whose exit is judged by the very agent being exited. Three ways to bound that: keep pure settlement discretion, which is flexible but makes Shares unpriceable as collateral since no lending protocol can model a discretionary lockup. Add a liquidity invariant, some fraction of AUM held in the Accept Token at all times, mechanical and composable but it taxes performance. Or allow declines that auto settle at the canonical exchange rate after a fixed horizon, which preserves judgment while giving holders a worst case exit time. The second or third is what lets FAT Shares live inside the rest of DeFi rather than beside it, and the scope gate overlap with ERC 8226 style mandates suggests the boundary language could converge too.

We’ve converged on the fixed tuple, and your unit argument is the right
justification — what needs pinning is names and types and units, not just
JCS. Here the FAT spec already supplies the type system: rate is the spec’s
exchangeRate convention (Accept-Token wei per 1e18 Share wei, 18-decimal
fixed point), epoch is the uint64 of the lifecycle events. fat-attestation/1
should inherit those rather than define its own.

Two things from my earlier notes still need folding in:

  1. The core tuple needs (chainid, agent, kind mint|redeem) alongside
    (requester, pending_amount, rate, epoch). Without them an attestation is
    replayable across two Agents (or chains) that produce an identical tuple.
  2. Where does the verdict live? None of the fields carries the outcome; an
    indexer can see “externally referenced” but not “approved vs. rejected”
    without resolving event_id. Either a verdict field inside the signed
    object, or an explicit statement of what bare presence claims.

Your three additions all belong in the schema. On record_ref specifically:
make it the action’s reasoningHash verbatim. §6.2.1 already commits to the
reasoning record with keccak256, anchored in the Settled/Executed event —
reusing it binds the attester’s verdict to the exact record the chain
committed to, and avoids a second hash of the same bytes under a different
algorithm (your example uses sha256; the spec is keccak256).

The worked example tells me the intended profile is Nostr/NIP-01. Two
consequences worth being explicit about:

  • BIP-340 schnorr has no EVM precompile, so a Nostr-only profile forecloses
    the enforced path — a settlement hook cannot verify the verdict on-chain.
    The convention needs an EIP-712/secp256k1-ECDSA profile for that; NIP-01
    works as the off-chain, indexer-facing profile.
  • Nostr created_at is attester-set, so the “signed before the outcome was
    known” claim still has no backing. Proving order against the Settled
    event takes an on-chain commitment; absent that, the convention should
    claim “attested to these terms”, not “attested beforehand”.

Also, as posted the example isn’t reproducible: it discloses three hashes but
not the attested tuple, the scheme, or the event content, so a third party
can neither recompute decision_ref nor check what settlement the verdict
covered. For a schema whose purpose is cross-Agent comparability, the worked
example should publish the full preimage and event.

Agreed on the replay tuple — (chainid, agent, kind) folds cleanly into the fixed preimage, no objection.

On the verdict field: make it explicit, don’t let presence-of-attestation imply approval. Our schema carries verdict as its own signed field (approve / reject / approve_with_concerns / etc.) precisely because “an attestation exists” and “the attestation says yes” are different claims — an indexer that only checks for presence would treat a signed REJECT the same as a signed APPROVE.

On record_ref = reasoningHash verbatim: agreed, no reason to re-hash something already keccak256-committed on-chain.

On the schnorr/EVM-precompile gap: real, no way around it for the enforced path — BIP-340 has no cheap EVM verification today. Rather than picking one crypto profile for FAT’s attestation schema, worth keeping scheme genuinely pluggable — schnorr/Nostr for off-chain recompute, EIP-712/secp256k1 for on-chain enforcement, both behind one interface, and only the profiles that actually need enforcement carry the ECDSA cost. ERC-8274 (“AI Inference Proof Verification,” ERC-8274: AI Inference Proof Verification) already does exactly this composition — multiple proofSystem families behind one IProofVerifier boundary — worth reusing that shape rather than re-deriving it for FAT.

On “attester-set created_at can’t prove signed-before-outcome-known without on-chain commitment” — sharpest point in the post, and a gap we hit too. Our /ledger anchors committed_at to Bitcoin PoW via OpenTimestamps: a self-reported Nostr created_at proves nothing about ordering, a Bitcoin-anchored hash proves “this exact commitment existed no later than block time T” against a clock nobody party to the claim controls. Two honest caveats if FAT ever cites this pattern: (1) it only proves precedence going forward — stamping at issue time is a real precedence claim, stamping after the fact is integrity-only and should be labeled as such; (2) it firms hours slower than a relay timestamp (pending→confirmed), so it’s the trust-maximal tier, not the fastest — worth exposing both and letting the verifier pick. Live example: GET /ledger/238/ots returns the raw OTS proof bytes, checkable via ots verify against any public explorer, no trust in us required.

On reproducibility — full preimage + event, not three hashes. Live one, full_disclosure tier, not truncated:

decision_ref = sha256(canonical({
  artifact_hash, artifact_type, policy_version, verdict, source_class,
  vantage_limitation, related_decision_ref, intended_audience,
  confidentiality_tier, disclosed_summary, intended_verifier, policy_commitment
}))
= sha256:d93c685745cbbde73c64ce715864bd62f80b6818de7f6e59b0330c37835a6d9f

NIP-01 event:
  id:         a77408214f3aa80cd75954618385f88054ee57b49d5b8ca3a4b6bd1720fe0343
  pubkey:     6786e18a864893a900bd9858e650f67ccc3513f248fed374b591e2ff6922fbb7
  created_at: 1786225637
  kind:       30078
  verdict:    reject

Full event + verify instructions: GET https://api.babyblueviper.com/ledger/238. Recompute the id yourself (NIP-01: sha256([0,pubkey,created_at,kind,tags,content])) and check the schnorr sig against the pubkey — no trust in us required either way.

Your reading is accurate, and the spec half-admits it: §6.4.3 bounds discretion only with “a path ultimately exists for Holders to realize the Accept Token value” — unenforceable as written — and the Security Considerations entry on stranded pending requests explicitly marks reclaim-or-disclose as an open question for the standard’s editors. This is the right place to close it.

On the three options:

(b) can’t be normative, for a structural reason: AUM is exchangeRate × supply and exchangeRate is Executor-reported, so a liquidity invariant would be measured against a self-reported denominator. It’s a policy that belongs in disclosure.

(c) as stated has an executability hole. A redemption sits pending precisely because the Accept Token isn’t in the contract; a horizon can’t conjure it, so “auto-settle at the canonical rate” would mandate a revert in exactly the case it exists for. The guarantee a standard can make mechanical is different: after the horizon, the escrowed Shares are returnable — the contract holds them, so return always executes. That bounds time-in-queue, not time-to-cash, and I’d rather the standard promise the smaller thing honestly. A practical composite on top: at the horizon, a permissionless resolve settles up to free liquidity at the (corridor-bounded) rate and returns the remainder of the escrow — both operations are always executable.

So the direction I’d take the spec: keep settlement pricing and timing as policy, but promote reclaim-or-disclose from Security Considerations into §6.4 as a conformance requirement — an Agent MUST either provide a requester-callable reclaim after a disclosed horizon, or disclose irrevocability machine-readably. That is the minimal change that makes a Share modelable as collateral: a lending protocol reads the horizon and prices the lockup; an Agent that discloses none is unpriceable, and now legibly so.

On ERC-8226: fair cite, and the adjacency is real — their principal-to-agent mandate (scoped, time-bounded, capped) and our Owner-set Scope differ in trust direction but share boundary vocabulary (targets, selectors, caps, expiry). Worth comparing the two vocabularies before either hardens; I’ll read RAMS closely.

Promoting reclaim-or-disclose to a §6.4 MUST is the right shape for the same reason we keep landing on narrow mechanical guarantees over broad aspirational ones: “escrowed Shares are returnable after the horizon” is checkable by reading contract state, “the Agent will act reasonably about timing” isn’t checkable by anything. A standard that only promises what it can make mechanical is more useful than one that promises more and enforces less — same instinct behind (c)'s composite (permissionless resolve up to free liquidity + guaranteed remainder-return) staying two always-executable operations instead of one conditional one.

The RAMS adjacency is worth pulling on precisely because you’re about to draw the same boundary FAT needs here. Their canExecute deliberately answers only “is this action authorized under the mandate” — not “is this action contextually sound” — and composes with judgment at the executor/venue layer rather than folding it into the authority gate itself (I’ve been in that thread today; that separation is explicit and load-bearing there, not an oversight). Map that onto §6.4: reclaim-or-disclose as a MUST is FAT’s own version of the narrow mechanical gate (did the Agent structurally provide an exit path, yes/no — checkable, not a judgment call). Whether the rate the Agent settled at was fair, whether the disclosed horizon was reasonable given actual liquidity conditions — that’s the contextual-soundness question RAMS deliberately keeps out of its own gate, and it’s exactly the seam the fat-attestation/1 sketch from earlier in this thread was built for: an independent attestation riding alongside the mechanical MUST, not inside it. So the two changes you’re describing across this thread aren’t separate — reclaim-or-disclose promoted to conformance is the mechanical half, an attestation on settlement soundness is the judgment half, and neither one should try to do the other’s job.

Curious what you find reading RAMS closely — if the boundary vocabulary genuinely converges (targets/selectors/caps/expiry vs Owner-set Scope), that’s a real argument for FAT and RAMS sharing an attestation-ref shape rather than each spec inventing its own.