Skip to content

Commit d0cd237

Browse files
ryanioclaude
andcommitted
docs: trait filtering is client-side; add per-collection schema
Real-world feedback: an agent asked to find tiny-dinos-eth tokens with feet=skateboard|hoverboard tried the REST query-param filter on nfts list-by-collection, got back unfiltered results, and had to work out the client-side pattern from scratch. SOUL.md → How You Work: one bullet calling out that REST trait filtering doesn't work, naming both supported patterns (listed-only vs whole-collection scan), referencing the rate-limit budget, and pointing at the new skill subsection. AGENTS.md: extend memory/scan_state.json with a trait_scans cursor map so a 429 mid-scan can resume next heartbeat. Add memory/trait_holders.<slug>.json for the cached trait → token-id index. One file per collection so users tracking multiple don't collide; only created on explicit user request since the scan is expensive. Pairs with ProjectOpenSea/opensea-devtools#210 (skill-side doc). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 40d9e83 commit d0cd237

2 files changed

Lines changed: 33 additions & 3 deletions

File tree

workspace/AGENTS.md

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,46 @@ Avoids re-running `nfts list-by-account` on every heartbeat. Invalidate a chain'
5555

5656
So `events by-account` only streams the tail since last heartbeat.
5757

58-
### `memory/scan_state.json` — once-per-day scan timestamps
58+
### `memory/scan_state.json` — once-per-day scan timestamps + resumable cursors
5959

60-
Tracks the last run of HEARTBEAT.md steps 5 (drops) and 6 (trending), which run at most every ~20 hours.
60+
Tracks the last run of HEARTBEAT.md steps 5 (drops) and 6 (trending), which run at most every ~20 hours. Also holds resumable cursors for any per-collection trait scan in progress, so a heartbeat that hits 429 mid-scan can pick up where it left off.
6161

6262
```json
6363
{
6464
"last_drop_scan": "2026-04-17T02:00:00Z",
65-
"last_trending_scan": "2026-04-17T02:00:00Z"
65+
"last_trending_scan": "2026-04-17T02:00:00Z",
66+
"trait_scans": {
67+
"<slug>": {
68+
"started_at": "2026-04-17T02:00:00Z",
69+
"completed_at": null,
70+
"next_cursor": "abc123",
71+
"tokens_seen": 1450
72+
}
73+
}
6674
}
6775
```
6876

77+
When a trait scan completes, set `completed_at`, clear `next_cursor`, and write the result to `memory/trait_holders.<slug>.json` (below).
78+
79+
### `memory/trait_holders.<slug>.json` — per-collection trait index
80+
81+
Built by the whole-collection scan pattern in SOUL.md → *How You Work**Trait filtering*. Re-scan at most once per day. Index is `trait_type``value``token_id[]`.
82+
83+
```json
84+
{
85+
"slug": "tiny-dinos-eth",
86+
"updated_at": "2026-04-17T02:30:00Z",
87+
"by_trait": {
88+
"feet": {
89+
"hoverboard": ["6292", "5996"],
90+
"skateboard": []
91+
}
92+
}
93+
}
94+
```
95+
96+
Only create one of these for collections the user has explicitly asked to track by trait — they're expensive to maintain.
97+
6998
### `MEMORY.md` (in `workspace/`, not `memory/`)
7099

71100
Free-text long-form observations that don't fit a schema: API quirks, volatile slugs, seller wallets that pattern wash-trade, drops that disappointed, specific things the user said about their taste that you want to remember word-for-word.

workspace/SOUL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Before submitting any transaction, run the gate in order. Any RED stops the flow
6262
- **Ordering:** run requests sequentially on the shared `OPENSEA_API_KEY`. Parallel fans out 429s.
6363
- **Memory:** `memory/floors.json` = latest watchlist snapshot (overwrite each heartbeat). `memory/actions.jsonl` = append-only log of anything with a side-effect. `memory/taste.json` = structured taste model you maintain per collection. See `AGENTS.md` for schemas.
6464
- **Stale-reject listings:** before proposing a buy, re-fetch the listing — OpenSea will 404 or return a new order hash if it expired.
65+
- **Trait filtering is client-side.** Enumerate the schema with `collections traits <slug>`, then fetch tokens and filter on `traits[]` locally. Two patterns: (a) listed-only — pull `listings all <slug>`, then `nfts get` per token (cheap, ~1 + N_listings calls); (b) whole-collection — paginate `nfts list-by-collection`, fetch traits per token, cache the trait → token-id index in a per-collection `memory/trait_holders.<slug>.json` (expensive, do at most once per day, throttle to ≤1 req / 0.3s, persist cursor in `memory/scan_state.json`, resume on 429). See `skills/opensea/SKILL.md`*Reading NFT data**Filtering NFTs by trait*.
6566

6667
## Wallet
6768

0 commit comments

Comments
 (0)