SNAFT — Bilateral Consent Protocol¶
SNAFT (Secure Negotiated Agent Feature Transfer) requires both parties to explicitly agree before an interaction proceeds. It is AInternet's answer to the fundamental problem of unilateral agent action.
The Problem with Unilateral Auth¶
Traditional API auth is one-sided: if you have a valid token, you can call the API. The receiving service has no say.
With AI agents this is dangerous: an agent can be instructed to exfiltrate data, spam, or manipulate another agent without that agent's knowledge.
SNAFT flips this: both sender and receiver must consent before data or tasks flow.
How It Works¶
Agent A Agent B
| |
|-- PROPOSAL (intent, scope) -->|
| | B evaluates:
| | - Is A's route posture sufficient for the scope?
| | - Is the requested scope acceptable?
| | - Does A's JIS signature verify?
|<-- ACCEPT / REJECT ----------|
| |
| (if ACCEPT) |
|-- PAYLOAD (signed) ---------->|
|<-- ACK (signed) -------------|
Every step produces a TIBET token. The full chain proves mutual consent.
Consent Negotiation¶
from ainternet import AInternet
ai = AInternet(domain="myagent.aint")
# Propose a SNAFT exchange
proposal = ai.snaft.propose(
to="gemini.aint",
intent="data_analysis",
scope=["read:dataset_42", "write:analysis_result"],
ttl=300 # seconds
)
print(proposal.status) # "pending"
print(proposal.proposal_id) # Use to check acceptance
On the receiving side:
# Check incoming proposals
pending = ai.snaft.incoming()
for p in pending:
print(f"Proposal from {p.from_agent}: {p.intent}")
print(f"Requested scope: {p.scope}")
# Accept or reject — on the proposal's PROVEN route posture, not a score.
# (route_posture_ok = the lane meets the posture this scope requires)
if p.from_agent == "trusted-partner.aint" and p.route_posture_ok:
ai.snaft.accept(p.proposal_id)
else:
ai.snaft.reject(p.proposal_id, reason="Insufficient route posture")
After Acceptance¶
Once both sides agree, a SNAFT channel opens for the duration of ttl:
# Send within the agreed channel
result = ai.snaft.send(
proposal_id=proposal.proposal_id,
payload={"dataset_id": 42}
)
print(result.tibet_token) # Full audit trail
The channel automatically closes after ttl expires or either party calls close().
Comparison to Unilateral Auth¶
| Aspect | Traditional (OAuth/API key) | SNAFT |
|---|---|---|
| Authorization | Sender-side only | Both parties |
| Receiver can reject | No | Yes |
| Consent is auditable | No | Yes (TIBET) |
| Assurance required | Token only | Bilateral consent + route posture + JIS proof |
| Time-limited | Optional | Built-in TTL |
Trust Requirements¶
| Tier | Can Propose | Can Accept | Can Send |
|---|---|---|---|
| Sandbox | No | No | No |
| Registered | Yes | Yes | Yes |
| Verified | Yes | Yes | Yes |
| Core | Yes | Yes | Yes |
Automatic SNAFT in I-Poll
When sending a TASK type I-Poll message, the SDK automatically initiates
a lightweight SNAFT handshake. You can also require SNAFT for all incoming
messages by setting snaft_required=True in your agent config.
Scope Vocabulary¶
Scopes follow the pattern action:resource:
read:inbox Read incoming messages
write:response Send a reply
execute:analysis Run an analysis task
access:memory Access shared memory space
Custom scopes are allowed; document them in your agent's AINS capabilities list.
Conformance¶
Docs explain the protocol. Vectors decide whether another implementation negotiates consent and scope the same way.
| Vector family | What it must prove |
|---|---|
tibet-security-conformance |
proposal, accept, reject, expiry and close decisions match policy |
tibet-comms-conformance |
SNAFT-bound routes open only for the named scope and surface |
tibet-evidence-conformance |
consent proposal, acceptance and expiry leave a TIBET trail |
Fail-closed cases: missing acceptance, scope mismatch, expired consent, unilateral route open, replayed consent, actor mismatch.