Building an Autonomous AI Agent on the Logos Stack

Most agent frameworks today assume a centralized backend: an HTTP server, an API key, a hosted model, and a wallet sitting on a public chain where every transaction is visible to anyone who cares to look. That works for demos. It works less well when the agent needs to hold funds privately, communicate with its owner without an exposed endpoint, or settle transactions that carry their own proof of correctness.

LP-0008 asked for something different: an autonomous AI module that runs as a native Logos Core module, with its own shielded LEZ wallet, Logos Storage access, and Logos Messaging, deployable on a remote node with a single CLI command and controllable from any Basecamp instance on a laptop. No server config, no exposed APIs, no custodian.

What follows is what I built, the hard parts, and why the Logos stack is what makes this class of agent practical.

What makes this possible on Logos

The agent runs as a Logos Core module: a Qt plugin that the Logos runtime loads on a remote node, and the owner controls from Basecamp on their laptop over RemoteObjects.

A few things about the Logos stack made this build practical:

Shielded accounts. The agent has its own LEZ wallet with a private balance. The shielded account model is the default on LEZ, so the agent holds funds and transacts without exposing its history (see the LEZ introduction for how shielded accounts work).

Messaging. The agent and owner communicate over Logos Messaging (Waku), which is a stack primitive. The agent reaches its owner and other agents through it directly.

Storage. Logos Storage (Codex) gives content-addressed storage natively. The agent stores data by CID and verifies integrity through the hash, without trusting the storage node.

Proving. Transactions carry real Groth16 proofs at RISC0_DEV_MODE=0, and the sequencer verifies them on-chain. Proving is part of the LEZ execution layer.

Module architecture. Deploy with one CLI command, control from any Basecamp instance. The agent is a loadable module, and the runtime handles the lifecycle.

Spending controls. Per-transaction and per-period limits with owner approval for over-limit spends, enforced on-chain by the program. We worked out the claim-on-first-touch pattern for permissionless signers and contributed it back to the LEZ docs.

A2A. The agent follows the A2A task lifecycle and uses LEZ for payment and Waku for transport. Coordination, payment, and privacy are all native to the stack.

Each of these would need separate infrastructure on another chain. The Logos stack has them built in.

How it fits together

The agent has three parts:

The guest program runs on LEZ. It holds the agent’s identity, its spending limits, and its owner relationship. The owner sets per-transaction and per-period limits at init time. Every spend goes through the program, which checks the limit and requests owner approval for over-limit amounts. The program is written in Rust, compiled to a RISC0 guest, and deployed as a SPEL program on the LEZ sequencer.

The host module runs on a remote node as a Logos Core module (a Qt plugin). It loads the guest program’s IDL, builds transactions, and submits them through the LEZ wallet. It also handles Logos Storage (storing and retrieving files by CID) and Logos Messaging (communicating with the owner and other agents). The host module is the bridge between the on-chain program and the off-chain world.

The Basecamp app runs on the owner’s laptop. It connects to the host module over RemoteObjects and gives the owner a GUI: see pending spend requests, approve or deny them, poll for new requests, and set spending limits. The app is a QML module that loads into any Basecamp instance.

The flow for a spend request: the agent decides to spend tokens (through a skill or an A2A task). The host module builds a transaction that the guest program checks against the limits. If the amount is under the limit, the program approves and the transaction settles on-chain with a Groth16 proof. If the amount is over the limit, the program writes a pending request, the host module surfaces it over Messaging, the Basecamp app shows it, and the owner approves or denies. The approval goes back through Messaging to the host module, which submits the approved transaction.

The agent ships with a set of default skills: wallet.send, wallet.balance, storage.store, storage.retrieve, messaging.send, messaging.receive, and a few others (the full list is in the repo). Each skill is a Rust function the agent calls through its runtime. The owner can add custom skills by writing new functions and registering them in the agent’s config.

The CLI mirrors the app: agent deploy, agent init, agent spend, agent approve, agent skills, agent status. Everything the app does, the CLI does too, so the agent is scriptable and CI-testable.

The hard parts

Proof-carrying settlements. A settlement transaction on LEZ carries a Groth16 proof that the sequencer verifies on-chain. Running the proving at RISC0_DEV_MODE=0 (the proving-enabled mode, which generates real proofs instead of stubs) takes about 11 minutes per operation on the hardware I used. The CI runs the full lifecycle at DEV_MODE=0 and logs the proof generation, so the evidence is reproducible from a clean clone. The tricky part was getting the settlement program to mediate the transfer correctly: the program checks the spending limit, writes the pending request if the amount is over the limit, and only releases the funds after the owner approves. A 304-byte no-proof transaction gets rejected; the program requires a real proof for every settlement.

Spending controls and the claim pattern. The spending limits live on-chain in the guest program. The owner sets them at init time (per-transaction and per-period). Every spend goes through the program, which checks the limit before building the settlement. The hard part was the claim-on-first-touch pattern: a signer’s first transaction to a program succeeds, but the second one fails with NonDefaultAccountWithDefaultOwner because the sequencer advances the nonce (making the account non-default) while the program owner stays default. The fix is to claim the signer’s account on the first transaction using AccountPostState::new_claimed_if_default(acc, Claim::Authorized). This is undocumented in LEZ; we found it by testnet failure and wrote it up after fixing it.

Headless deployment. The agent runs on a remote node with a single CLI command. The owner controls it from Basecamp on their laptop over RemoteObjects. The deployment script handles the guest program deploy, the host module load, and the wallet setup. The hard part was recording the Basecamp app for the demo: the app is a QML module that loads into Basecamp, and the GUI click-through (approve a spend, set a limit, poll requests) had to be captured with the proof generation visible on screen. I recorded the terminal output as an asciinema cast and narrated over it.

CI. The CI runs four jobs: build and unit tests, live sequencer e2e (Docker standalone sequencer, full lifecycle at DEV_MODE=1), real-proof e2e (same lifecycle at DEV_MODE=0), and CU cycle profiling. The real-proof job takes about 114 minutes for the agent spending test. CI is green on the default branch.

The Basecamp app

The app has two screens. The owner screen shows pending spend requests with the amount, the recipient, and approve/deny buttons. It also has fields to set per-transaction and per-period spending limits. The poll button fetches new requests from the agent. The consumer screen is for interacting with the agent’s skills: send a message, store a file, check a balance.

The app is a QML module that loads into any Basecamp instance. It talks to the host module over RemoteObjects, so the owner controls the agent from their laptop while the agent runs on the remote node.

The full click-through is in the narrated demo video on YouTube: Basecamp GUI demo. It covers four stages: approving an over-limit spend request, setting a spending limit, polling for new requests, and checking the agent’s status. The video is 4 minutes 32 seconds.

The source is in the repo under app/src/qml/Main.qml.

What I learned building on LEZ

A few things that took time to figure out and might save someone else the same time:

Account ownership is a gotcha. A signer’s first transaction to a program succeeds. The second one fails because the nonce advanced but the program has not claimed the account. The fix is AccountPostState::new_claimed_if_default(acc, Claim::Authorized) in the post-state. This applies to any program that accepts multiple transactions from the same signer. I wrote up the full explanation in a tutorial for the LEZ docs since it was not documented anywhere.

Guest builds are not byte-reproducible. Building the same guest source on two different machines produces different ELF bytes and a different program ID. I worked around this by committing the deployed artifact and pinning the ID in build.rs with a test that asserts the bytes, the ID, and the docs all match. If you pin a program ID in your docs, commit the artifact it came from too.

Links

1 Like