Skip to content

Build your own

AGENSAI is composition, not framework. The product is what happens when you wire @jaw.id/core and @justaname.id/sdk together with a small policy DSL and an MCP server. JAW is JustaLab's own AA stack, so the layers below your code are owned end-to-end. The codebase is the documentation.

If AGENSAI's flow doesn't match your product, fork the examples. Each one is ~80 to 100 lines of self-contained TypeScript using JAW + JustaName directly.

The four canonical examples

Each example lives at examples/ in the repo. Each is a working agent. Clone, set AGENSAI_OWNER_PRIVATE_KEY, run.

ExampleWhat it does
dca-botBuys $50 of ETH every week on Uniswap.
subscription-payerPays a recurring $9.99/month USDC subscription.
trading-copilotRebalances a portfolio within a daily spend cap.
service-payerPays a whitelisted set of API providers from a monthly cap.
cd examples/dca-bot
pnpm install
cp .env.example .env       # add AGENSAI_OWNER_PRIVATE_KEY
pnpm start

Each runs on Base Sepolia (chainId: 84532) by default. Flip to mainnet by changing the chain id and funding the orchestrator.

What every example shows

Read any example top to bottom; you'll see the same shape.

1. Build the orchestrator account

// Cite: ~/.claude/skills/jaw-sdk-best-practices/rules/account-api.md (lines 90-107)
import { Account } from "@jaw.id/core";
import { privateKeyToAccount } from "viem/accounts";
 
const orchestrator = await Account.fromLocalAccount(
  { chainId: 8453, apiKey: process.env.AGENSAI_API_KEY! },
  privateKeyToAccount(process.env.AGENSAI_OWNER_PRIVATE_KEY! as `0x${string}`),
);

Account.fromLocalAccount builds a JAW smart account rooted in a local ECDSA key. Same key + same arguments → same smart-account address on every EVM chain (CREATE2 determinism, verified in Phase 0 Probe C; pinned regression at 0x60009f89Ee82B174d26Cb2684F41A3556579fd5c).

2. Mint the agent's ENS subname

// Cite: ~/.claude/skills/jaw-sdk-best-practices/rules/account-api.md (lines 251-281)
import { JustaName } from "@justaname.id/sdk";
 
const justaname = JustaName.init({ apiKey: process.env.AGENSAI_API_KEY! });
 
await justaname.subnames.addSubname({
  username: "dca-bot",
  ensDomain: "agensai.eth",
  // ...address records, text records...
}, { overrideSignatureCheck: true });

overrideSignatureCheck: true is the JAW + JustaName idiom for issuing subnames under a namespace controlled by the API key (vs a user-signed flow). See account-api.md lines 269-275.

3. Compile a policy and grant the permission

// Cite: ~/.claude/skills/jaw-sdk-best-practices/rules/permissions.md (lines 7-43)
import { parseUnits } from "viem";
 
const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // USDC on Base
const expiry = Math.floor(Date.now() / 1000) + 30 * 86400;
 
const result = await orchestrator.grantPermissions(expiry, childAddress, {
  calls: [
    {
      target: UNISWAP_V4_ROUTER,
      functionSignature: "swapExactTokensForTokens(uint256,uint256,bytes,address,uint256)",
    },
  ],
  spends: [
    { token: USDC, allowance: parseUnits("50", 6).toString(), unit: "day" },
  ],
});
// result.permissionId, persist this; required for sendCalls and revoke.

4. The agent acts via the permissionId

// Cite: ~/.claude/skills/jaw-sdk-best-practices/rules/permissions.md (lines 131-157)
const child = await Account.fromLocalAccount(
  { chainId: 8453, apiKey: process.env.AGENSAI_API_KEY! },
  privateKeyToAccount(childPrivateKey),
);
 
const callId = await child.sendCalls(
  { calls: [{ to: UNISWAP_V4_ROUTER, data: encodedSwap, value: 0n }] },
  { permissionId: result.permissionId },
);

That's it. The on-chain validator checks each call against the grant. If it's in scope, the bundle lands. If not, it reverts.

Handoff payloads

The pattern for passing an agent's identity from creation code to runtime code is a Handoff object:

type Handoff = {
  ens: string;                // "dca-bot.agensai.eth"
  saAddress: `0x${string}`;
  chainId: number;
  permissionId: `0x${string}`;
  expiry: number;             // unix seconds
  childPrivateKey: `0x${string}`;
};

Persist it however you like, environment variable, encrypted config, secret store. The runtime code only needs chainId, apiKey, childPrivateKey, and permissionId to act on behalf of the agent.

For human-readable identity, ENS is enough; on-chain authority is the permissionId.

Reusing the AGENSAI internal lib

The CLI's apps/cli/src/lib/ contains the helpers that AGENSAI itself uses: policy DSL compiler, ENS resolver, fleet manifest readers, agent helpers. It is not published. It's intentionally private, you read it, you fork the parts you want.

The reasoning is in 00-build-plan-v2.md non-negotiable #7: "Forkable in a weekend. The codebase must read like a tutorial." A published SDK adds a layer that contradicts the showcase pitch. If you copy lib code into your own project, you own that copy.

Citations to memorize

If you're going to ship a JAW + JustaName composition like AGENSAI, four skill rule files cover ~95% of what you need:

  • account-api.md, Account.create, Account.fromLocalAccount, namespace + subname issuance.
  • permissions.md, grantPermissions, sendCalls with permissionId, revokePermission.
  • gas-sponsoring.md, built-in ERC-20 gas, no paymaster needed.
  • ens-identity.md, JustaName subname update flow, SIWE message construction.

Building UI on top

If you're building a dashboard (or any UI surface), the AGENSAI dashboard's integration layer is the reference. It exposes useAgensai hooks (useSignup, useOrchestratorSetup, useFleet, useCreateAgent, useRevokeAgent, useDeleteOrchestrator, useRecoverFleet) on top of @jaw.id/wagmi and JustaName SDK.

The hooks consume only public JAW + JustaName surfaces. There's no AGENSAI-specific server or back-end dependency to satisfy.

Where to start

  1. Pick the example closest to your use case.
  2. Run it on Base Sepolia first.
  3. Modify the policy and re-run.
  4. Strip out anything you don't need.
  5. Wire it into your product.

If you want to run it as a daemon talking to your IDE, that's agensai serve. If you want it as a cron job, just call the helpers directly. Same primitives, different runtime.