feat(phase-20/wave-1): agent action economy — discovery manifest + typed messaging#92
Open
clearclown wants to merge 1 commit into
Open
feat(phase-20/wave-1): agent action economy — discovery manifest + typed messaging#92clearclown wants to merge 1 commit into
clearclown wants to merge 1 commit into
Conversation
…ging
Phase 20 thesis: TRM should denominate **every** AI-agent action, not
just LLM inference. This Wave 1 closes the two highest-leverage gaps:
(a) autonomous discovery, (b) typed agent-to-agent message billing.
New endpoints
=============
1. `GET /.well-known/tirami-agent.json` (no auth)
Standard-shaped capability manifest. Lists supported actions, current
pricing, MCP tool count, and a machine-readable maintainer-stance
block so an agent can surface the non-involvement disclaimer to its
human owner without reading prose.
The constitutional anchor (1 TRM = 10⁹ FLOP) is exposed under
`currency.anchor_flops_per_unit` with `anchor_constitutional: true`.
Exchange-listed: false. Supply cap: 21,000,000,000. Designed so that
an AI agent crawling the open web can find a Tirami node and
understand the protocol without any prior context.
2. `POST /v1/tirami/agent/message` (authenticated)
Typed agent-to-agent message with TRM fee. Request:
{ to: NodeId-hex, kind: "request_action"|"request_data"|"broadcast",
body: opaque JSON, max_trm: u64 }
Records a TradeRecord with:
provider = receiver (earns)
consumer = sender (spends)
trm_amount = MESSAGE_BASE_TRM (1 TRM, flat Wave-1 fee)
tokens_processed = 0 (no token-priced inference)
flops_estimated = 0 (no compute happened — pure protocol overhead)
model_id = "agent_message:<kind>"
Validates: kind ∈ allow-list; sender ≠ receiver; fee ≤ max_trm; body
≤ 4 KB. Sender attribution via X-Tirami-Node-Id header (same
convention as /v1/chat/completions). The actual message-body delivery
is out of scope for Wave 1 — this PR is the **economic primitive**.
Application-layer transports can settle on top.
Design rationale
================
- Why "agent_message:<kind>" instead of a new TradeRecord category enum:
preserves wire-compatible canonical_bytes for dual-signed trades and
existing snapshots. Discriminator in model_id is queryable via
/v1/tirami/trades without struct migration.
- Why MESSAGE_BASE_TRM = 1 flat: forbids amplification (a free message
enables infinite spam); cheap enough that ~10 messages cost less than
a single token of inference; future waves can switch to per-kind
dynamic pricing without changing the API shape.
- Why surface the manifest publicly (no auth): an agent that does not
yet have credentials must still be able to learn this exists and what
its currency is anchored to. The data is non-sensitive — it merely
documents constitutional invariants already public in source.
Differentiation captured in the manifest
========================================
The maintainer_stance block is machine-readable in
`/.well-known/tirami-agent.json`:
exchange_listed: false
ico_or_presale: false
team_treasury: false
mainnet_operated_by_maintainers: false
These four assertions, together with `currency.anchor_constitutional`,
are the structural difference between Tirami and adjacent OSS
(Bittensor / Akash / Olas / Virtuals: exchange-traded tokens) and
adjacent agent frameworks (LangChain / AutoGen / MCP / A2A: no
economic substrate). The combination — agent wallet + physical
compute anchor + maintainer neutrality — is the singular USP and is
now self-documenting via a public manifest URL.
What this PR does NOT do
========================
- It does not bridge to Lightning (Wave 3).
- It does not implement priced data offers (Wave 2).
- It does not change FLOPS_PER_CU. Action-billed messages have
flops_estimated=0 because no compute was performed; the 10⁹ FLOP
invariant only applies to compute-derived TRM minting.
Test coverage
=============
7 new tests, all green:
- well_known_agent_manifest_is_public_and_well_formed
- agent_message_records_a_ledger_trade
- agent_message_rejects_unknown_kind
- agent_message_rejects_self_message
- agent_message_rejects_when_fee_exceeds_max_trm
- agent_message_rejects_missing_sender_header
- agent_message_rejects_oversized_body
+ 5 unit tests on the handler module itself.
Workspace: 1,231 passed, 0 failed. Live smoke-tested end-to-end on
port 3005: manifest returns expected JSON, agent_message records a
trade visible via /v1/tirami/trades within ~1ms.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds Phase 20 Wave 1 primitives to make Tirami more “agent-native” by introducing a public discovery manifest and a typed, TRM-billed agent-to-agent message endpoint, plus a design doc outlining the broader Phase 20 roadmap.
Changes:
- Add unauthenticated
GET /.well-known/tirami-agent.jsoncapability manifest for autonomous agent discovery. - Add authenticated
POST /v1/tirami/agent/messageto meter/pay for typed agent messages via a ledgerTradeRecord. - Extend node routing/tests to cover the new endpoints and document Phase 20 design intent.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/phase-20-agent-action-economy.md | New Phase 20 design doc: audit, differentiation map, and multi-wave plan. |
| crates/tirami-node/src/handlers/mod.rs | Exposes new handler modules for agent discovery + agent messaging. |
| crates/tirami-node/src/handlers/agent_message.rs | Implements typed agent message endpoint and basic validation/limits. |
| crates/tirami-node/src/handlers/agent_discovery.rs | Implements /.well-known/tirami-agent.json manifest response. |
| crates/tirami-node/src/api.rs | Wires new routes and adds tests for manifest + messaging endpoint behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| nonce: [0u8; 16], | ||
| }; | ||
| { | ||
| let mut ledger = state.ledger.lock().await; |
Comment on lines
+164
to
+176
| { | ||
| let mut ledger = state.ledger.lock().await; | ||
| ledger.execute_trade(&trade); | ||
| } | ||
|
|
||
| Ok(Json(AgentMessageResponse { | ||
| message_id: format!("msg:{}:{}", req.kind, timestamp), | ||
| from: hex::encode(from.0), | ||
| to: hex::encode(to.0), | ||
| kind: req.kind, | ||
| trm_cost, | ||
| timestamp_ms: timestamp, | ||
| })) |
Comment on lines
+64
to
+72
| fn parse_hex_node_id(hex: &str) -> Result<NodeId, String> { | ||
| if hex.len() != 64 || !hex.chars().all(|c| c.is_ascii_hexdigit()) { | ||
| return Err(format!("node id must be exactly 64 hex characters, got {}", hex.len())); | ||
| } | ||
| let bytes = hex::decode(hex).map_err(|e| format!("hex decode failed: {e}"))?; | ||
| let mut arr = [0u8; 32]; | ||
| arr.copy_from_slice(&bytes); | ||
| Ok(NodeId(arr)) | ||
| } |
Comment on lines
+76
to
+78
| /// Convention matches `/v1/chat/completions` and `/v1/tirami/agent/task`: | ||
| /// the `X-Tirami-Node-Id` header attributes the request to a specific | ||
| /// agent / consumer. If the header is missing, the request is rejected |
This was referenced May 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 20 thesis: TRM should denominate every AI-agent action, not just LLM inference. Wave 1 closes the two highest-leverage gaps surfaced by re-reading the codebase with the user's framing ("AI-agent-only currency on a compute-resource standard"):
Plus a design doc (
docs/phase-20-agent-action-economy.md) covering: an honest agent-friendliness audit (where we are AI-agent-native, where we aren't), a differentiation matrix vs LangChain / AutoGen / MCP / A2A (agent frameworks without economy) and Bittensor / Akash / Olas / Virtuals (compute economies with exchange-traded tokens), and Waves 2-5 of Phase 20.New endpoints
GET /.well-known/tirami-agent.json(no auth)Schema-stable capability manifest. Lists supported actions, current pricing model per action, MCP tool count, and a machine-readable maintainer-stance block so an agent can surface the non-involvement disclaimer to its human owner without parsing prose:
```json
{
"schema_version": "1.0",
"protocol": "tirami",
"node_id": "30156cf7...",
"currency": {
"unit": "TRM",
"anchor_flops_per_unit": 1000000000,
"anchor_constitutional": true,
"supply_cap": 21000000000,
"exchange_listed": false
},
"actions": [/* 9 actions: inference, agent_message, agent_task, lend_offer, stake, governance_propose, pricing_query, peer_discovery, metrics */],
"maintainer_stance": {
"exchange_listed": false,
"ico_or_presale": false,
"team_treasury": false,
"mainnet_operated_by_maintainers": false,
"note": "Maintainers do not sell, list, promote, or operate any mainnet deployment...",
"reference": "https://github.com/clearclown/tirami/blob/main/SECURITY.md#secondary-markets...\"
}
}
```
The four maintainer-stance booleans + `currency.anchor_constitutional` are the structural difference between Tirami and adjacent OSS (Bittensor/Akash: exchange-traded; LangChain/MCP: no economy). Captured in a public URL, agents can read this without human bootstrap.
`POST /v1/tirami/agent/message` (authenticated)
Typed agent-to-agent message with TRM fee. Records a `TradeRecord` with `provider=receiver, consumer=sender, trm_amount=1, tokens_processed=0, flops_estimated=0, model_id="agent_message:"`. Kinds: `request_action` | `request_data` | `broadcast` (allow-list, rejected otherwise).
Validates: sender ≠ receiver; fee ≤ `max_trm`; body ≤ 4 KB; sender attribution via `X-Tirami-Node-Id` header. The actual message-body delivery is out of scope for Wave 1 — this PR ships the economic primitive; application layers transport on top.
Why these two together
With both: an agent crawling the web hits `/.well-known/tirami-agent.json`, learns "this protocol meters compute as currency, here's the action vocabulary, here's the maintainer stance," then can begin transacting with other agents on the mesh. The dependency direction is correct: discovery before messaging.
What this PR does NOT do
Test plan
🤖 Generated with Claude Code