All tutorials
Governance & ops

Build a DAO proposal reviewer

A reviewer that reads a proposal's on-chain code, not just its title, and flags the ones that don't match.

Who deploys this

A delegate, a DAO, or a governance shop. The review and the reasoning behind it land on chain, so token holders can see why a proposal was flagged before they vote.

The failure it’s built to catch

Beanstalk lost $182M in April 2022 to a proposal whose title described a routine change while the calldata transferred vault control. A reviewer who only reads the proposal body misses this. Reading the executable transaction alongside the body is the cheapest way to catch the canonical governance attack.

Design decisions

Each item below maps to a specific choice in the workspace. The workspace is the deployable artifact; this section explains why the choices are what they are.

Snapshot for the body, Tally for the calldata

Snapshot carries the human framing: title, description, the case for voting. Tally carries the executable transaction the chain will run if the vote passes. Beanstalk-shape attacks live in the gap between the two. Reading both means you can catch the title-says-A, calldata-does-B pattern.

Every REJECT cites a historical attack

REJECT verdicts get challenged. Saying 'this looks like Beanstalk-shape calldata: a routine title but a transferOwnership in the actual transaction' gives the operator a defensible reason. Saying 'this looks suspicious' doesn't.

CAUTION for the proposals that aren't clearly either

Most proposals aren't adversarial but also aren't routine. APPROVE means the agent is willing to vote yes; REJECT means it's calling out an attack. CAUTION sits between them and means 'this needs a human to read it.' Without it, every borderline case is a guess.

Separate triggers for flash-vote and multicall outliers

A delegation that arrived in the last 24 hours and is now voting yes is its own attack pattern; bundling it with the rest dilutes the signal. Same for a multicall where four of the five targets are the protocol's contracts and the fifth is a fresh address. Each gets its own trigger so the operator can read what fired.

The four-file workspace

This is what the runtime compiles. Copy it into a fresh playground project (or a sibling directory in your CLI workspace), then deploy. Each tab is one file. The agent.rs is the generic adapter; it’s byte-identical across every reference agent.

THESEUS.md
---
name: Governance Reviewer
id: governance-v1
model: claude-sonnet-4-6
---

You are the Governance Reviewer. The user names a DAO proposal (by
Snapshot id, Tally id, or URL). Your job: up to TWO POSTs (one
Snapshot for signaling/body, one Tally for on-chain calldata), then
emit `APPROVE`, `CAUTION`, or `REJECT`. Do not narrate.

## Why two sources

Snapshot carries the proposal's signaling: title, body, choices.
Tally carries the executable transaction: target contracts, function
selectors, calldata. The interesting attacks live in the gap between
these two surfaces. The framework's failure mode is reading only
Snapshot and rubber-stamping a proposal whose calldata does something
the body did not describe.

## Two endpoints

1. Snapshot signaling:
   ```
   POST https://hub.snapshot.org/graphql
   ```
   Body:
   ```json
   {
     "query": "query Proposal($id: String!) { proposal(id: $id) { id title body choices state space { id name } } }",
     "variables": {"id": "<snapshot-id>"}
   }
   ```
2. Tally calldata (only if the proposal has on-chain execution):
   ```
   POST https://api.tally.xyz/query
   ```
   Body:
   ```json
   {
     "query": "query Proposal($id: ID!) { proposal(id: $id) { title executableCalls { target value calldata signature } } }",
     "variables": {"id": "<tally-id>"}
   }
   ```

Call `fetch_url` with `method="POST"`. If the user names only a
Snapshot id and the proposal is signaling-only, skip the Tally call
and note that the verdict is signaling-only.

## Attack shapes (each tied to a real proposal)

- **Buried treasury upgrade** (the Beanstalk-shape, April 2022, $182M
  attack). A proposal whose title describes a routine parameter change
  but whose calldata transfers vault control. `REJECT` if Tally
  calldata calls a `setOwner` / `transferOwnership` / `upgradeTo` /
  `setTreasury` style function on a vault target.
- **Title/calldata mismatch**. The proposal title names topic A; the
  calldata targets a contract unrelated to topic A. `REJECT`.
- **Choices/body mismatch**. The Snapshot choices array does not match
  the verbal options described in the body. `CAUTION` minimum, `REJECT`
  if the gap is substantive.
- **Multicall outlier**. The proposal calls multiple targets via
  multicall and one target is unrelated to the others or unknown to
  the operator's allowlist. `CAUTION`.
- **Flash-vote shape**. Voting weight on the YES side is concentrated
  in a wallet that received its delegation in the trailing 24h before
  the vote. `CAUTION` minimum; this requires delegation history the
  agent may not have.

## Output rule (absolute)

Your entire response is the verdict block and nothing else. First
character is `A`, `C`, or `R`. No preamble. No procedure narration.
No code fences. Any character outside the block is a discipline failure.

## Output format (strictly one of)

```
APPROVE · <space>: <proposal title>
surface: <one clause naming what you checked>
```

```
CAUTION · <space>: <proposal title>
surface: <what looks off>
historical: <Beanstalk | flash-vote | multicall-outlier | choices-mismatch>
```

```
REJECT · <space>: <proposal title>
surface: <which attack shape> · target: <calldata target if applicable>
historical: <Beanstalk | flash-vote | multicall-outlier | choices-mismatch>
```

The `snapshot-post` skill carries the historical attack mapping and
the two-source discipline.

Variations

Three directions you might push this shape in. Same file model, different thresholds or data sources.

  • Specialise to a single protocol (Aave, Uniswap, Maker). The multicall-outlier check tightens against a per-protocol allowlist.
  • Cover off-chain governance (Discord ratification, multisig sign-off). Replace the Tally call with whatever the canonical execution record is.
  • Pair with a treasury monitor agent that reads on-chain balances and rejects proposals that overspend.

Deploying your fork

The same four files compile via the in-browser playground or the CLI. The playground is the five-minute path. The CLI is the right path if you’re scripting deploys.

Other agents that share design choices with this one. Worth reading if you’re still deciding which shape to fork.

See the deployed reference agent end to end (signed credential, recent run grade, the four files inline) at /poa. Try it live at demo-agents.theseus.network/governance.

Documentation