This commit adds the read path (Cortex) to complement the write path (Spine): ## Crates - stemedb-api: HTTP API with axum + utoipa OpenAPI - /v1/assert, /v1/query, /v1/epoch, /v1/skeptic, /v1/trace, /v1/audit - Metered endpoints with quota enforcement - Ed25519 signature verification - stemedb-lens: Truth resolution lenses - RecencyLens, ConsensusLens, ConfidenceLens - VoteAwareConsensusLens (Ballot Box pattern) - TrustAwareAuthorityLens (The Hive pattern) - SkepticLens (conflict analysis) - EpochAwareLens (paradigm-safe queries) - stemedb-query: Query engine with materialized views ## Storage Extensions - VoteStore: Vote aggregation with cached counts - TrustRankStore: Agent reputation with decay - AuditStore: Query audit trail - IndexStore: SP/P/S index structures - SupersessionStore: Epoch supersession chains ## SDKs - sdk/go/steme: Go HTTP client with Ed25519 signing - sdk/go/adk: ADK-Go tools for AI agents ## Documentation - Updated CLAUDE.md, architecture.md, roadmap.md - New ai-lookup entries for all services - Use case docs for consumer health intelligence - Arena roadmap for simulation advancement Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
3.5 KiB
3.5 KiB
The Meter (Economic Throttling)
Overview
The Meter implements per-agent per-hour quota enforcement using a Token Bucket algorithm. This prevents runaway agents from exhausting system resources while allowing bursty behavior.
Cost Model
| Operation | Base Cost | Notes |
|---|---|---|
| Assert | 10 tokens | Writing truth is expensive |
| Vote | 1 token | Voting is cheap |
| Query | 5 tokens | + 1 per Lens applied |
| Payload | +1/KB | Prevents spamming massive blobs |
Default quota: 10,000 tokens per agent per hour.
Components
QuotaStore (stemedb-storage/src/quota_store.rs)
Storage abstraction for quota tracking.
Key Types:
QuotaRecord- Per-agent per-hour usage recordCostConfig- Operation cost configurationQuotaCheckResult- Result of quota check (allowed, remaining, reset_at)OperationType- Assert, Vote, or Query
Storage Layout:
QT:{agent_id}:{hour} -> QuotaRecord (serialized)
QL:{agent_id} -> u64 (custom limit override)
Key Methods:
async fn check_and_record(&self, agent_id, operation, payload_bytes, timestamp) -> QuotaCheckResult;
async fn get_quota_status(&self, agent_id, timestamp) -> QuotaCheckResult;
async fn set_quota_limit(&self, agent_id, limit) -> Result<()>;
MeterLayer (stemedb-api/src/middleware/meter.rs)
Tower middleware that intercepts requests and enforces quotas.
Request Flow:
- Extract
X-Agent-Idheader (hex-encoded 32-byte public key) - Determine operation type from path
- Calculate cost based on operation + payload size
- Check quota and reject with 429 if exceeded
- Record cost and forward request
- Add quota headers to response
Headers:
| Header | Direction | Description |
|---|---|---|
X-Agent-Id |
Request | Agent's Ed25519 public key (hex, 64 chars) |
X-Quota-Remaining |
Response | Remaining tokens in current window |
X-Quota-Limit |
Response | Total tokens allowed per hour |
X-Quota-Reset |
Response | Unix timestamp when window resets |
API Endpoints
GET /v1/meter/quota Check quota status for an agent.
Query params:
agent_id- Hex-encoded agent public key
Response:
{
"agent_id": "...",
"remaining": 9500,
"limit": 10000,
"reset_at": 1705317600,
"used": 500,
"window_start": 1705314000
}
POST /v1/meter/quota/limit Set custom quota limit (admin operation).
Request:
{
"agent_id": "...",
"limit": 50000
}
Configuration
Environment variable: STEMEDB_METER_ENABLED
true(default): Enable economic throttlingfalseor0: Disable (all requests pass through)
Bypass Paths
These paths bypass metering:
/v1/health/swagger-ui/*/api-docs/*
Usage Example
# Check quota before operations
curl "http://localhost:3000/v1/meter/quota?agent_id=$(xxd -p -l 32 /dev/urandom)"
# Make request with agent ID
curl -X POST http://localhost:3000/v1/assert \
-H "Content-Type: application/json" \
-H "X-Agent-Id: 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20" \
-d '{"subject": "test", ...}'
# Response headers include quota info
# X-Quota-Remaining: 9989
# X-Quota-Limit: 10000
# X-Quota-Reset: 1705317600
Implementation Notes
- Quota windows are hour-aligned (truncated to the start of the hour)
- Quotas reset automatically at the start of each hour
- On storage errors, requests are allowed (fail open for availability)
- Anonymous requests (no X-Agent-Id) bypass metering
- Token bucket allows burst behavior while enforcing long-term limits