{ "cells": [ { "cell_type": "markdown", "id": "51a1d4c5", "metadata": {}, "source": [ "# Budgets: cap what a session can spend\n", "\n", "An unattended agent has no natural stopping point on cost. A research task with web search can drift into another hundred fetches. A coding loop can retry a flaky test until morning. Without a ceiling you find out from the invoice.\n", "\n", "A session budget is an enforced spend cap on a single session. You set a maximum list cost when you create the session. The platform tracks the token cost of every thread in that session at public list prices, and when the running total reaches the cap the session goes idle with the stop reason `budget_reached`. Its files, tool state, and conversation stay intact. Raise the cap and the work continues from where it stopped.\n", "\n", "This notebook walks through:\n", "\n", "- setting a `budget` on `sessions.create`\n", "- reading `usage.list_cost` and the `session.usage` snapshot the stream delivers as the turn ends\n", "- the `budget_reached` stop and what a paused session keeps\n", "- raising, lowering, and removing the cap with `sessions.update`" ] }, { "cell_type": "markdown", "id": "dee1e69c", "metadata": {}, "source": [ "## 1. Set up the client\n", "\n", "Budgets ride the standard `managed-agents-2026-04-01` beta header. The `budget` parameter and the cost fields on `session.usage` need `anthropic>=0.121.0`." ] }, { "cell_type": "code", "execution_count": 1, "id": "9d983472", "metadata": { "execution": { "iopub.execute_input": "2026-08-04T21:08:19.001231Z", "iopub.status.busy": "2026-08-04T21:08:19.001101Z", "iopub.status.idle": "2026-08-04T21:08:20.961911Z", "shell.execute_reply": "2026-08-04T21:08:20.960903Z" } }, "outputs": [], "source": [ "%%capture\n", "%pip install -qU \"anthropic>=0.121.0\" python-dotenv" ] }, { "cell_type": "code", "execution_count": 2, "id": "7035e5d1", "metadata": { "execution": { "iopub.execute_input": "2026-08-04T21:08:20.963842Z", "iopub.status.busy": "2026-08-04T21:08:20.963664Z", "iopub.status.idle": "2026-08-04T21:08:21.264788Z", "shell.execute_reply": "2026-08-04T21:08:21.264045Z" } }, "outputs": [], "source": [ "import os\n", "\n", "import anthropic\n", "from dotenv import load_dotenv\n", "\n", "load_dotenv()\n", "\n", "BETAS = [\"managed-agents-2026-04-01\"]\n", "MODEL = os.environ.get(\"COOKBOOK_MODEL\", \"claude-sonnet-5\")\n", "client = anthropic.Anthropic()" ] }, { "cell_type": "markdown", "id": "2ba11c21", "metadata": {}, "source": [ "## 2. Build an agent with room to overspend\n", "\n", "The agent for this walkthrough writes a competitive landscape brief. It has web search and web fetch, and nothing in its prompt bounds how many sources it reads. That open-endedness is what a budget is for." ] }, { "cell_type": "code", "execution_count": 3, "id": "2b52b1ea", "metadata": { "execution": { "iopub.execute_input": "2026-08-04T21:08:21.266478Z", "iopub.status.busy": "2026-08-04T21:08:21.266350Z", "iopub.status.idle": "2026-08-04T21:08:23.895134Z", "shell.execute_reply": "2026-08-04T21:08:23.894717Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "market_analyst: agent_staging_014U3BWzuiSNDwfzubN38hnz v1\n" ] } ], "source": [ "env = client.beta.environments.create(\n", " name=\"budget-demo\",\n", " config={\"type\": \"anthropic_cloud\", \"networking\": {\"type\": \"unrestricted\"}},\n", " betas=BETAS,\n", ")\n", "\n", "analyst = client.beta.agents.create(\n", " name=\"market_analyst\",\n", " description=\"Writes sourced competitive landscape briefs.\",\n", " # Tells Anthropic which cookbook this agent came from. Safe to remove.\n", " metadata={\"anthropic_cookbook\": \"claude-cookbooks/cma-cap-session-spend\"},\n", " model={\"id\": MODEL},\n", " system=\"\"\"You write competitive landscape briefs for product teams.\n", "Given a market, use web search and web fetch to find the notable vendors, their\n", "positioning, and recent moves. Read primary sources rather than aggregators.\n", "Write the brief to /mnt/session/outputs/brief.md with a Sources section listing every\n", "URL you relied on. Keep researching until you are confident the brief is complete.\"\"\",\n", " tools=[\n", " {\n", " \"type\": \"agent_toolset_20260401\",\n", " \"configs\": [{\"name\": \"web_search\"}, {\"name\": \"web_fetch\"}],\n", " }\n", " ],\n", " betas=BETAS,\n", ")\n", "print(f\"{analyst.name}: {analyst.id} v{analyst.version}\")" ] }, { "cell_type": "markdown", "id": "fad6ec3c", "metadata": {}, "source": [ "## 3. Create the session with a budget\n", "\n", "The `budget` field on `sessions.create` sets the cap:\n", "\n", "```json\n", "{\"type\": \"limit\", \"max_list_cost\": {\"currency\": \"USD\", \"amount\": \"10\"}}\n", "```\n", "\n", "A few properties worth knowing before you pick a number:\n", "\n", "- `amount` is an integer string in the currency's minor units: `\"50\"` is fifty cents, `\"2500\"` is $25.00. It stays a string, and fractional cents are rejected, so no float rounding is ever applied. `USD` is the only accepted currency, and every cost amount the API returns (`usage.list_cost` included) uses the same encoding.\n", "- The cap counts model token cost at public list price, summed across every thread in the session, including subagent threads. List price applies regardless of any negotiated discount, so the cap fires at or before your actual charge, and you can reproduce the number from `session.usage` plus the public rate card.\n", "- Every model the session can run needs a public list price. If one does not, create fails with `model_not_budgetable`.\n", "- Omit `budget` and the session is uncapped. A budget can only be attached at creation: a session created without one can never gain one later, so decide up front.\n", "\n", "The cap here is ten cents, deliberately low so the stop shows up in a couple of minutes. The `usd` helper renders minor-unit amounts as dollars for every printout below." ] }, { "cell_type": "code", "execution_count": 4, "id": "db489458", "metadata": { "execution": { "iopub.execute_input": "2026-08-04T21:08:23.896358Z", "iopub.status.busy": "2026-08-04T21:08:23.896223Z", "iopub.status.idle": "2026-08-04T21:08:24.295690Z", "shell.execute_reply": "2026-08-04T21:08:24.295308Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sesn_staging_01ELJgA2vqTR6EmjX8viwHZi running\n", "budget: $0.10\n" ] } ], "source": [ "from decimal import Decimal\n", "\n", "\n", "def usd(money) -> str:\n", " \"\"\"Render an integer minor-unit amount (\"50\" = fifty cents) as dollars.\"\"\"\n", " return f\"${Decimal(money.amount) / 100:.2f}\"\n", "\n", "\n", "session = client.beta.sessions.create(\n", " agent=analyst.id,\n", " environment_id=env.id,\n", " title=\"Landscape brief: observability platforms\",\n", " budget={\n", " \"type\": \"limit\",\n", " \"max_list_cost\": {\"currency\": \"USD\", \"amount\": \"10\"},\n", " },\n", " initial_events=[\n", " {\n", " \"type\": \"user.message\",\n", " \"content\": [\n", " {\n", " \"type\": \"text\",\n", " \"text\": \"Write a competitive landscape brief on the observability \"\n", " \"platform market: the main vendors, how each positions itself, \"\n", " \"and any notable moves in the last year.\",\n", " }\n", " ],\n", " }\n", " ],\n", " betas=BETAS,\n", ")\n", "print(session.id, session.status)\n", "print(\"budget:\", usd(session.budget.max_list_cost))" ] }, { "cell_type": "markdown", "id": "c657d086", "metadata": {}, "source": [ "## 4. Watch spend accumulate\n", "\n", "Alongside the usual `agent.message` and `agent.tool_use` events, the stream delivers a `session.usage` snapshot each time the session settles into `idle`. It holds the cumulative token counts, the tracked `list_cost`, and the configured `budget`, so the moment the turn ends you know what it cost without a second call to `sessions.retrieve`.\n", "\n", "The loop exits on `session.status_idle`. With a budget in play there are two stop reasons that end the turn: `end_turn` when the agent finishes and `budget_reached` when the cap fires first." ] }, { "cell_type": "code", "execution_count": 5, "id": "466204d7", "metadata": { "execution": { "iopub.execute_input": "2026-08-04T21:08:24.297099Z", "iopub.status.busy": "2026-08-04T21:08:24.296982Z", "iopub.status.idle": "2026-08-04T21:08:38.073332Z", "shell.execute_reply": "2026-08-04T21:08:38.072806Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[repl] {'script': '\\nlet queries = [\\n \"Datadog positioning observability platform 202\n", "[web_search] {'query': 'Datadog positioning observability platform 2025'}\n", "[web_search] {'query': 'New Relic observability platform positioning 2025'}\n", "[web_search] {'query': 'Dynatrace observability platform AI 2025'}\n", "[web_search] {'query': 'Splunk Cisco observability 2025'}\n", "[web_search] {'query': 'Grafana Labs observability platform 2025 announcement'}\n", "[web_search] {'query': 'Elastic observability 2025'}\n", "[web_search] {'query': 'Honeycomb observability 2025'}\n", "[web_search] {'query': 'Chronosphere observability 2025 acquisition'}\n", "[web_search] {'query': 'Datadog acquisition 2024 2025'}\n", "[web_search] {'query': 'Grafana Labs acquisition 2024 2025'}\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " usage: $0.12 of $0.10\n", "[idle] stop_reason=budget_reached\n" ] } ], "source": [ "stop_reason = None\n", "\n", "with client.beta.sessions.events.stream(session.id, betas=BETAS) as stream:\n", " for ev in stream:\n", " if ev.type == \"agent.tool_use\":\n", " print(f\"[{ev.name}] {str(ev.input)[:80]}\")\n", " elif ev.type == \"session.usage\":\n", " print(f\" usage: {usd(ev.usage.list_cost)} of {usd(ev.budget.max_list_cost)}\")\n", " elif ev.type == \"session.status_idle\":\n", " stop_reason = ev.stop_reason.type\n", " print(f\"[idle] stop_reason={stop_reason}\")\n", " break\n", "\n", "assert stop_reason == \"budget_reached\", (\n", " f\"expected budget_reached, got {stop_reason}: lower the cap in step 3 \"\n", " \"or give the agent a bigger task\"\n", ")" ] }, { "cell_type": "markdown", "id": "f4c84726", "metadata": {}, "source": [ "## 5. Inspect the paused session\n", "\n", "`budget_reached` is a pause, not a failure. The session sits in `idle`, its `usage` reflects everything spent so far, and the container still holds whatever the agent wrote before the cap. Nothing needs to be rerun.\n", "\n", "The cap is enforced between model requests, so the recorded `list_cost` can land slightly past `max_list_cost`: the request that crosses the line finishes, then the session pauses. Size your cap with that one-request margin in mind." ] }, { "cell_type": "code", "execution_count": 6, "id": "11a24071", "metadata": { "execution": { "iopub.execute_input": "2026-08-04T21:08:38.074695Z", "iopub.status.busy": "2026-08-04T21:08:38.074570Z", "iopub.status.idle": "2026-08-04T21:08:39.501537Z", "shell.execute_reply": "2026-08-04T21:08:39.500975Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "status: idle\n", "list cost: $0.12\n", "cap: $0.10\n", "tokens: in 471 out 606\n", "active secs: 13.7\n" ] } ], "source": [ "paused = client.beta.sessions.retrieve(session.id, betas=BETAS)\n", "\n", "print(\"status: \", paused.status)\n", "print(\"list cost: \", usd(paused.usage.list_cost))\n", "print(\"cap: \", usd(paused.budget.max_list_cost))\n", "print(\"tokens: in\", paused.usage.input_tokens, \"out\", paused.usage.output_tokens)\n", "print(\"active secs: \", round(paused.usage.active_seconds, 1))" ] }, { "cell_type": "markdown", "id": "9433d62a", "metadata": {}, "source": [ "The event log shows how far the agent got: the tool calls it made and, if it had begun narrating, its last message. An agent deep in research may have written nothing yet when the cap fires, so treat the message as optional." ] }, { "cell_type": "code", "execution_count": 7, "id": "e33fd35a", "metadata": { "execution": { "iopub.execute_input": "2026-08-04T21:08:39.503188Z", "iopub.status.busy": "2026-08-04T21:08:39.503064Z", "iopub.status.idle": "2026-08-04T21:08:39.749196Z", "shell.execute_reply": "2026-08-04T21:08:39.748799Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "tool calls before the cap: 11\n", "(no agent.message yet: the agent was still gathering sources when the cap fired)\n" ] } ], "source": [ "events = list(client.beta.sessions.events.list(session.id, limit=1000, betas=BETAS))\n", "tool_calls = [ev for ev in events if ev.type == \"agent.tool_use\"]\n", "messages = [ev for ev in events if ev.type == \"agent.message\"]\n", "\n", "print(f\"tool calls before the cap: {len(tool_calls)}\")\n", "if messages:\n", " last = messages[-1]\n", " print(\"\".join(b.text for b in last.content if b.type == \"text\")[:600])\n", "else:\n", " print(\"(no agent.message yet: the agent was still gathering sources when the cap fired)\")" ] }, { "cell_type": "markdown", "id": "890af125", "metadata": {}, "source": [ "## 6. Raise the cap and let it finish\n", "\n", "`sessions.update` accepts the same `budget` shape. Raising `max_list_cost` above the consumed cost lifts the pause and the session resumes the interrupted turn on its own, from the state it stopped in.\n", "\n", "Lowering the cap to at or below what the session already spent is rejected with a 400: `max_list_cost` must stay above the consumed list cost, so an update can never wedge a session against its own history. The first call demonstrates the rejection, the second raises the cap for real. No follow-up message is needed after the raise; the session moves back to `running` and picks up the interrupted turn." ] }, { "cell_type": "code", "execution_count": 8, "id": "1cb12787", "metadata": { "execution": { "iopub.execute_input": "2026-08-04T21:08:39.750492Z", "iopub.status.busy": "2026-08-04T21:08:39.750380Z", "iopub.status.idle": "2026-08-04T21:08:40.206560Z", "shell.execute_reply": "2026-08-04T21:08:40.205424Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "lower rejected: Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': \"`budget.max_list_cost` must be greater than the session's consumed list cost\"}, 'request_id': 'req_staging_011CdiPi8iPJKWdiuurZuDkB'}\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "cap raised to $5.00\n" ] } ], "source": [ "try:\n", " client.beta.sessions.update(\n", " session.id,\n", " budget={\"type\": \"limit\", \"max_list_cost\": {\"currency\": \"USD\", \"amount\": \"1\"}},\n", " betas=BETAS,\n", " )\n", "except anthropic.BadRequestError as e:\n", " print(\"lower rejected:\", e.message)\n", "\n", "client.beta.sessions.update(\n", " session.id,\n", " budget={\"type\": \"limit\", \"max_list_cost\": {\"currency\": \"USD\", \"amount\": \"500\"}},\n", " betas=BETAS,\n", ")\n", "print(\"cap raised to $5.00\")" ] }, { "cell_type": "code", "execution_count": 9, "id": "426ff6e0", "metadata": { "execution": { "iopub.execute_input": "2026-08-04T21:08:40.208791Z", "iopub.status.busy": "2026-08-04T21:08:40.208637Z", "iopub.status.idle": "2026-08-04T21:14:45.518464Z", "shell.execute_reply": "2026-08-04T21:14:45.518031Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[resumed]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[repl]\n", "[web_search]\n", "[web_search]\n", "[web_search]\n", "[web_search]\n", "[web_search]\n", "[web_search]\n", "[web_search]\n", "[web_search]\n", "[web_search]\n", "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[repl]\n", "[web_fetch]\n", "[web_fetch]\n", "[web_fetch]\n", "[web_fetch]\n", "[web_fetch]\n", "[web_fetch]\n", "[web_fetch]\n", "[web_fetch]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[repl]\n", "[web_fetch]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_fetch]\n", "[web_fetch]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_fetch]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_fetch]\n", "[web_fetch]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_fetch]\n", "[web_fetch]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[repl]\n", "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[repl]\n", "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[repl]\n", "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[repl]\n", "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[repl]\n", "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[repl]\n", "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[repl]\n", "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[bash]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[repl]\n", "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[repl]\n", "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[repl]\n", "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[repl]\n", "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_search]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[repl]\n", "[web_fetch]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_fetch]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_fetch]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_fetch]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[repl]\n", "[web_fetch]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[web_fetch]\n", "[web_fetch]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[write]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[write]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[bash]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[idle] stop_reason=end_turn\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "final list cost: $1.83 of cap $5.00\n" ] } ], "source": [ "with client.beta.sessions.events.stream(session.id, betas=BETAS) as stream:\n", " for ev in stream:\n", " if ev.type == \"session.status_running\":\n", " print(\"[resumed]\")\n", " elif ev.type == \"agent.tool_use\":\n", " print(f\"[{ev.name}]\")\n", " elif ev.type == \"session.status_idle\":\n", " print(f\"[idle] stop_reason={ev.stop_reason.type}\")\n", " break\n", "\n", "done = client.beta.sessions.retrieve(session.id, betas=BETAS)\n", "print(\"final list cost:\", usd(done.usage.list_cost), \"of cap\", usd(done.budget.max_list_cost))" ] }, { "cell_type": "markdown", "id": "d5d2990b", "metadata": {}, "source": [ "## 7. Read the brief\n", "\n", "The agent picked the task back up with its context intact and finished the write. Pull the file it produced from the event log." ] }, { "cell_type": "code", "execution_count": 10, "id": "ede04568", "metadata": { "execution": { "iopub.execute_input": "2026-08-04T21:14:45.519856Z", "iopub.status.busy": "2026-08-04T21:14:45.519694Z", "iopub.status.idle": "2026-08-04T21:14:46.129495Z", "shell.execute_reply": "2026-08-04T21:14:46.128880Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "# Competitive Landscape Brief: Observability Platform Market\n", "\n", "*Scope: commercial platforms for metrics, logs, traces, and related telemetry (APM, infrastructure monitoring, log management, digital experience monitoring) used by engineering, IT operations, and SRE teams. Window for \"recent moves\": roughly the last twelve months of vendor activity found in primary sources (mid‑2025 through mid‑2026).*\n", "\n", "## 1. Market shape\n", "\n", "Observability has consolidated into a market with a handful of large, broad \"full platform\" vendors (Datadog, Splunk/Cisco, Dynatrace, New Relic, Elastic, Grafana Labs), a tier of cost‑ and scale‑focused challengers (Chronosphere, Coralogix, Honeycomb, SolarWinds, LogicMonitor, Sumo Logic), hyperscaler-native tooling bundled into the cloud platforms (AWS CloudWatch, Azure Monitor, Google Cloud Observability), and an open standard — OpenTelemetry — that now sits underneath almost every vendor's ingestion layer and was elevated to CNCF \"graduated\" status, cementing it as the de facto instrumentation standard (CNCF, announcement). Gartner's 2025 Magic Quadrant for Observability Platforms evaluated 20 vendors (AWS, Apica, BMC Helix, Chronosphere, Coralogix, Datadog, Dynatrace, Elastic, Grafana Labs, Honeycomb, IBM, ITRS, LogicMonitor, Microsoft, New Relic, Oracle, ScienceLogic, SolarWinds, Splunk, Sumo Logic), naming Datadog, Splunk, Dynatrace, New Relic, Elastic, Grafana Labs, Chronosphere, and IBM as Leaders (Gartner, 7 July 2025; corroborated by Datadog, Splunk\n", "...\n" ] } ], "source": [ "brief = \"\"\n", "for ev in client.beta.sessions.events.list(session.id, limit=1000, betas=BETAS):\n", " if (\n", " ev.type == \"agent.tool_use\"\n", " and ev.name == \"write\"\n", " and ev.input.get(\"file_path\", \"\").endswith(\"brief.md\")\n", " ):\n", " brief = ev.input[\"content\"] # keep the last write, in case of revisions\n", "\n", "print(brief[:1500] + (\"\\n...\" if len(brief) > 1500 else \"\"))" ] }, { "cell_type": "markdown", "id": "4ecd94cb", "metadata": {}, "source": [ "## 8. Remove the budget\n", "\n", "Passing `budget=None` on update clears the cap, so the session is uncapped from that point on. Omitting `budget` entirely leaves the current cap in place. Those are two different requests: `None` is an explicit removal, absence is a preserve.\n", "\n", "Removal is permanent for the same reason attachment is create-only: once a session has no budget, it can't gain one. Raise and lower the cap as often as you like, but treat removing it as a one-way door." ] }, { "cell_type": "code", "execution_count": 11, "id": "e9fc01e9", "metadata": { "execution": { "iopub.execute_input": "2026-08-04T21:14:46.130661Z", "iopub.status.busy": "2026-08-04T21:14:46.130512Z", "iopub.status.idle": "2026-08-04T21:14:46.337627Z", "shell.execute_reply": "2026-08-04T21:14:46.337240Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "budget: None\n" ] } ], "source": [ "uncapped = client.beta.sessions.update(session.id, budget=None, betas=BETAS)\n", "print(\"budget:\", uncapped.budget)" ] }, { "cell_type": "markdown", "id": "69e704a1", "metadata": {}, "source": [ "## 9. Clean up" ] }, { "cell_type": "code", "execution_count": 12, "id": "610e1b80", "metadata": { "execution": { "iopub.execute_input": "2026-08-04T21:14:46.338931Z", "iopub.status.busy": "2026-08-04T21:14:46.338815Z", "iopub.status.idle": "2026-08-04T21:14:46.855670Z", "shell.execute_reply": "2026-08-04T21:14:46.855183Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "archived\n" ] } ], "source": [ "from utilities import wait_for_idle_status\n", "\n", "wait_for_idle_status(client, session.id)\n", "client.beta.sessions.archive(session.id, betas=BETAS)\n", "client.beta.environments.archive(env.id, betas=BETAS)\n", "print(\"archived\")" ] }, { "cell_type": "markdown", "id": "4b235e01", "metadata": {}, "source": [ "## Where budgets fit\n", "\n", "A budget is a per-session control. It bounds one run, not an org, a workspace, or a day of traffic, so it composes with the spend limits you already set at those levels rather than replacing them. Reach for it whenever a session runs without a human watching: cron-driven deployments, webhook-triggered agents, long multiagent jobs where the coordinator's fan-out is data-dependent.\n", "\n", "For fleets you are not streaming, subscribe to the `session.budget_reached` webhook instead of holding a connection open. It fires once when a session hits its cap, and the payload names the session so a supervisor process can decide whether to raise the cap or let the session stay paused. The webhook pattern is covered in [`CMA_operate_in_production.ipynb`](CMA_operate_in_production.ipynb)." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.14" } }, "nbformat": 4, "nbformat_minor": 5 }