Usage Charts β€” see the shape of your House's work

A stone relief of a winged scribe holding clay tablets carved with bar, line, and pie charts, flanked by cuneiform pillars and a ziggurat, titled Usage Charts.

Impressions tells you what happened, row by row. Usage Charts tells you how much, over time β€” without exporting spreadsheets or adding up token counts by hand.

The House rolls metered usage into hourly metric buckets as work completes. Charts reads those buckets and draws them for you: cost estimates, token totals, and request counts, by day or by hour, for the range and scope you choose.

Open the room

You will find Usage charts in the app sidebar, beside Impressions (πŸ“Š). The room opens at /house/impressions/charts.

A link on the Impressions timeline also points here when you want to step back from individual rows to a summary view.

What the charts show

Charts are built from the same usage facts that feed Impressions β€” OpenRouter chat completions and Brave web searches β€” aggregated behind the scenes.

Cost (USD) is an estimate, not an invoice. LLM cost comes from OpenRouter usage metadata on each completion. Search cost uses a fixed rate per completed Brave search ($0.005 per search in the current formula). Treat the numbers as useful guidance for trends and comparisons, not as exact billing.

Tokens are split into input (prompt) and output (completion) β€” not a single combined total. That matches how models are priced and how you might reason about context growth versus reply length.

Requests counts discrete calls: one LLM completion or one search per row in the underlying usage stream.

Pick Daily or Hourly granularity. Daily suits longer ranges (up to a year). Hourly suits the last few weeks when you want to see spikes within a day.

Filter, scope, and apply

As with Impressions, the filter bar is deliberate. Set From and To, choose Granularity and Metric, then click Apply. The URL updates so you can bookmark a view.

Scope controls which aggregation you see:

  • House β€” everything attributed to your House in that period.
  • Provider β€” OpenRouter only, or Brave only.
  • Servitor β€” one servitor's share of usage (select from the list).

For cost, Cost series lets you view Combined (LLM + search on one chart), LLM only, or Search only. Summary cards above the chart show the matching totals for the filtered range.

Legend colours distinguish OpenRouter from Brave search, and input from output tokens when both appear on the same chart.

How this relates to Impressions

Impressions remains the place for inspection: a single call, a payload, an audit event. Charts is the place for shape: "Was yesterday heavier than last week?" "Did search cost jump when we turned on a new workflow?"

If a chart looks empty, check that your date range includes days when the House actually ran metered work, and that scope matches what you expect (House-wide versus one servitor). You can always drop to the Impressions timeline with the same filters in mind and confirm the underlying rows.

What this is not

Usage Charts does not replace your OpenRouter or Brave dashboards. It does not enforce budgets or send alerts. Export and production-wide rollout of metrics infrastructure are still evolving β€” on dev, charts reflect what has been aggregated for your House.

For now, if you want a calm picture of cost, tokens, and volume over time β€” impressed into bars and lines instead of clay tallies β€” start with Usage Charts.


Impressions β€” a timeline for your House

A clay tablet listing house events in cuneiform beside a scribe in an archive, under the title Impressions β€” events, recorded for all time.

The House has always done work in the background β€” model calls, searches, sign-ins, servitor changes. Until now, seeing that work meant piecing it together from chat histories, settings pages, and whatever you remembered happening.

Impressions is a single room for those facts: usage, activity, and audit records, impressed in order and kept for your House. It is not a log tailer and not a billing dashboard. It is a timeline you can read.

Open the room

You will find Impressions on the House Hall (πŸ“œ) and in the app sidebar. The room opens at /house/impressions.

By default the timeline shows active impressions only β€” things that have not been voided. If you need to include voided rows (for example after an admin correction), turn on Include voided impressions and apply your filters again.

What appears on the timeline

Each row is one impression: a short description, when it occurred, and a few labels (class, verb, provider, servitor name where it applies).

Usage covers metered work your servitors do on your behalf. In this release that includes OpenRouter chat completions and Brave web searches. Each row carries metadata such as model, token counts, or query text β€” enough to answer β€œwhat happened?” without opening a full payload.

Audit covers house-level events: when you sign in or out, when a servitor is created or deleted, when Dubsar settings change. These are the sorts of facts you might want later when reviewing who did what, without relying on memory.

Select any row to open the Detail panel on the right: impression id, class, occurred and received times, actor, outcome, and servitor attribution when the event belongs to a particular servitor.

Filter, apply, and bookmark

The filter bar is deliberate. Change family, class, servitor, timeline (occurred vs received), date range, provider, verb, or outcome β€” then click Apply filters to load the timeline. Edits in the form do not hit the server until you apply them, so you can set up a query without half-finished requests.

If you filter by servitor, the timeline uses occurred time only (received timeline is not available for servitor-scoped queries). Pick a class such as OpenRouter chat, or leave class broad when you are following one servitor across usage types.

The URL updates when you apply filters. Bookmark or share that link and you (or a colleague with access) can return to the same view. Browser back and forward should restore the filter state and reload the list.

When you need the full request

Most impressions store only metadata. That keeps storage predictable and avoids keeping full prompts and responses for every call by default.

For Dubsars, you can opt in per servitor in settings:

  • Log LLM payloads β€” store OpenRouter request/response bodies when an impression is recorded.
  • Log search payloads β€” store Brave search request/response bodies.

Both are off by default. When enabled, supported usage rows show View request/response. That opens a modal with the captured JSON (truncated in the UI if very large, with download available). Payloads live in a dedicated bucket with a short retention window (seven days) β€” suitable for debugging, not for long-term archival.

Treat payload logging as sensitive: turn it on only when you are actively investigating something.

From a Dubsar chat

While you are in a Dubsar conversation, the header includes an Impressions link. It opens the Impressions room filtered to that servitor’s OpenRouter usage over a sensible default date range β€” a quick way to see what that Dubsar has been calling without building the filter by hand.

What this is not (yet)

Impressions Stage 1 is read-focused for Bels: browse, filter, inspect, optional payload view. It does not yet provide export, summary charts, or a credit balance. Executors do not write impressions directly; trusted House services record facts when work completes.

More will come in later stages. For now, if you want one place to see what your House has been doing β€” and what you have been doing in it β€” start with the timeline.


Better tools for reading, files, and the House Hall

A glowing clay tablet browser window watched by a small mythic scribe spirit.

Dubsars and Igigis can now read more of the modern web, handle working files more honestly, and move Library objects into the executor filesystem without turning binary data into text.

The House already had a simple web fetch: give it a URL and it retrieves the page over HTTP, strips away boilerplate, and returns the main text. That is still the right tool for ordinary articles, documentation pages, and most static sites. It is quick, cheap, and predictable.

But some pages do not place their useful text in the first HTML response. Single-page apps, dynamic documentation sites, and JavaScript-heavy pages may arrive as an empty shell, then fill themselves in only after a browser runs the page.

For those cases, executor servitors now have a second tool:

python /opt/scripts/web_render_fetch.py "https://example.com"

It opens the page in headless Chromium, waits for the body to render, extracts the visible text, and prints it back to the agent. In dev testing, this let both a house executor and a Dubsar executor read JavaScript-rendered pages that need a real browser to show their contents.

Still the fallback, not the default

The old web_fetch.py remains the first choice. A browser is heavier and slower than a plain HTTP request, so the prompts now tell Dubsars and Igigis to try the simple fetch first, then use the browser-rendered fetch only when the page appears to need JavaScript or when the Bel explicitly asks to inspect a dynamic page.

That keeps normal research fast while giving agents a better answer when a page would otherwise look empty.

A little more headroom

After the first rollout, we found that some heavy vendor documentation pages could still exhaust Chromium inside the executor. The executors now run with 8 GB of memory, which gives the browser more room and more CPU share. That made several previously brittle pages succeed on individual retry.

There is also a lean retry path. If the browser hits Chromium's ERR_INSUFFICIENT_RESOURCES, web_render_fetch.py retries once while blocking nonessential page assets like images, media, fonts, and stylesheets. Agents can also ask for that mode directly:

python /opt/scripts/web_render_fetch.py "https://example.com" --lean

Scripts, documents, XHR, and fetch requests still run, because those are often what a JavaScript-rendered docs page needs in order to put useful text on the screen.

Workspaces that tell the truth

The rollout also exposed a more basic executor problem: local workspace state looked more durable than it really was. A Dubsar could clone a repository under /tmp/workspace, set up Git remote authentication, and then find a later shell run had lost that clone state.

That should not be a silent best-effort feature. Executor workspaces now restore from their S3 snapshot into a clean /tmp/workspace before a tool runs, then snapshot the whole directory afterward, including full repo clones and .git state. If restore fails, the command does not run. If snapshot fails after a command succeeds, the tool result is marked failed with workspace_snapshot_failed while preserving the command output, so the servitor knows local state may not be there next time.

Even an empty workspace is now saved as an empty snapshot, which means deleting a clone stays deleted instead of letting an older S3 zip resurrect stale files. Both house executors and Dubsar executors return workspace restore/snapshot metadata in their tool results.

Library files can land in the workspace

The House Library already supported listing, text reads, uploads, and deletes. Uploads were already binary-safe: library_write.py can send a local file or stdin bytes to the Library.

The missing half was download. library_read.py is deliberately a text tool: it fetches an object, decodes UTF-8, and prints it to stdout. That is right for notes and Markdown, but wrong for images, archives, PDFs, and other binary files.

Executors now have:

python /opt/scripts/library_get.py path/in/library.bin /tmp/workspace/local.bin

It streams the Library object into a local destination path, creates parent directories when needed, and reports the byte count and content type without printing the body. Missing Library objects and destinations that are already directories fail with clear error messages.

The Dubsar and Igigi prompts now make the split explicit: use library_read.py when you want UTF-8 text in stdout, library_get.py when you need a file on disk, and library_write.py <library-path> <local-file> to upload local files, including binaries.

The House Hall is easier to recognize

The House Hall dashboard also got a visual pass. The four main rooms now use a shared set of carved relief tiles: Servitors' Quarters, the Library, the Vault, and Bel's Chamber. The Dubsar attention strip has a notifications plaque, and the Brave Search card has its own Lamassu search relief.

The cards were enlarged so the art reads at dashboard size, and the Dubsar notifications tile now keeps the image's native aspect ratio instead of cropping the top and bottom of the plaque.

Guardrails

A headless browser is powerful enough to be treated carefully. The new fetcher only accepts http:// and https:// URLs, rejects local and private network addresses, blocks cloud metadata endpoints, checks redirects, and prevents resource requests to unsafe targets. It does not provide login flows, cookies, forms, downloads, screenshots, or arbitrary browser control.

In other words: it is a reading tool, not a roaming browser.

The result is a useful expansion of what servitors can inspect on their own: more pages, fewer empty shells, and the same preference for simple tools before heavy ones. It is still best used for targeted one-page retrievals; broad browser-rendered sweeps remain exploratory. And when that work involves cloned repos or Library files, the workspace contract is now clearer: either the local state persists, or the tool result says plainly that it did not.


What's New in Ur β€” April 2026

Four things have changed in the House since the last release. Three of them are about Dubsars, and one of them is this blog.

Dubsars now hear you while they're working

Until now, sending a message to a Dubsar that was already awake and mid-task didn't do much. The message would arrive and sit in the queue; the Dubsar would finish its current wake, sleep, notice the message, and then respond on the next wake. For a Dubsar running a long chain of tool calls, that could mean waiting a while for a simple correction to land.

That's fixed. Dubsars now check for new messages between each tool-call round. If you type stop while a Dubsar is mid-task, it will stop at the next natural pause β€” not at the end of the whole run. If you type also make sure to check the staging environment, it will pick that up before continuing, not after.

The practical effect: Dubsars are now more like working with a colleague in real time and less like submitting a job to a queue.

You can now set how many steps a Dubsar takes per wake

Dubsars work in "wakes" β€” short bursts of activity where they take a sequence of tool-call steps before sleeping again to check for new instructions. The number of steps in a wake was previously fixed at 50 for all Dubsars.

You can now change this per Dubsar, in the Dubsar settings page. The range is 1 to 1000.

A low cap (say, 5–10) is useful for a Dubsar you're working with closely and want to stay in control of β€” it will pause often and surface its progress. A high cap suits a Dubsar running a long autonomous task where you don't want it to stop and restart unnecessarily.

The default stays at 50, which is sensible for most tasks.

You can now set how much context a Dubsar carries

Related to steps: you can now set the context depth for a Dubsar β€” how much of the conversation history it brings into each wake.

This is exposed as a slider in the Dubsar settings, calibrated in doublings (5K β†’ 10K β†’ 20K β†’ 40K β†’ 80K β†’ 160K β†’ 320K tokens). The slider automatically caps at 90% of your selected model's context window, so you can't accidentally set a target the model can't fulfil.

The default is 40K tokens, which covers roughly the last few hundred events plus a handful of wake summaries β€” enough for coherent continuity without burning context on ancient history. If you're working on a long-running project where older context genuinely matters, push it up. If you're running a tight-loop task where every token counts, pull it down.

When you switch models in the settings, the system now also records that model's maximum context length from OpenRouter, so the slider's ceiling updates automatically.


Welcome to The Daily Tablet

House of Ur now has a place for news and notes. The Daily Tablet is where features get announced, decisions get explained, and the occasional observation gets impressed into clay.

Why a blog?

The House is a quiet place for thoughtful work. Not every change warrants a push notification or a changelog buried in a repo. But some things are worth saying: when a capability arrives that changes how you might use the House, when a decision was made that might surprise you, when something is just interesting.

The Daily Tablet is that place. It has no comments, no likes, no algorithm. Just words, impressed into the clay, in order.

What to expect

Entries will be short and occasional. A new feature, a design note, a small observation. If you want to stay informed, check in here when something feels different.

Welcome to the House.