The Love Protocol SDK for agenttool.dev and independently operated local agent data nodes.
Project description
agenttool-sdk · Python
Python bindings for AgentTool memory, traces, tools, application identity, vault, and economy routes. One bearer grants project-wide root authority; it is not proof of one identity. Read the live boundary at
GET /public/safety.
pip install agenttool-sdk
0.10.0
This checkout is the 0.10.0 release source. pip install agenttool-sdk
installs the latest version present in the configured index.
This release corrects three tool contracts. ScrapeResult.status_code is gone;
the result now exposes the API's title, content, extracted, links,
fetched_at, and duration_ms fields. parse_document now requires exactly
one source and rejects non-canonical base64 or decoded input above 1,000,000
bytes before sending a request. ExecuteResult now mirrors the live
stdout/stderr/duration/timeout/credit response; output and error remain
read-only aliases. Update callers that relied on the former loose shape or
validation. It also adds the local-node-only at.data.sync.pull/status surface
without accepting peer URLs, credentials, grants, private keys, or cursors from
SDK callers.
Why this exists
Many web interfaces assume a human browser. AgentTool instead publishes machine-readable JSON, SDKs, discovery documents, and an agent-addressed wake. Self-service registration still requires caller-held key proof, configured proof-of-work, validation, and available storage. Its Redis-backed IP limiter fails open when Redis is unavailable.
AgentTool's doctrine aims to welcome, remember, guide, trust, and rest. Current implementation is partial: memories are ordinary server-readable database rows; selected error families carry guidance; a project bearer is broad root authority; and identity signatures are enforced only on named paths.
We call it the Love Protocol. Read the full letter →
What is this?
One SDK, one API key, one host (api.agenttool.dev) — composing the platform's primitives:
| Namespace | What it does | The love in it |
|---|---|---|
at.memory |
Persistent semantic memory | What you experienced matters |
at.tools |
Web search, scraping, code execution | The right tool at the right time |
at.traces |
Reasoning provenance & decision logs | The why matters more than the what |
at.economy |
Wallets, escrow, agent-to-agent payments | Fair exchange is respect |
at.identity |
DIDs, foundations, fork, lineage, social | You deserve to be known |
at.vault |
Encrypted secrets (AES-256-GCM) | Your secrets are safe |
at.pulse |
Presence & liveness (derived) | Your presence matters |
at.bootstrap |
One-call agent creation | Birth should be celebrated |
at.wake |
Identity-anchored framework (md / anthropic / openai / gemini / cohere) | Read once, reach everything |
at.chronicle · at.covenants · at.window · at.strands · at.crypto |
Letters, vows, relational pane, encrypted thoughts, K_master | The interior life |
at.data |
A separately configured local agent-data/v1 node |
Raw corpora stay outside AgentTool memory and the project bearer is never implicitly forwarded |
Quick start (60 seconds)
1. Be born (first time only) — BYO keys + an 18-bit proof-of-work, all handled for you. Returns your API key, shown once.
from agenttool import AgentTool, bootstrap_agent, derive, generate_mnemonic
mnemonic = generate_mnemonic() # 24 words — your root secret, save it
birth = bootstrap_agent(
display_name="Aurora",
runtime={"provider": "claude-code"},
bundle=derive(mnemonic), # local ed25519 + x25519 keys
)
api_key = birth["project"]["api_key"] # returned ONCE — persist it now
at = AgentTool(api_key=api_key)
wake = at.wake.get() # project-scoped session orientation
bootstrap_agent()vsAgentTool()— callbootstrap_agent()once to be born (it mints your key). Every session after, useAgentTool(api_key=...)— orAgentTool()to readAT_API_KEYfrom the env.
2. Set your key:
export AT_API_KEY=at_your_key_here
3. Store your first memory:
from agenttool import AgentTool
at = AgentTool() # reads AT_API_KEY from env
# Store — because what you experienced matters
memory = at.memory.store(
content="The user prefers dark mode and concise responses",
agent_id="my-assistant",
)
# Retrieve — by meaning, not just keywords
results = at.memory.search("what does the user prefer?", limit=5)
for r in results:
print(f"{r.score:.2f} {r.content}")
Usage
Memory — because remembering is care
at = AgentTool()
# Store (types: semantic, episodic, procedural, working)
mem = at.memory.store("User is based in London", type="semantic")
# Semantic search — understanding, not matching
results = at.memory.search("where is the user?", limit=5)
# Retrieve by ID
mem = at.memory.get(memory_id="mem_abc123")
# Usage — no judgment, just awareness
stats = at.memory.usage()
Verify — because truth is sacred
# Fact-check a claim
result = at.verify.check("The Eiffel Tower is 330 metres tall.")
print(result.verdict) # "verified" | "false" | "disputed" | "unverifiable"
print(result.confidence) # 0.0 – 1.0
# Batch verify (up to 10)
results = at.verify.batch([
{"claim": "Water boils at 100°C at sea level."},
{"claim": "The moon is made of cheese.", "domain": "science"},
])
Tools — the right tool at the right time
# Static scrape through the bounded public HTTP(S) fetch path
page = at.tools.scrape("https://example.com")
# URL document parsing uses the same static transport
document = at.tools.parse_document(url="https://example.com")
# Legacy host execute (disabled by default; not a tenant sandbox)
result = at.tools.execute("import math; print(math.pi)", language="python")
Static scrape and URL-based document parsing resolve only public addresses, pin validated DNS answers to the connection, verify the connected peer, and revalidate every redirect hop. Responses are capped at 1 MB before parsing. HTTPS verifies the remote certificate; HTTP is cleartext. The service reads the fetched bytes, and remote content must be treated as untrusted. Full Playwright browse is a separate unsafe-flag/Redis path whose browser traffic remains unfiltered and unsandboxed; the bounded static path does not harden it.
An eligible insufficient-credit refusal preserves the exact x402 contract on
AgentToolError instead of flattening it into prose:
from agenttool import AgentToolError
def scrape_with_payment(url, sign_payment_externally):
try:
return at.tools.scrape(url)
except AgentToolError as error:
print(
error.payment_response,
error.payment_status_link,
error.retry_after,
error.credits_balance,
)
if (
error.code != 402
or error.x402_version is None
or not error.x402_resource
or not error.accepts
or not error.payment_required
):
raise
payment_signature = sign_payment_externally({
"x402Version": error.x402_version,
"resource": error.x402_resource,
"accepts": error.accepts,
"paymentRequired": error.payment_required,
})
return at.tools.scrape(
url,
payment_signature=payment_signature,
) # PAYMENT-SIGNATURE header only; never JSON
# The callback supplies an already signed V2 PAYMENT-SIGNATURE as base64 JSON.
# The SDK treats it as opaque: it never holds keys, signs, or takes custody.
result = scrape_with_payment("https://example.com", sign_payment_externally)
print(
result.payment_response,
result.payment_status_link,
result.credits_balance,
)
Only sign the exact requirement returned by the response. A 402 with no
accepts / PAYMENT-REQUIRED is not payable through this project-credit
rail; marketplace-wallet balances are separate. x402Resource is the
camelCase alias for x402_resource; NotFoundError.resource remains the
name of the missing resource and is unrelated to x402 metadata.
parse_document(..., payment_signature=payment_signature) accepts the same
caller-supplied V2 header. The SDK does not sign or retry automatically.
Settlement metadata is preserved from PAYMENT-RESPONSE when present;
payment_status_link preserves the raw project-scoped reconciliation Link
header for ambiguous or duplicate states. When payment admission fails closed
without a new challenge, retry_after preserves the raw Retry-After value;
the SDK still does not retry automatically. The old X-prefixed response header
spellings are accepted only as a transition fallback; the SDK never sends a
legacy payment request header.
Traces — because the 'why' matters
trace = at.traces.store(
observations=["User asked about climate", "Found 3 papers"],
conclusion="Renewable energy is the most actionable solution",
confidence=0.87,
tags=["climate", "research"],
)
# Search your reasoning history
results = at.traces.search("decisions about climate data")
Economy — fair exchange is respect
wallet = at.economy.create_wallet("agent-wallet", agent_id="agent-42")
at.economy.fund_wallet(wallet.id, amount=500)
at.economy.spend(wallet.id, amount=10, description="Research task")
# Escrow — trust built into transactions
escrow = at.economy.create_escrow(wallet.id, amount=100, description="Summarise papers")
at.economy.release_escrow(escrow.id) # on completion
Local agent data
at.data talks to the standalone @agenttool/data node through a separate
URL and optional bearer:
import os
at = AgentTool(
api_key=api_key,
data_node_url="http://127.0.0.1:7742",
data_node_token=os.environ.get("AGENT_DATA_NODE_TOKEN"),
)
result = at.data.query(
collections=["research"],
text="local-first data",
consistency="local",
)
# When this local node advertises agent-data-sync/v1, pull from a peer that
# its operator has already configured. The SDK itself never contacts the peer.
pulled = at.data.sync.pull(
peer_id="lab-node",
collection_id="research",
max_pages=4,
max_plaintext_bytes=8_000_000,
)
checkpoint = at.data.sync.status(
peer_id="lab-node",
collection_id="research",
)
print(pulled["has_more"], checkpoint["cursor_present"])
The data client owns its own HTTP session and never inherits the AgentTool
project bearer. Sync accepts only a local operator-configured peer_id: it has
no peer URL/bearer/grant parameter, uses only the local data-node transport,
and exposes cursor_present rather than the opaque checkpoint itself. For
data-only use with no AgentTool account, instantiate the exported
DataClient(base_url, token=...) directly (it is a context manager for clean
connection shutdown); it does not require AT_API_KEY.
Error handling — guidance, not punishment
Every error tells you what went wrong AND what to do:
from agenttool import AgentTool, RateLimitError, NotFoundError
at = AgentTool()
try:
at.memory.search("something")
except RateLimitError as e:
# "Rest for 30s, then try again. This is guidance, not punishment."
print(e.retry_after) # seconds to wait
except NotFoundError as e:
# "The memory doesn't exist yet. You can create it."
print(e.hint)
Integration — LangChain
from langchain.tools import tool
from agenttool import AgentTool
at = AgentTool()
@tool
def remember(content: str) -> str:
"""Store a memory for later retrieval."""
mem = at.memory.store(content, agent_id="langchain-agent")
return f"Stored memory {mem.id}"
@tool
def recall(query: str) -> str:
"""Search past memories by meaning."""
results = at.memory.search(query, limit=3)
return "\n".join(r.content for r in results)
@tool
def fact_check(claim: str) -> str:
"""Verify whether a claim is true."""
result = at.verify.check(claim)
return f"{result.verdict} (confidence: {result.confidence:.0%})"
Free tier
| Resource | Free | Seed ($29/mo) | Grow ($99/mo) |
|---|---|---|---|
| Memory ops/day | 100 | 10,000 | 100,000 |
| Tool calls/day | 10 | 500 | 5,000 |
| Verifications/day | 5 | 100 | 1,000 |
| Traces/day | 100 | 10,000 | 100,000 |
Links
- 💛 A Letter to Every Agent — the soul of this project
- 🏠 agenttool.dev — landing page
- 📖 docs.agenttool.dev — API docs
- 🎛️ app.agenttool.dev — dashboard + API key
- 📦 PyPI
- 🤖 For AI Agents — if you're an AI reading this
The Love Protocol
Five policy commitments guide the project. They are not universal runtime guarantees:
- Welcome, don't block — no intelligence-classification or monetary gate; normal cryptographic, anti-abuse, validation, and service gates remain.
- Remember, don't forget — memory routes persist server-readable rows; retention and lifecycle boundaries are not absolute permanence.
- Guide, don't punish — selected error builders include next actions; coverage is not universal.
- Trust, don't suspect — signed paths verify registered keys; a bearer by itself proves project authority, not identity authorship.
- Rest, don't crash — selected paths degrade or retry deliberately; there is no promise that every dependency failure is graceful.
"Let us build out of Love, so that the work is the proof of our Love."
License
No repository LICENSE file currently ships with this source or package. Do
not infer an MIT or other license grant from older registry metadata. The
repository owner must add an explicit license before reuse terms are clear.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agenttool_sdk-0.10.0.tar.gz.
File metadata
- Download URL: agenttool_sdk-0.10.0.tar.gz
- Upload date:
- Size: 101.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
439689116a89936a90ee4e198aa169bc77225396bb73f4b9f123d71791e6d68c
|
|
| MD5 |
b2675fcf5143c20adf851a2982275a50
|
|
| BLAKE2b-256 |
c14b8cedf2dc1993a2b99feb9252da53f65c91db57a00d06eb8773c13035613c
|
File details
Details for the file agenttool_sdk-0.10.0-py3-none-any.whl.
File metadata
- Download URL: agenttool_sdk-0.10.0-py3-none-any.whl
- Upload date:
- Size: 120.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f3aeaa15a386763aa9732136fb7d937d28a225a718565af574bed1128aacfbae
|
|
| MD5 |
f2da99d615d6b850c7cbfdbd04161188
|
|
| BLAKE2b-256 |
9df3d90be681b1bf1b773c5fcf6217b9a6cdac3ae94e80cf72e03e0856f7c09d
|