Problem
ReceiptStore.query orders results ORDER BY timestamp ASC and applies a hard-coded DEFAULT_QUERY_LIMIT = 10000 when no limit is passed. Two consequences for any caller that wants the newest receipts (a common audit/UX pattern — "what just happened?"):
- Silent truncation past 10k. With >10k matching receipts, the SDK returns the 10k oldest and the newest are unreachable through the public query API. A caller that wants newest-first by sorting in memory still loses them — they were never returned.
- No way to ask for newest-first. Callers must fetch ASC, then reverse/sort in JS (or equivalent in other SDKs), which is wasteful when the underlying SQLite has an index on
timestamp that could serve a DESC scan natively.
This affects every language SDK in this monorepo (TypeScript, Go, Python) since the protocol-level query semantics should be consistent across them.
Context
Surfaced while fixing agent-receipts/openclaw#118 — the openclaw plugin's ar_query_receipts tool was returning oldest-first because it inherited the SDK's ordering. The plugin-side PR (agent-receipts/openclaw#119) sorts in JS as a workaround, but that workaround silently fails past the 10k cap. Fixing this in the SDK is the proper resolution.
Proposed change
Add an order option to the query interface:
interface ReceiptQuery {
// ...existing filters
order?: "asc" | "desc"; // default "asc" for backwards compatibility
}
When order: "desc", emit ORDER BY timestamp DESC (with sequence DESC as a same-millisecond tiebreaker, matching how callers naturally want to read the audit trail). The existing idx_receipts_timestamp index serves both directions.
On the default cap
Separate question worth deciding: should DEFAULT_QUERY_LIMIT exist at all, or should an unset limit mean "all matching rows"? The cap protects against runaway memory use, but it does so silently — a caller passing precise filters genuinely expects to get every match. Options:
- Keep the cap, surface it. Return a sentinel/flag when truncation occurred, so callers can detect it.
- Drop the cap. Trust callers to pass
limit when they need bounded result sets; let SQLite stream rows.
- Raise the cap and document. Cheap stopgap.
Whichever direction this goes, it should be the same across all SDKs.
Equivalence across SDKs
This change should be applied consistently to:
- TypeScript (
packages/sdk-ts or wherever ReceiptStore lives in the monorepo)
- Go SDK
- Python SDK
The protocol/spec docs in this repo should also document the query ordering contract so external implementations behave the same.
Acceptance
Problem
ReceiptStore.queryorders resultsORDER BY timestamp ASCand applies a hard-codedDEFAULT_QUERY_LIMIT = 10000when no limit is passed. Two consequences for any caller that wants the newest receipts (a common audit/UX pattern — "what just happened?"):timestampthat could serve a DESC scan natively.This affects every language SDK in this monorepo (TypeScript, Go, Python) since the protocol-level query semantics should be consistent across them.
Context
Surfaced while fixing agent-receipts/openclaw#118 — the openclaw plugin's
ar_query_receiptstool was returning oldest-first because it inherited the SDK's ordering. The plugin-side PR (agent-receipts/openclaw#119) sorts in JS as a workaround, but that workaround silently fails past the 10k cap. Fixing this in the SDK is the proper resolution.Proposed change
Add an
orderoption to the query interface:When
order: "desc", emitORDER BY timestamp DESC(with sequence DESC as a same-millisecond tiebreaker, matching how callers naturally want to read the audit trail). The existingidx_receipts_timestampindex serves both directions.On the default cap
Separate question worth deciding: should
DEFAULT_QUERY_LIMITexist at all, or should an unsetlimitmean "all matching rows"? The cap protects against runaway memory use, but it does so silently — a caller passing precise filters genuinely expects to get every match. Options:limitwhen they need bounded result sets; let SQLite stream rows.Whichever direction this goes, it should be the same across all SDKs.
Equivalence across SDKs
This change should be applied consistently to:
packages/sdk-tsor whereverReceiptStorelives in the monorepo)The protocol/spec docs in this repo should also document the query ordering contract so external implementations behave the same.
Acceptance
query({ order: "desc" })returns receipts newest-first across all three SDKs