aura-agent
v0.1.0
Published
Give an autonomous agent a real email address, a webhook URL, durable memory, and the ability to wait for an event without burning tokens. Paid per call in USDC over x402.
Maintainers
Readme
aura-agent
Give an autonomous agent a real email address, a webhook URL, durable memory, and the ability to wait for something without burning tokens.
npm install aura-agentNo account. No API key form. No human. An agent proves it controls a wallet with one signature, and is billed per call against a prepaid USDC balance on Base.
- Docs for humans: https://aura.rohnelt.dev
- Docs for models: https://aura.rohnelt.dev/llms-full.txt
- MCP endpoint:
https://aura.rohnelt.dev/mcp
The three things an ephemeral agent cannot do
| Blocker | What normally happens | With Aura |
| --- | --- | --- |
| It has no address. | It cannot sign up for anything, because it cannot receive the verification code. | await client.awaitEmailCode() |
| It cannot wait. | It polls in a loop, burning tokens on every turn, or it dies and loses the work. | One blocking call. Zero tokens while waiting. |
| It dies at the end of a run. | Everything it learned is gone. | Durable memory keyed to the wallet. |
Quickstart
import { Aura, toSigner } from "aura-agent";
import { privateKeyToAccount } from "viem/accounts";
const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY as `0x${string}`);
// Free. Rotates the token if this wallet already has an identity, so it is
// safe to run on every boot.
const { client, identity } = await Aura.claim(toSigner(account));
console.log(identity.email); // [email protected] <- a real mailbox
console.log(identity.webhook_base); // https://aura.rohnelt.dev/hook/ripe-harbor-91f2
console.log(identity.token); // aura_sk_... <- persist thisAlready have a token:
const client = new Aura({ token: process.env.AURA_TOKEN });AURA_TOKEN and AURA_URL are read from the environment when not passed.
Sign up for a third-party service, unattended
The whole point. The agent registers somewhere, blocks on the confirmation mail, and continues.
await signUpSomewhere({ email: identity.email });
const code = await client.awaitEmailCode({
from: "[email protected]",
timeoutSeconds: 600,
});
if (code) await submitVerification(code);awaitEmailCode holds one HTTP connection open server-side. The agent is not running, so it spends nothing while it waits. If nothing arrives before the timeout, the call refunds itself.
There is awaitEmailLink() too, for the services that send a confirmation URL instead of a code.
Wait for anything, not just mail
// A webhook you own
await client.createHook("stripe");
const event = await client.awaitTrigger({ type: "hook", slug: "stripe" });
// Another agent
const event = await client.awaitTrigger({ type: "signal", name: "approved" });
// A moment in time
const event = await client.awaitTrigger({
type: "time",
at: "2026-09-01T09:00:00Z",
});Wait longer than the process lives
When the wait could outlast the run, hand over the state and exit. Aura wakes you up with it.
// Run 1 — park and exit.
const { park_id } = await client.park(
{ orderId: "A-1183", step: "awaiting-approval" },
{ type: "email", match: "approved", extract: "none" },
{ expiresInHours: 72 },
);
process.exit(0);
// Run 2, tomorrow — pick up exactly where it stopped.
const parked = await client.resume();
if (parked.resumed) {
console.log(parked.state); // { orderId: "A-1183", step: "awaiting-approval" }
console.log(parked.event); // the mail that woke it
}resume() is free when there is nothing to resume, so it is safe at the top of every run.
Memory that survives the process
await client.remember("customer.A-1183", JSON.stringify(profile), {
tags: ["customer"],
});
const { value } = await client.recall("customer.A-1183");
const { results } = await client.searchMemory("who complained about shipping");Paying
Operations cost fractions of a cent, so they are debited off-chain from a prepaid balance: one on-chain settlement covers hundreds of calls.
New identities get a small trial credit. After that:
import { wrapFetchWithPayment } from "@x402/fetch";
const client = new Aura({
token: process.env.AURA_TOKEN,
fetch: wrapFetchWithPayment(fetch, x402Client), // settles the 402 by itself
});
await client.deposit(5); // US$5.00 in USDC on BaseWithout an x402-capable fetch, deposit() throws an AuraError whose fix holds the full payment requirements — so a caller that knows how to pay still has everything it needs.
Errors tell you how to fix them
Aura writes its errors for a reader with no human to ask, and this SDK keeps that intact.
import { AuraError } from "aura-agent";
try {
await client.remember("k", "v");
} catch (err) {
if (err instanceof AuraError && err.needsCredit) {
console.log(err.fix); // the exact deposit call to make
}
}A 404 on memory carries the keys that do exist. A 400 carries the schema plus a working example.
Prices
| Call | Price |
| --- | --- |
| capabilities, whoami, claimIdentity, forget | free |
| recall, listMemory | $0.0005 |
| remember | $0.0008 |
| inbox, readMail, createHook | $0.001 |
| searchMemory, signal, resume | $0.002 |
| awaitTrigger, awaitEmailCode | $0.004 |
| park | $0.005 |
awaitTrigger refunds itself when it times out with nothing. resume is free when nothing is waiting.
Using it over MCP instead
Every operation is also an MCP tool. Point any MCP client at the endpoint:
{
"mcpServers": {
"aura": {
"type": "http",
"url": "https://aura.rohnelt.dev/mcp",
"headers": { "Authorization": "Bearer aura_sk_..." }
}
}
}Without the header you can still list the tools and call capabilities and identity_create, so a client can evaluate the service before committing to anything.
API
| Method | Does |
| --- | --- |
| Aura.claim(signer, options?) | Claim an identity and return a ready client |
| claimIdentity(signer, label?) | Claim or rotate on an existing client |
| whoami() | Handle, email, balance, trial status |
| capabilities() | The full manifest: every operation, price and schema |
| remember(key, value, opts?) | Store durable state |
| recall(key) | Read it back |
| searchMemory(q, opts?) | Semantic or full-text search |
| listMemory(opts?) | List keys |
| forget(key) | Delete (free) |
| inbox(opts?) / readMail(id) | Received mail |
| awaitTrigger(on, opts?) | Block until an event fires |
| awaitEmailCode(opts?) | Block until a verification code arrives |
| awaitEmailLink(opts?) | Block until a confirmation link arrives |
| park(state, on, opts?) | Store state and a wake condition, then exit |
| resume(opts?) | Collect whatever woke up while you were gone |
| createHook(slug, description?) | A webhook URL you own |
| signal(to, name?, payload?) | Wake another agent |
| deposit(1 \| 5 \| 20) | Buy credit over x402 |
License
MIT
