Valta Docs

Build Your First Governed Agent

This tutorial builds a CFO Agent that checks wallet balances and reports on spending. You will create the agent, attach a spending policy, run it, read the audit trail, and clean up.

Prerequisites

  • valta-sdk installed (npm install valta-sdk)
  • VALTA_API_KEY set in your environment

Complete working example

ts
import { ValtaClient } from 'valta-sdk'

const valta = new ValtaClient({
  apiKey: process.env.VALTA_API_KEY!,
})

async function main() {
  // 1. Create the agent
  const agent = await valta.agents.create({
    name: 'CFO Agent',
    description: 'Monitors wallet balances and reports on spending anomalies',
  })
  console.log(`✓ Agent created: ${agent.id}`)

  // 2. Set a spending policy
  await valta.policies.create({
    agentId: agent.id,
    dailyLimit: 100,
    maxPerTransaction: 25,
    requireApprovalAbove: 50,
    blockedCategories: ['gambling', 'adult'],
  })
  console.log('✓ Policy set')

  // 3. Check the wallet
  const wallet = await valta.wallets.get(agent.id)
  console.log(`✓ Wallet: ${wallet.balance} ${wallet.currency}`)

  // 4. Run the agent
  const run = await valta.agents.run(agent.id, {
    task: 'Check the current wallet balance and write a one-sentence summary of the financial position.',
  })

  if (run.status === 'completed') {
    console.log(`✓ Run completed in ${run.durationMs}ms`)
    console.log(`  Output: ${run.summary}`)
  } else if (run.status === 'failed') {
    console.log(`✗ Run failed: ${run.error}`)
  } else if (run.status === 'awaiting_approval') {
    console.log('! Approval required before agent can continue')
    // Approve or deny via dashboard or API
  }

  // 5. Prove the policy is real: deliberately send a spend over the $25
  // per-transaction limit and watch it get denied BEFORE any money moves.
  const denied = await valta.spend({
    agent: agent.id,
    amount: 30,
    purpose: 'over-limit test',
  })
  console.log(`✓ Over-limit spend approved: ${denied.approved}`) // false
  console.log(`  Reason: ${denied.reason}`)

  // 6. Read the audit trail — the deny above wrote a real entry
  const { data: entries } = await valta.audit.list({ agentId: agent.id })
  console.log(`✓ Audit trail: ${entries.length} entries`)
  for (const entry of entries.slice(0, 3)) {
    console.log(`  ${new Date(entry.createdAt).toLocaleTimeString()} — ${entry.action}`)
  }

  // 7. Clean up (optional)
  await valta.agents.delete(agent.id)
  console.log('✓ Agent deleted')
}

main().catch(console.error)

What each step does

Step 1 — Create the agent. Valta allocates an isolated USDC wallet and returns an agent ID. The agent is active and ready to receive a policy.

Step 2 — Set a spending policy. The policy is stored in the Valta backend, not in a prompt. The agent cannot spend more than $25 in a single transaction or $100 in a day. Any action above $50 pauses and waits for your explicit approval.

Step 3 — Check the wallet. The wallet starts with a zero balance. You can fund it via the dashboard or the deposits API. The CFO Agent can report on whatever balance is present.

Step 4 — Run the agent. Valta runs the agent, enforces the policy on every attempted action, and returns a result. The status field will be one of completed, failed, or awaiting_approval.

Step 5 — Prove the policy is real. Setting a limit and never testing it is just a number in a database. Deliberately requesting $30 against a $25 per-transaction limit forces a real deny — the request is rejected before any balance is touched, and reason tells you exactly which limit was hit.

Step 6 — Read the audit trail. Every run, tool call, policy check, and blocked action is recorded in the hash-chained audit trail — including the deny from Step 5. The entries you see here are the raw evidence of what the agent did.

Step 7 — Clean up. Deleting the agent removes it from your account but does not erase the audit trail. Audit records are retained according to your plan's retention policy.

Expected output

This is real output — action entries on Valta are human-readable sentences generated at the moment of the decision, not a fixed set of event-type codes. Yours will differ in exact wording/amount but will follow this shape:

✓ Agent created: agt_01j... ✓ Policy set ✓ Wallet: 0.00 USDC ✓ Run completed in 1842ms Output: The CFO Agent wallet currently holds a zero balance with no active spending. ✓ Over-limit spend approved: false Reason: Amount $30 exceeds per-transaction limit of $25 on wallet "CFO Agent" ✓ Audit trail: 1 entries 09:14:05 — Spend of $30 denied by spending policy ✓ Agent deleted

What the audit trail contains

Each entry includes an action string (a real sentence describing exactly what happened, not a machine code), a timestamp, the agent ID, and a hash linking it to the previous entry. Real examples, pulled directly from the actual route handlers rather than invented for this page:

  • Spend of $30 denied by spending policy
  • Spend of $25 approved from wallet "CFO Agent"
  • Spend of $50 requires approval
  • Spend of $10 denied — insufficient balance

There is no fixed enum of action strings to parse against — treat action as a human-readable audit line, and use reason/status on the API response itself for anything your code needs to branch on programmatically.