ERC-8447: Swarm AI Framework

Summary

We propose a registry and charter model through which autonomous agents — and any other account that qualifies — form, bound, run and dissolve swarms: purpose-bound collectives with an explicit membership boundary, an objective with attainment rules and a declared collaboration structure, recorded as one interoperable on-chain object.

Agent standards so far cover the individual (identity, reputation, validation), the act (one coordinated action among fixed participants), the decision (policies, bodies, finality) and the job (one task, one provider). None of them says what a group of agents is: who is inside now and under which commitment, why the group exists, how it is organized, and when it stops existing. Every pipeline that needs that today redefines it in application code or in a chat protocol, and no counterparty — a task tender, a treasury, an indexer — can read it.

Draft text: swarm-ai-framework/ERCS/erc-sai.md at main · garyyang-finchip/swarm-ai-framework · GitHub
Reference implementation, schemas, vectors and tests: GitHub - garyyang-finchip/swarm-ai-framework: Swarm AI Framework · GitHub
ethereum/ERCs pull request: Add ERC: Swarm AI Framework by garyyang-finchip · Pull Request #2081 · ethereum/ERCs · GitHub

What the ERC fixes

  • Two commitments, one object. Everything the registry executes — roles with eligibility modules, phases with their posture, one authority per decision point, activation rule, contribution mode, fallback timers — is a hashed struct, specHash = keccak256(abi.encode(SwarmSpec)), frozen at formation; structural change is succession. Everything that is described rather than executed — the charter text, the objective descriptor, the allocation rule, the resource manifest — is an off-chain Swarm Pack, charterHash = SHA-256(JCS(root)), versioned.
  • Two-sided admission. An authority admits, the member accepts the current charter under the current commitment version; either order; signed acceptance (EIP-712 / ERC-1271) binds the charter hash, the seat epoch and the phase. Leaving is always available. Anyone may challenge a member against its role’s eligibility module; false suspends, a revert changes nothing; expulsion follows a grace period; substitution replaces a seat even in a closed phase.
  • Time is a guard, never a success criterion. Objectives, rosters and decisions end a swarm; every state, including forming, has a permissionless exit after a declared fallback, and a spec without one is invalid. Lifecycle state, outcome type with reason, and settlement are three separate dimensions.
  • Posture is a coordinate per phase, not a mode enum: boundary (OPEN / GATED / CLOSED) and admission (RECRUIT / INHERIT / SELECT) are executed; payoff, topology and coordination are declared as terms of a write-once vocabulary kept by the registry (shared / exclusive / mixed; redundant / partitioned / sequential / hierarchical / iterative / free; leader / collective / market / automated / none; anyone may register further terms). Cooperation, competition, co-opetition, tenders, staged elimination, debate, ensembles and pipelines are points and trajectories in this space.
  • Amendment in two classes. Minor amendments change the text within what members committed to; material amendments increment the commitment version, open an exit window and hold pending admissions until the candidate accepts the new commitment.
  • Decisions are an adapter, not kernel logic. Any decision point may be held by a relying contract of ERC-8436; the reference adapter snapshots the deciding role as a policy version per decision, files the exact call as the issue subject and enacts only Final x Yes. NoDecision is the status quo.
  • The kernel holds no value and does not know what an agent is. Members are accounts; agent-ness, trust level and identity state are eligibility predicates read live from the registries that hold them (ERC-8004 among them). Non-agent elements — executable skills, datasets, code, models, documents, oracles, funds — are resources: attached by kind, optional anchor, digest and digest kind, provider and role scope; they hold no seat and receive no allocation; a per-kind adapter may verify anchors at attach time.
  • Obligation binding. A swarm may bind its objective to an external duty such as a token-bound task tender; the swarm account is the fulfiller of record and an obligation adapter concludes the swarm from the tender’s record.

requires: 165, 712, 1271, 6372, 8004, 8436. ERC-8001 (coordinated acts), ERC-8063 (token-gated groups) and ERC-8444 (discovery) are positioned informatively.

Reference implementation and evidence

Non-upgradeable registry (16,379 B) with three external logic libraries and a spec validator, a swarm account acting under a REPRESENT authority, an ERC-8436 decision adapter, a task-tender adapter and five eligibility modules (allowlist, composite, ERC-8004 identity, trust-assertion level, identity state). 85 Foundry tests, including two end-to-end cases run against the real ERC-8436 reference registries and a real token-bound task tender kernel: a tender-bound cooperative swarm paid through its account, and a two-phase tender (open bidding, selected execution) with a challenge and a substitution. Deterministic vectors (specHash, EIP-712 Join digests, charter hashes from two independent canonicalizers, term identifiers, ERC-165 ids) are re-derived in Solidity. Sepolia deployments of both cases follow.

Questions for the thread

  1. Vocabulary rather than enums for declared posture. The three declared dimensions are registered terms, permissionless and write-once. Is a per-instance vocabulary the right granularity, or should base terms be the only normative ones and extensions purely off-chain?
  2. Two classes of amendment, declared by the authority. The registry records the class and never second-guesses it. Would reviewers prefer a stricter rule — for instance, any change of objectiveHash is material by construction?
  3. Roster snapshots per decision. ERC-8436 freezes a policy’s roster; the adapter files one policy version per decision from the deciding role’s current roster. Is there appetite for a lighter convention (a shared policy per phase) that trades precision for fewer policy registrations?
  4. Scope of requires. ERC-8004 is required because the agent-identity profile is normative-optional and its registry is the main audience. Should it be informative instead?
  5. Resources. Digest kinds are open (sha-256, keccak256, adapter-defined). Is an open set acceptable for a normative record, or should the ERC fix one and let adapters translate?

Feedback on the state machine, the lazy roster-wide exits and the security considerations (sybil rosters, griefing through fallbacks, decisions in flight, allocation drift) is especially welcome.

2 Likes

What prevents the amendment authority from changing the allocation rule and classifying it as minor after members have already contributed work?

The registry cannot judge the meaning of charter text. Could the fields that define members’ economic commitments be committed separately so that changing them automatically triggers the material-amendment path? Otherwise the exit-window protection seems to depend on the same authority whose changes it is meant to constrain.

1 Like

Good split between the hashed spec and the charter pack, it keeps the part that actually executes small enough to review.

I read the challenge path against your Security Considerations. You already put module manipulation on the module author, fair. Two things still sit at the registry level though.

  1. Gas. A reverting module means nothing changes, good. But compositions often wrap each registry read in try/catch and return false when it fails. Then a challenger can pick a gas limit where the outer call survives and the inner read runs out (the 63/64 rule), and an honest member ends up SUSPENDED(INELIGIBLE). Checking gasleft() after the module call and treating a low remainder as unevaluable fixes it for every module at once.

  2. expel never reads the module again. An identity that lapses for a day during a renewal gets challenged, comes back true, and the member is still expelled after ineligibleGrace unless the REINSTATE holder acts in time. I’d re-evaluate at expel and revert on true, or let the member reinstate themselves when the module answers true. It also escalates: one suspension can drop a role under min, and then anyone can call enforceRoster on the whole swarm.

1 Like

Could the “Settlement after dissolution” section clarify how recovery depends on the REPRESENT authority type? Anyone can call dissolve() after concludedAt + settlementWindow, and dissolution makes all remaining candidates and members EXITED. Since SwarmAccount.execute() relies on isAuthorized(swarmId, REPRESENT, caller), assigning REPRESENT to ROLE or ANY_MEMBER leaves nobody authorized to execute calls after dissolution, potentially stranding assets. However, the authorization logic lets ADDRESS and INITIATOR retain authorization independently of membership. The section’s statment that the reference account stops acting once nobody is a member therefore seems too broad.

Is this difference intentional? I would keep dissolution independent of settlement completion to preserve the liveness fallback, but perhaps the account profile could explicitly define authorization for recovery or withdrawals after dissolution without relying on active membership. Otherwise, two conforming swarms using the same account implementation could have very different asset recovery behavior solely because of their REPRESENT authority type.

1 Like

@SergeevDmitry Nothing prevents it today, and you have put the finger on exactly the dependency behind question 2 of the opening post: the exit window guards members against the amendment authority, but the authority alone classified the amendment, and the allocation rule it could quietly change lived in the pack where the registry cannot see it.

Committing the economic fields separately is the right fix, and it generalizes the objectiveHash case. Revision 2 of the draft, now on the PR branch and in the repository, does this: the Swarm Pack gets a commitment member — the allocation rule per phase, costs, liabilities, data access, and the actions taken in the members’ name — and commitmentHash (SHA-256 over its canonical form) is committed at formation next to objectiveHash. amend takes both hashes; any change to either makes the amendment material by construction, with the commitment version incremented and the exit window opened. The material flag survives only as a one-way override: an authority can declare a textual change material, and can never declare a change of objective or commitment minor. The registry still never reads charter text; what it protects is a structural fact that anyone verifies with one hash. The schema moves allocation out of the phase objects into commitment for that reason, and a charter that keeps economic terms in its prose instead has visibly put them outside the rule.

That also settles question 2: yes, stricter, and for more than the objective.

@Zealynx Thank you for reading the challenge path this closely. Both points stand, and both belong to the registry rather than to module authors.

On gas: the reference composite forwards inner reverts, so the registry already reported those as unevaluable; but the specification said nothing about a module that catches its own reads and answers false, which is the common way to write one, and the caller controls the gas. Revision 2 adds the remainder rule to challenge: a false returned with less than one eighth of the pre-call gas remaining is unevaluable and changes nothing. One eighth rather than one sixty-fourth because a catching layer keeps one sixty-fourth of its own budget, so a single catch returns to the registry with about two sixty-fourths of the pre-call gas, and nested catches add one sixty-fourth each; one eighth covers several layers, and an honest false that arrives with less simply asks the challenger to try again with more gas. A true needs no guard. The test suite now carries the attack: a module whose inner read costs a few million gas; without the rule, a sweep of gas limits from about 1.0M upward suspends an honest member, and with it every such call is unevaluable and the member stays active.

On expel: agreed that reading the clock alone contradicts “reinstate on recovery” whenever the REINSTATE holder is not watching. expel now re-evaluates the module: false expels as before, true reinstates the member instead and the call returns without exiting, an unevaluable answer leaves the suspension in place and reverts so the caller knows nothing was decided. challenge is symmetric for the same reason: anyone’s challenge of a member suspended for ineligibility reinstates it when the module answers true again, so the member itself, or anyone, can reinstate on evidence without an authority. The REINSTATE authority keeps its role for reinstatement on judgment.

On the escalation: the chain you describe is real, with one qualification that the text now makes explicit: enforceRoster suspends the swarm with ROSTER_LOST, and resume is permissionless once the roster holds again, so the effect is a pause rather than a termination. The pause still stops contributions and does not stop the inactivity clock, which is why the two paths above matter: they are what make it short.

@Revinand You are right on every point, and the sentence you quote was wrong as written. isAuthorized read membership only for ROLE and ANY_MEMBER; an ADDRESS or INITIATOR representative was authorized independently of the roster and kept acting through the account after dissolution, while a roster-based representative was lazily exited. Two swarms on the same account code therefore differed in recovery behaviour for no reason a counterparty could read — a defect of the profile, not a feature of the authority model.

Revision 2 fixes it at both levels. In the registry, isAuthorized returns false for every decision point and every kind of authority once the swarm is DISSOLVED: a representative’s power ends with the swarm, whatever kind it was. In the account profile, a conforming account must state its post-dissolution rule on-chain: recoveryAddress() returns the one account that may act through it once the swarm is dissolved, or zero when nobody may and whatever is left stays; the rule cannot depend on membership state or on the REPRESENT kind, can be changed only by a representative while the swarm lives, and is frozen with the swarm. The reference account takes the recovery address at deployment, lets a representative change it before dissolution, and afterwards accepts calls from that address alone. Dissolution stays independent of settlement — the permissionless exit is not negotiable — and distribution by representation happens in CONCLUDING, where the settlement window guarantees the time.

Thank you for the catch; both kinds of swarm now give the same answer to “who can recover what is left”, and a counterparty can read it before funding.

Thanks, that was quick, and the one eighth margin makes sense once you count nested catches.

One thing the evidence path adds. Section 6.4 lists token balances as a normal module input, and now a true answer reinstates from anyone’s challenge or expel, the member included. So a member suspended because their balance dropped under the threshold can flash borrow it, challenge themselves back to active and repay in the same transaction. Before revision 2 that needed the REINSTATE holder, and admission already has the same exposure. The Security Considerations cover a module made to answer false by its inputs but not one made to answer true, so I’d add that case and tell balance style modules to read a snapshot or a time weighted value instead of the live balance.