Skip to content

A research agent with the web tool and a budget#

You will build researcher: given a question, it searches and reads public pages and answers in five bullet points, each with its source. Each run stops at $1; short requests go to a cheaper model; and when the agent has spent $100 in the month, each further run asks an admin before spending another $10.

Before you begin#

  • A machine that can run agents; the editor role, and admin for the monthly budget.
  • An Anthropic API key in a credential anthropic-key (key api_key).

1. Create the agent#

The web tool reads any public page, GET only; the provider's own search is allowed, its code execution is not:

researcher.cedar
@id("web-reads")
permit (principal, action == Action::"http", resource in Server::"web")
when { ["GET", "HEAD"].contains(resource.method) };

@id("model-web-search")
permit (principal, action == Action::"model/server_tool", resource)
when { ["web_search", "web_fetch"].contains(resource.tool) };
  1. Anemoi → Agents → New agent, name researcher, Blank, Assistant.
  2. Model: Anthropic, claude-sonnet-4-5, anthropic-key.
  3. Connections: Web browsing, Read only. Tick Let the model search the web with its provider's search.
  4. Instructions: You research a question on the public web and answer in five bullet points, each with its source URL. Prefer primary sources: release notes, specifications, vendor documentation.
  5. Limits: Longest run, minutes 15; Stop at, per run 1.00; When reached Stop the run.
  6. Switch to Advanced → Routing, Add a rule: at most 2 000 input tokens → Anthropic, claude-haiku-4-5, anthropic-key.
  7. Create the agent.
$ jq -n --rawfile p researcher.cedar '{
    metadata: {name: "researcher"},
    spec: {
      kind: "astralyx",
      model: {
        provider: "anthropic", model: "claude-sonnet-4-5", credential: "anthropic-key",
        routes: [{when: {max_input_tokens: 2000},
                  model: {provider: "anthropic", model: "claude-haiku-4-5", credential: "anthropic-key"}}]
      },
      instructions: "You research a question on the public web and answer in five bullet points, each with its source URL. Prefer primary sources: release notes, specifications, vendor documentation.",
      tools: [{name: "web", kind: "web"}],
      policy: {tools: $p},
      budget: {max_seconds: 900, max_cost_usd: 1.0, on_exceed: "stop"}
    }}' \
  | curl -sS -X POST "$WS/agents" -H "Authorization: Bearer $ASTRA_TOKEN" -H "Content-Type: application/json" -d @-

2. Add a monthly budget#

Anemoi → Budgets → New budget: name researcher-monthly; For the agent researcher; Per Month (UTC); Dollars 100; When reached Ask for approval; Each approval allows, dollars 10. Create.

$ curl -sS -X POST "$WS/agent-budgets" -H "Authorization: Bearer $ASTRA_TOKEN" -H "Content-Type: application/json" \
    -d '{"metadata": {"name": "researcher-monthly"},
         "spec": {"scope": "agent:researcher", "period": "month", "max_cost_usd": 100,
                  "on_exceed": "approval", "grant_usd": 10}}'

3. Run it#

Run agent, input Which GPUs did CUDA 13 drop, and when?

$ curl -sS -X POST "$WS/agents/researcher/runs" -H "Authorization: Bearer $ASTRA_TOKEN" \
    -H "Content-Type: application/json" -d '{"input": "Which GPUs did CUDA 13 drop, and when?"}' | jq -r .run.metadata.name
researcher-7a2c90

The Trace shows the model calls, each with its route — route:0 for short requests on the smaller model, primary for the rest — and tokens and cost; the provider's searches with their queries; and each page the agent fetched through web, with its URL. Activity lists the same requests as they left the machine.

4. Watch what it spends#

The run's page shows its cost. The agent's Costs tab lists each run for Today or This month; Anemoi → Budgets shows researcher-monthly: spent, limit, Ask for approval, Ok.

$ curl -sS "$WS/agents/researcher/costs?period=month" -H "Authorization: Bearer $ASTRA_TOKEN" | jq .total
$ curl -sS "$WS/agent-budgets/researcher-monthly" -H "Authorization: Bearer $ASTRA_TOKEN" | jq '{state: .status.state, observed}'

What happens at each limit#

Limit When reached
$1 in one run The next model call is refused: budget reached: $1.00 of $1.00. The agent ends with what it has.
$100 in the month The budget turns Exhausted. New runs are refused with 409 BUDGET_EXHAUSTED; a running run's next model call asks for an approval of kind budget. Approved, the agent may spend $10 more.
A new month The budget is Ok again.

The monthly budget is close but not exact: runs under way may spend about 20 seconds' worth past it. The per-run limit is exact to one call.

Narrow the web#

To keep it to trusted sites, replace web-reads:

@id("web-reads-trusted")
permit (principal, action == Action::"http", resource in Server::"web")
when { ["GET", "HEAD"].contains(resource.method) &&
       (resource.host like "*.nvidia.com" || resource.host == "docs.nvidia.com" || resource.host like "*.rust-lang.org") };

Addresses inside your network are never reachable through the web tool, whatever the policy says.