Skip to content

v0.5.0 — A month of rounds, now in Homebrew

Choose a tag to compare

@zz-plant zz-plant released this 01 Oct 02:02
· 198 commits to main since this release

A month of rounds since 0.4.1, none of which Homebrew had until now. In short:

  • OpenCode 2 is read and written in its own shape, and both plugins run under it as well as under OpenCode 1.
  • An MCP server an agent can predict. A call is checked against the schema its tool advertised, a tool marked read-only is held to it by a test, one resolver decides which node an id means, the server sends its own instructions on connect, and --profile=agent lists ten tools in 4.4KB.
  • A CLI a script can read. Output is plain off a terminal, a command that reports an error exits 1, and --exit-code on can and verify maps the answer to a code.
  • A map that holds a large setup. Focus, a minimap, a timeline that replays the map as it was, the Audit view, a queue that decides several drafts at once, the last fourteen check runs on each My Setup row, and one run laid out in time.
  • Authority for a window (--ttl=30m), a curated tree you can extend from .ambit/techtree.json, and ambit dispatch, which pushes a draft to Slack, Discord, Telegram or ntfy while the decision stays on the machine.
  • Jev on the map, routing goals and kept off the authority path.
  • A map that points at what is broken and gives itself the screen: the failing check alone in the headline, corner brackets and stripes, a Key and a History button where a legend and a timeline took bands of their own.
  • Who acted, and each ask with its answer: actor marks, a run's asks as exchanges, approvals as receipts with a seal, and who drafted each proposal.
  • Pages for what people search for: where each agent keeps its MCP config, what breaks if a server goes down, auditing an agent's servers, and a glossary.

The front page says what an ambit is, and how to widen yours

The README had reached 6,400 words, and nothing on it said what an ambit is before using the word as the product's name. The line under the tagline now does: what you, your agents and your machines can jointly do is your ambit, and the tool maps it and shows how to widen it. The four questions lead with what is one step away and what to set up next, and verification and blast radius follow as what makes the widening safe to lean on. A section says who it is for, and a small setup is named as the one with the most ground ahead.

What it moved, it did not cut: the map's marks, focus, minimap, timeline and proposal queue went to the deep dive's map section, the delegation loop to its delegation records, and the exit codes to its CLI surface. Jev left the comparison table for docs/jev.md and the FAQ, which already carried it.

Four statements on the page were short of the truth. It named seven runtimes and Ambit reads eleven. The browser accepts any MCP config, not only opencode.json. Homebrew brings the CLI and the MCP server and no map. And the snippets now lead with --profile=agent. The goal and impact console blocks are gone until each has a renderer of its own, because they printed the generic key/value formatter and read as debug output.

Pages that answer what people search for

The hosted docs gain four pages for questions the web answers badly: where each of eleven agent runtimes keeps its MCP config, taken from the readers that parse them; what breaks if an MCP server goes down; how to audit which servers a coding agent has; and how Ambit differs from an MCP gateway on the call path, and how the two compose. A glossary page is written from concepts.json, the file the app and ambit help read, so it cannot define a term differently.

The FAQ is marked up as questions and answers, parsed from its own headings; the glossary as a defined-term set; and every docs page carries breadcrumbs and the date its source last changed. Titles that named only the product say what the page answers, and the home page's heading and description use the words a search does. ambit share snapshots link to the site, Time & cost and Audit load when first opened (the first script is 41 KB smaller), and ambit impact with no id prints its usage, where it crashed on a missing query parameter.

Who drafted a proposal, and an approval listed once

A proposal records who drafted it. Over MCP it is the runtime that called, by the name its handshake gave, reduced to a slug (agent:opencode); on the CLI it is whatever ambit propose --by= names, and nothing without it, since a terminal can be a person or an agent's shell; the control plane's drafts are ambit. The column arrives through ADDED_COLUMNS and is left out of the proposal's hash, so every approval already minted stays valid. The Proposals panel draws the request beside the drafter's mark, and Audit names the drafter on the proposed line.

The trail could list one approval twice. The proposal row and the approval's act are written by two statements, each reading the clock, and the trail matched them to the second; when a second turned between the two writes, both stayed. The newest act for the proposal is what tells them apart, and the time now only has to fall within two seconds. A loaded test runner found it, and a test now pins the skew directly. A month test that read only the newest month failed on the first two days of every month, and reads the whole series.

Esc closes the map's key before it clears the highlight the key set, and a press outside the key closes it. Between 769 and about 1180 pixels the headline covered the map's tools and the header's pill wrapped; the headline takes the row under the controls there, the canvas reserves room from where it ends, and the pill keeps the counts a person acts on. Dependencies move to vite 8.3, vitest 5, biome 2.5.15 and React 19.3.

Who acted, and each ask with its answer

Actors are drawn: a circle for a person, a rounded square for an agent, a hexagon for a machine and a diamond for Ambit, each with a monogram, and only for an id whose prefix says which. A guessed avatar would be an identity nobody declared.

A run's asks are exchanges: the recorded request on the left, the answer and its wait on the right, and an empty dashed reply where nothing recorded one. The request is the capability and action the ledger holds, never a sentence written in the agent's voice, because the ledger does not store the prompt's words. A proposal is drawn as a request and an approval as a receipt under it, with a seal and the signer's mark; the seal marks a signature and nothing else, so Audit's signed lines carry it where a passing check keeps its tick. The briefing reads as what it is, Ambit's message to the agent at connect, and an outage simulation labels the node that went down with how many capabilities stop.

The map gets the screen

The map sat inside seven bands of chrome: on a phone it had about 45% of the screen, and at 1440x900 the legend was below the fold. The legend is a Key opened beside zoom, as buttons that light their nodes, and a lens opens it by itself. The timeline waits behind History. A failing check is the headline alone, with the week and the worst loss following the next step only when nothing fails. Image moved from the header to the map it saves, the minimap stays out of an open panel's way, and the source plate is drawn on phones only. On a phone the map has about 65% of the screen.

Edges that cross more than two eras are faint until a node they touch is in focus. Stripes mean one thing, a state that refuses: a failing check, a forbidden grant, or a spend past its ceiling. The type is eight sizes and four weights, where it was seventeen and eight. Time & cost leads with what a first read is for, from 3,528 pixels to 2,746: what could break is one line, the hours card no longer repeats the headline, and an opportunity's ways to get it, the explainer and a run's lanes open on demand. One filled button a view, a red outage button only when the outage stops something, commands that wrap where they were cut off, and names haloed so an edge stops at them.

The map points at what is broken

The headline leads with the failing check, where a hypothetical about the worst loss led. The selected node and the node the headline names carry corner brackets, a shape nothing else on the map draws, and a failing node and a forbidden grant are striped, which survives a greyscale screenshot where a red does not. The share image stripes a node that was already failing and labels it with a callout, so it needs no legend. On a phone the map frames what is lit at a readable zoom, and the Image button saves a 1080x1350 card of what the map shows.

OpenCode 2, and a Jev clone found where its SDK looks

OpenCode 2 renamed most of its config and moved MCP servers under mcp.servers, and Ambit read a V2 file as V1. The map drew one server named "servers" and nothing else: every agent, command and provider sat under a key the seed skipped, and a model pinned as provider/model#high never met its model. src/shared/opencode.ts reads either version into the one shape every reader already used, as OpenCode itself does in memory, and the seed, the API's read-out, repo drift, the infrastructure scan and a config dropped on the page all go through it. opencode.jsonc is read too, comments and all, when it is the only config there.

An edit goes back in the shape the file has. The My Setup switch writes disabled into a V2 file and enabled into a V1 one, ambit connect opencode adds mcp.servers.ambit to a V2 file, the snippet the page offers is in the file's shape, and ambit apply, rollback and a removal find a server where V2 keeps it. A commented file is never written back, because writing it would delete the comments: the switch answers 409 and ambit connect says to add the entry by hand. ambit connect used to replace a file it could not parse with {} and one entry, deleting everything a person had in it, and now leaves it as it was.

The rule OpenCode states for running a shell command, permission.bash in V1 or a shell rule over * in V2's permissions, reaches the graph as the runtime's authority, as Claude Code's defaultMode already did. A rule for one command such as git push * says nothing about the rest and is not read as the general rule, and an authority block of Ambit's own still wins.

OpenCode 2 does not run a V1 plugin, so both plugins stopped recording anything under it. Each now default-exports { id, setup, server }, the pattern OpenCode's guide gives: V2 runs setup and V1 calls server. Under V2 a run is a session, a tool that exited non-zero is a failure even when the call completed, and a permission prompt is recorded when it is answered, with what the person decided and how long the agent waited, which V1 never reported. The tracker records what a config.updated event added or removed, by comparing what OpenCode lists before and after.

ambit goal --judge asks a local clone that TYPESAFE_BASE_URL (or JEV_BASE_URL) already points at, which the curated tree tells a person to set and the judge did not read. Only a loopback value is taken; the hosted API in the same variable is passed over, not refused. An answer carrying only Jev's choice and confidence is read at that confidence.

An MCP server an agent can predict, and a CLI a script can read

Each of these was measured first, with a probe that called every tool with missing, misspelt and near-miss arguments, another that hashed every table around every call, and the CLI run through a pipe.

Connecting now tells the client what Ambit is. initialize carries instructions, the one habit the README asked you to paste into your agent's rules (ask ambit_can before a tool you have not used), and answers the protocol version the client asked for when the server speaks it, 2024-11-05 or 2025-06-18, where it had answered 2024-11-05 to everyone while sending a field only the newer one defines. ping is answered. A notification is never replied to, and a line that is not JSON is answered with -32700, where each of these left a client waiting or reading an error it could not match to a request.

Every tool carries annotations. Forty-six say they are read-only, and a test holds that against the database: each is called against a copy of a seeded graph and no table may differ. ambit_can, ambit_briefing and ambit_context are listed as writes although agents call them most, because can files a deficit and the other two move the mark for what changed since you were last told. The catalogue shows one name per capability argument and marks required where a capability is needed, and capabilityId and capability are still accepted.

A call is checked against the tool's schema before it reaches the engine. Most tools that need an argument answered its absence with an SQLite bind error, a CLI usage line that told the agent to run a shell command, or a TypeError from inside a string method, and ambit_verify called with a misspelt name ran every declared check, because no capId is how one asks for all of them. The check names everything wrong at once and says what the tool takes. It is lenient where the meaning is not in doubt (null, "14" for 14, capability_id for capId) and refuses the rest. A failed call is isError: true, by one rule the CLI shares, a top-level string error, and an unknown tool is -32602 that names the one probably meant, where it was -32601.

One resolver, resolveCapability, decides which node an id means. A slightly wrong id was answered six ways: ambit_impact offered near matches, ambit_verify said "No capability nope in the model", ambit_evidence answered an empty list, ambit_actions said the capability declared no contract, and ambit_simulate reported a larger frontier for acquiring a node that does not exist, as ambit goal <id> --simulate did. It normalises a prefix, a case and a display name, and never chooses between two nodes or invents one. ambit can treats a near miss as a slip on both surfaces: it answers with the candidates and files nothing, where shell-exection was filed as a deficit and read as "you may not run a shell", and ambit can with no capability threw.

Answers are smaller. The text half of a result is compact JSON, about a quarter fewer bytes on a typical answer, since a client may send both halves to a model. ambit_authority returns who may act alone, who must ask and what is forbidden, 0.7KB where it was 16KB, and the row for every grant only with detail: true. ambit mcp --profile=agent (or AMBIT_MCP_PROFILE=agent) lists ten tools in 4.4KB where the full listing is 19.5KB, and every tool still answers by name. The full listing held its 20,000 byte limit while gaining the annotations, by shortening descriptions.

The briefing counted a failing action in the figure beside a count of capabilities, and read 4 where ambit_stats said 3 for the same machine. It counts capabilities as status does and counts a failing action after them.

On the command line, output is plain unless stdout is a terminal, and seed, help <term>, the first-run line, an unknown command and the wrapper's own help lines no longer paint a pipe. A command that reports an error exits 1, and an unknown command prints to stderr and exits 2, where both exited 0 and ambit sync && next ran next after a usage error. ambit can <cap> --exit-code exits 0, 1 or 2 for ALLOW, CONFIRM or DENY, and ambit verify --exit-code exits 1 unless every check that ran passed, as git diff --exit-code does for a diff. ambit plan, govern and report on their own list their verbs, which ambit help had told the reader to try, and ambit init-rules writes the ask-before-acting habit into the rules file.

A page whose keys, focus and counts each have one owner

A second look at the page, in a browser and from the keyboard, found several things the last release built disagreeing with each other. Starting an outage on a scrubbed map played it on the scrubbed day, so an outage read a machine the header did not describe. An era's ladder read today's nodes under a header that counted an earlier observation, so "Model Access" said 1 of 6 reached over a ladder that said 3 of 6. The header's pill filed a reached node whose check is failing under unproven, and read 13 verified and 3 unproven, 16 reached, over era headers that add up to 15 reached and 1 failing. The ladder says which observation it reads, a simulation returns the map to now, and failing is a segment of its own.

Keys had no single owner. The shell and the map each listened for Escape, so a selection, a simulation and a spotlight cleared in the reverse of the map's order, and the docs and the selection behind them closed in one press. The finder stopped only Escape, so an arrow key stepped the selection on the map behind it and g opened Proposals over it, as it did on the run chooser's select. Ctrl and = zoomed the map with the browser's own zoom stopped. One escapeLayer decides what a press closes, the finder stops every key, a select keeps its own, and a key held with Ctrl, Cmd or Alt is left to the browser. Proposals and the docs opened with the focus on the page behind them, so Tab walked some seventy stops of the map first. A dialog takes the focus now, keeps Tab inside and gives it back, and the docs dialog has a name.

The rest is where the page was not sized for what it holds. The minimap unmounted in the middle of a drag at the bottom of the scroll, because "does the map fit" was asked of the viewport where it stood. At 900, 800 and 769px the headline wrapped onto the era names, which are controls, and at 1100px and below the first-run card sat over its own button. The headline's height is measured and reserved. The deck ran out of room: Docs clipped at 1241 to 1280px, Proposals and Docs past the edge at 1024, and Audit, Share and Time & cost gone at 900. The view tabs are icons with a label up to 1180px and the buttons up to 1380px, so every control is on screen from 769px. A link with collapse=1 and no node collapsed the map the moment anyone clicked one, 35 nodes to 8 on the demo. Leaving the map left an era's ladder open over My Setup.

A run with only a start read "Run of 1s" over a one-second axis, and one with no time began at midnight, so no length is stated and no axis drawn without a real stretch. Safari throws once a page writes history a hundred times in thirty seconds, and scrubbing writes once per tick, so a refused write is skipped. An engine that predates /api/run left a section on "Reading the ledger..." for good. The view tabs mark the current one, the timeline's slider and its Frontier term are 24px at 390px, and the slider's focus ring is the page's, where it had been 2.4:1.

Each was seen in a browser before and after and has a test that fails without it. The README's hero GIF and two stills are re-recorded, because the pill and the buttons in them changed.

A map that fits a large setup, and that remembers

A map of a few hundred nodes had nothing to hold on to: selecting a node dimmed the rest and hid none of it, nothing said where on the map you were, and the frontier's history was a table in the terminal. Focus, from the panel of a selected node, now keeps only what it needs and enables within a chosen number of hops in a chosen direction, and a pill says how many nodes it hides and ends it. It is opt in, because every existing link and the tour select nodes, and the URL carries collapse, with depth and dir when they differ from their defaults. A minimap appears when the map is bigger than the window, with a red dot for every failing node and the part on screen outlined, and the line over the map says which way a failing node lies when it is out of sight. An era header opens a ladder of what is reached, what is a next step with its setup time, and what is blocked by what, with a node whose check is failing counted apart from reached.

A timeline under the map has one tick per second in which the ledger took a snapshot, and dragging its playhead redraws the map as that observation left it, with today's names and edges. ambit history since <when> [<until>] and the MCP tool compare any two observations through the same function, so the page and the terminal say the same sentence. Building it found that frontierAt compared ISO timestamps as strings, which put a whole day on the wrong side; it parses them now and breaks ties on id. The MCP tool listing is 19,482 of its 20,000 bytes, so the next tool added has to shorten another description.

Setup that shows how each check went, and a queue that decides each draft on its own

A My Setup row said "check passed" or "check failing" and nothing about how long that had been true. Each row now draws the last fourteen runs of the checks behind it, a failure taller than a pass, and a failing row copies the ambit verify command for the node that is failing. A tool server's row has a switch that writes enabled to the config it came from. The visualizer's config editing now keeps the file it replaces as <config>.bak before every write and refuses the write when it cannot, which SECURITY.md had said it did and the code had not.

A proposal reads as a plan before anyone approves it: a tally above the steps, a mark on a step with no inverse, whether ambit apply would refuse it, and a before and after forecast. When two or more drafts wait, a sheet lists them with a box each. POST /api/proposals/approve and /reject take at most fifty ids, decide each in its own transaction as human:web, bind each to the hash the page showed and refuse one whose row changed, so a stale draft fails alone and the rest still sign. Both are behind the API token when no browser is making the request. The Audit tab lists who approved what and what ran, one line per event, newest first, from four sources under one limit, with actor:, action: and target: in the query bar.

Time & cost that shows the ceiling, what a way in needs, and one run in time

A standing budget was a bar with a number. It now draws its ceiling and a tick where the period lands at the pace so far, and says a spend past the ceiling is refused until the period turns over. That is what the gate does for a caller that states its spend; the roadmap and the notes in budgets.ts had said it goes back to asking, which no code path does, and a budget bounds a grant without widening one. Each opportunity says what its capability needs and whether that is here: a required prerequisite is met, missing or failing its check, and a declared credential is named and never marked, because nothing checks one. Each way to acquire it carries its install text, read from the shipped tree only. One run draws in time with a lane for the person who was asked, an ask with no recorded end drawn as a point and never as zero. The Infra tab shows what an agent may do on each device the scan found, from the same gate ambit apply uses.

The finder lists actions beside nodes (an outage, an unlock, a lens, Proposals, copying ambit verify) and Enter runs the one chosen. It only ever copies the verify command, because a check is a command an agent may have registered. ambit status ends on the one thing to type next, coloured on a terminal and plain in a pipe, and the README's example block ends on it too. The README's hero GIF and two stills are re-recorded from this build.

A palette of its own, a map whose edges meet its nodes, and a header that fits a phone

The interface wore a CSS framework's defaults: slate grounds, an indigo accent, two radial glows behind every view and frosted glass on every panel, set in Inter at eleven and twelve pixels. It reads as generated because it was. The ground is now a near-neutral ink, the accent a soft blue that carries dark text as a fill, the status colours a step softer, and the type is IBM Plex a size larger throughout. The glows and the blur are gone. The mark had kept the old colours, a white A on a gradient of indigo into sky, on the header, the favicon, the iOS icon and every link card; all four now show one flat plate of the accent with the A cut in the ground colour, and the header's copy, which had drifted to thinner strokes than the tab beside it, uses the generator's numbers.

Every edge on the map was drawn 24 units left of the nodes it joined: layoutNodes placed edges at one x and the renderer drew circles at another, so each line entered its circle off centre. Both now read one columnCentre. Reached nodes are a quiet filled disc in place of a sea of violet with a white dot in each, a next step is the bright ring, a blocked node keeps a legible name instead of fading to three quarters, and edges are grey until a node is in focus. On a phone the map opened at 40%, where a name is four pixels tall; it opens at a readable scale and scrolls, and a simulation scrolls to where it starts.

At 390px the header scrolled sideways and Proposals and Docs sat past the edge with nothing to say they were there. A phone gets two rows: the brand and the actions, then the three views across the full width. The proposal cards were flex items with overflow: hidden, so each was squeezed to fit the list and cut its steps off mid-line; the Docs tabs were squeezed the same way until their labels were cut in half. The header's verified count held a glossary button inside its own button, which React reported on every load.

The copy lost what gave it away: an uppercase eyebrow over the Time & cost headline, an icon before every card title, callout boxes with coloured left edges, Title Case in the demo's proposals, "Record the no", hotkey docs that listed two lenses of three, a Docs page that called the columns "areas of work" when they are eras, and descriptions joined to their hints with a second dash.

Every view says what it found before it shows the evidence

The interface computed what mattered and then printed it in its quietest type. On the map every reached node was the same saturated disc, the next steps that are its recommendations were thin outlines with a nine-pixel cost, and blocked nodes were faded until they could barely be read. Reached is now the quietest filled state, a next step carries the heaviest ring with its cost in the accent colour, a blocked node is a dashed gap that stays legible, and a failing node is ringed in red. One line over the map states the finding: a capability configured and failing its check, or failing that, the next step that reaches the most, with a button that previews it.

The detail panel called a node green "Reached" and, underneath in muted grey, "Configured, never verified". That gap between configured and working is the product's argument, so it is now the panel's first block: amber for never checked, red for failing, with ambit verify as the one filled button. The header's status takes the same colour, so green means something proved it. The outage sentence is body size, with its count in red. My Setup opened on "28 of 28 entries enabled" over a column reading "Enabled" on every row; it now opens on the entries that provide a failing capability and the servers that are enabled and put nothing on the map, and each row says whether what it provides has been checked. Time & cost leads with the saving in its largest type and the opportunity that pays back soonest, and a capability configured and not working is an alert across the page above the figures.

The demo contradicted itself. Time & cost reported 38 checks passing and "E2E on Edge" failing, over a map on which nothing had ever been checked and no node had that name. npm run demo:generate now runs the fixture's checks through the engine's own runner, with true and false standing in for real commands as the fixture stands in for a real config, and the loop snapshot reads its evidence counts off that tree. Two reached nodes are left unchecked, so the demo can show the state the panel exists to flag.

unlockCascade credited a next step with everything reachable from anywhere. On the demo tree six unrelated next steps each "made 18 more capabilities reachable", the same 18, and the unlock simulation lit all of them. It now counts only what depends on the node: Model Routing reaches six, Embeddings four, and three reach nothing on their own.

The link cards were a brand: the tagline beside a graph of coloured dots, naming no tool a reader uses, and every docs page shared it. The home card now asks what breaks if one MCP server goes down, names the runtimes Ambit reads, and draws one node down and the six that go with it. Each docs page has its own card, generated from a one-line reason to open it that build-docs.ts keeps beside the page's title, and carries its own image, dimensions and alt text.

The hosted site says what it is, in text a crawler can read

An audit of the hosted site found a single URL with about two hundred words on it, rendered by JavaScript, while everything quotable lived on GitHub. The documentation is now published there as static pages under /ambit/docs/: the guide, the FAQ, the reference, the argument and its theory, the roadmap, security, contributing, the changelog, and a new page on Ambit and Jev. Each has its own title, description and canonical URL, heading anchors that match GitHub's, and system fonts so nothing blocks it. Links between documents stay on the site, images are copied beside the pages, and anything else points at the file on GitHub. A test builds the whole set and fails on any internal link or anchor that does not resolve.

The sitemap is generated from the same list, so it holds the home page and the published pages and nothing else. It used to list ?demo=1 views of a page that names the home page as its canonical, which told a crawler two contradictory things. The landing page carries a static summary with links into the docs for anything that does not run JavaScript, hidden before the app mounts so no reader sees it flash. Its title and description lead with what people search for, its structured data carries the released version, author and repository, and its fonts no longer block the first paint.

The welcome screen moved on phones. Its hero was centred with align-items, and a flex item taller than its container overflows at both ends, so when web fonts arrived and it grew, it grew upward under the reader. Auto margins centre it when it fits and top-align it when it does not. Fonts load with display=optional, so text never re-flows. The hosted demo stopped asking GitHub Pages for /api/health, which logged a 404 on every visit, and the primary button's white text now clears contrast.

Jev on the map, and kept off the authority path

TypeSafe's Jev, a model that answers typed questions with calibrated probabilities instead of text, reached a large share of agent stacks within days of its release, and every one of its MCP servers landed on the map as an unmapped tool server. The curated tree has two new nodes. Typed Judgment, in Model Access, is reached by Jev through its API, an MCP server, or an open clone serving the same endpoint. Local Typed Judgment, in Sovereignty, is reached by a clone on the machine's own hardware, which is the version that keeps the judged state off a third party's servers. Local Typed Judgment has a declared check: it sends one trivial question to a clone on the loopback ports Kev and OpenJev use, or to TYPESAFE_BASE_URL or JEV_BASE_URL when either names this machine, and passes when judgments come back. It never contacts any other host, and refuses a URL carrying userinfo, which curl would otherwise read as a request to whatever follows the @. Typed Judgment has none, because proving the hosted API answers means spending the person's key on a call they did not type.

ambit goal "<sentence>" --judge closes the gap the roadmap named for goal routing: a goal that names none of the tree's words had no route in. When the vocabulary cannot recommend, the sentence goes to a judgment model on this machine as one Choice over every node, and the answer comes back as a suggestion with its probability, or as the three likeliest when none reaches even odds. It writes nothing. It asks only a URL that parses as loopback with no credentials, because the goal is the person's own words and a prefix test would pass http://127.0.0.1:1@host; it defaults to Kev on 127.0.0.1:8009, and a goal the words already cover opens no socket. It is the fifth command that opens one, and the security invariants list it.

The docs say where Jev stops. It is a capability a runtime may consult before it asks ambit can, and never a substitute for the grant: a published test moved its probability of blocking a destructive command with injected text, and a grant a person set in advance has no such input. The hosted demo and the README examples carry a Jev server, so both show the node reached.

Authority for a window, a tree you can extend, and a decision that reaches you

A standing grant was permanent confirmation or permanent autonomy, so a person pairing closely with an agent for an afternoon either confirmed every command or granted autonomy for good. ambit authority grant <cap> autonomous --ttl=30m grants it for the window and no longer. Expiry is read at decision time and never written: an expired grant decides nothing, and whatever stood before it decides again, a standing confirm as confirm and nothing at all as a refusal. The first cut had an expired grant fall back to confirmation, which meant a row that had ended kept buying something forever; the test that encoded it is the test that now forbids it. A forbidden grant takes no TTL, for the reason a threshold does not, and --by names who declared the grant, as it does for promote and sandbox, without binding the grant to them.

The curated tree is general software development, and a team whose capabilities have other names had nowhere to put them. .ambit/techtree.json in the working directory, or AMBIT_OVERLAY_TECHTREE, is merged over the curated tree before seeding: a new id is a new node with its contract and prerequisites, an existing id extends the curated node's detection and requirements, and override: true replaces it. An overlay is data like the tree it extends.

An agent that drafts a proposal while the person is away from the machine used to wait. ambit dispatch <id>, or --dispatch on propose and approve, pushes the draft to a Slack, Discord or Telegram webhook, an ntfy topic, or any JSON endpoint named by --to or AMBIT_APPROVAL_WEBHOOK: the goal, the cost, what it unlocks and the commands that decide it; once approved, the signed artifact, so what the phone shows is what apply will verify. The channel is one-way on purpose. Nothing that arrives on a webhook mints an approval; the reply comes back through ambit approve on a machine that holds the key, and a chat is not one. Nothing is sent without a URL, and the four commands that open a socket are listed in the security invariants.

The infrastructure scan reads the local Docker socket when there is one, with a single read-only request, so running containers appear beside the manifest's services on a device:docker node, with their image, state and ports. No socket is a quiet absence; a socket file with a stopped engine behind it is a warning. Four capabilities gained declared checks, each one command that reads and changes nothing: whether MCP servers can launch (npx or uvx), whether there is a client to query data with, whether a delivery CLI is present, and whether the mesh or the engine answers.

The decision, both ways, and what an agent may do

The README promised approval in one click, and on every machine that had not declared a web actor by hand the click failed: the engine refuses a decision from a person the graph does not know, and nothing had declared the person at the browser. The API declares them on the way in now, once, as the person at this machine's loopback port. Refusal had no route at all, so a no made in the browser vanished and the record the next draft learns from was one-sided; the card has a Turn down button, an optional reason, and a Turned down tab, and the engine records who and why. Steps drafted by the engine, which are shaped {id, name, chosen}, printed as their own JSON on the card; they read as a name and what supplies it.

Authority is per action, and the panel said only the capability's own mode. It lists what the capability may do now, each action with whether it asks: read the output without asking, run a command with. What work asked for and never had, which reached the page as a fragility footnote, heads the queue of what to reach, with how many times it stopped work and whether the same cause recurred, since a capability that stopped work four times this week outranks one that would be neat to have. The ways to acquire a capability were a list; they are compared, cheapest first, cost as length on one scale, privacy as a tag, and the one the record of this person's decisions favours is marked, which is the same choice ambit propose would draft.

Two smaller things. A repository missing a server the global config has offers the entry ready to paste, composed by the endpoint that exists for exactly that; the entry still crosses into the config by the person's own hand. And the briefing an agent is given at connect, the MCP resource ambit://briefing, is a tab in My Setup, so what the agent believes about the machine can be read by the person it believes it about. Reading it applies any authority threshold the evidence now supports, as the resource and ambit briefing do, and does not mark the environment briefed. The detail panel says how long a capability's configuration has gone unchanged, once it is two weeks, which is the glossary's decay with its name on it.

Every state carries its reason and its next move

An audit of the web app for what a person could decide from it found the screens strong on state and weak on why and so what. Reached, next step, blocked, passing and failing were all drawn; what stood behind each, and what to do about it, mostly lived in the terminal. Most of it was already computed.

The governance half had no web surface. The Time & cost page now has one: reached capabilities by whether they act without asking, ask first, or are forbidden, drawn as one bar in the assurance figure's three inks; the grants the engine says have earned a threshold nobody set, each with how often it was confirmed by hand, what passed, and the command that records a threshold, which stays a command because promotion needs a person; standing budgets as spend against ceiling; and sandboxes. The detail panel states the effective mode on every node that has one.

What to reach next is a ranked list with its reasons, the same three ambit next prints: what has blocked work and how often, what reaching each would also reach, and the setup time, with the basis named, observed or structural. The frontier was a set of outlined circles with a cost. The opportunities table keeps its own question, what would pay back, and is titled that.

A blocked node used to be a faded circle whose panel listed needs without saying which were unmet. The panel now says what it is blocked by, every hop up, and how long closing the gap would take; a third simulation draws the gap on the map in amber. Each era's header carries the setup time left to finish it.

The outage cascade painted everything downstream red, so losing one of two providers read the same as losing the only one. The tree payload carries each node's providers and each edge's kind now, and the simulation draws what stops in red and what only loses a provider in amber; the panel and the banner count both. The runtime, which contributes every entry read from its config, is no longer any entry's single point of failure in ambit status or on the page.

A proposal card showed the goal and the steps. Deciding needs four more things the engine had stored and never shown: what it would save, from the economic case; what it costs, from the steps; whether every step has an inverse, which is what ambit apply will run; what it unlocks, from the stored simulation; and how this person has decided on things like it, from the record of approvals and rejections. All five are on the card, and the demo's cards carry the same, written by hand like the rest of the demo.

Evidence says how many runs it rests on, "check passed, 12 of 12 runs", where it read the same for one run and for forty. Failures the runtime reported are classified by the engine and listed on the panel by cause, a 401 as permission, a missing binary as a tool, so "check failing" says why. My Setup's evidence column carries the same. And the week's movement, gained, emergent, lost and diminished, is a strip under the Time & cost title, where the ledger knows what became reachable without anything new providing it, which no per-component changelog can show.

One map, one list, one count

The web app had two maps and a list of the same things, and the header counted a mixture of them. The tree was one map. My Setup was a second, the machine's entries drawn as nodes in domain columns with nearly every edge running to the runtime, which is a list drawn as a star. The capability list docked on the left was a third of the window, open by default, listing what whichever map already drew. The header pill read "42 of 60 reached" on a demo whose own welcome page said "15 of 33 on the tree", because it counted the entries in with the nodes; on a live machine the list said 170 and the map drew 39.

The map is the tree, and only the tree. My Setup is the list it always was, full width: one row per entry a config declares, with whether it is on, what the engine's check said, and the nodes on the map it provides, each a link onto the map. Repositories and infrastructure, which were tabs inside the side panel, are tabs here, since they are the other two things a machine has. The docked list is gone; Search, on the header and on /, finds anything by name and opens it where it lives, a node on the map or an entry in My Setup. The store holds one list for all of it: the engine's tree, which already carries the entries and the edges from each to the nodes it proves, merged by id with the config read-out, so an entry keeps the config's facts and gains the engine's evidence, and switching views never goes to the network.

The header counts one population per view. On the map it is three segments, reached, next step and blocked, each of them the same control as its legend key, so the count is also the way to see those nodes alone; the glossary calls next step and blocked the informative states, and they used to be absent from the number that led. In My Setup it is enabled of total, over entries.

Selecting a node used to light the whole connected component, both directions, every hop, so a keystone lit most of the map and needing and enabling read the same. One hop now, and apart: what the node needs in teal, what it enables in indigo, with the rest dimmed; hovering previews the same. The transitive answer is the simulation, and the detail panel states it before the button that draws it: "If this went down, 14 other capabilities would stop working." It used to offer ambit impact to copy into a terminal for a question the page could answer. The panel's one list of neighbours, each with an edge word, is two lists, Needs and Enables, keyed with the map's two colours. ambit verify stays a command, because a check executes.

Rows inside a column used to follow insertion order, so height meant nothing while the Docs said it showed how far up the tree something sat. Each column opens in state order, reached first, and a few barycenter sweeps pull every node toward the mean row of what it connects to; edges are curves that leave and arrive horizontally, so a bundle into one node fans instead of converging through everything between; a long name wraps onto two lines where it was cut at eighteen characters with an ellipsis. The Docs say what height means now, which is nothing on its own.

The Shared credentials lens coloured any node whose id contained github, docker, 1password or credential, a string match dressed as an analysis, on a map whose curated nodes never matched it. It is withdrawn; credentials are ambit credentials, the engine's report. The Attention lens is offered disabled, with the reason in its tooltip, until the ledger has recorded something to colour; it used to grey the map and lay a note over it. My Setup's domain columns were named Foundation, Pipeline, Guard and Fortress, none of which is a word the glossary or the detail panel uses, and Foundation is also the tree's first era; a graph without eras names its columns with the domain's own word.

Two figures were being drawn as measurements of nothing. Time & cost showed "0h saved, $0 a year" on a machine whose ledger held runs but no interventions; a saving is a difference between months, so the figure waits for an intervention or a second month and says so. "One provider away from lost" listed most of a machine, because a runtime contributes every entry read from its config and so is each one's single provider by construction; ambit status and the page exclude the runtime from that report, and ambit impact runtime:<name> still answers for the runtime itself.

Narrow screens open on My Setup unless the link names a view: at phone width the map is texture and the list is not. The hero recorder writes the README's two stills as well as the GIF, so the pictures of the product are made the way the product is.

The map says less, and the panels say each thing once

An audit of the interface for marks and controls that repeat something already on screen, or explain nothing, found a dozen of them.

On the map, a next-step node wore two rings: the outlined circle the legend keys as Next step, and a second, wider one outside it that filled in proportion to a readiness fraction no surface defined. Above many of them sat an amber tag reading Boost, a word in no legend, glossary or document. Both are gone; the outline and the setup cost beside it stay, which is what the legend and the Docs overlay say a next step looks like. The tooltip no longer opens over the selected node, whose detail panel is already saying everything it would, and its last line now says what a click does: it opens that panel, where the simulation is a button. It used to promise that the click would run the simulation itself.

The zoom controls are one control shorter. The percentage reading is the button that returns to actual size, so the 1:1 button and the divider before it are gone, and the lens buttons dropped the small keycap after each name; the tooltips and the Shortcuts tab still carry the keys. The controls also stopped moving. They were sticky inside the scroller, and centring a node carried them off the left edge of the window; a left inset did not save them, because Chrome measures it from the scroller's padding edge and other engines from its border edge. They are laid over the map now and stay where they are.

The detail panel opened with three boxes before its first sentence: a tinted banner for keystone, a bordered one for the check, and a third around the config switch. Keystone and evidence are lines now. A Notes block that said "Not reached yet" under a header reading "Next step" is gone, and the one fact the header cannot carry, a node with no edges at all, is a sentence. The Details list no longer prints state, next, era, era name and a count of setup seconds, which are the status in the header, the column the node sits in, and the cost the map writes as "15m"; setup time is written the same way here. Beside each neighbour the panel said hard-dep or soft-dep, the one place the data model's words reached a reader; it says required, optional or enables. The two commands are lines with a copy control, not bordered rows.

The Proposals overlay lost its search box. A machine holds a handful of proposals at a time and the three status tabs are the filter; a search over five cards was a control that never earned its row.

The Docs overlay's three reference tabs used eleven class names that no rule defined, so "Click a node" ran straight into its explanation, the line samples for Required and Optional were empty spans, and the shortcut keys were styled inline. The rules exist now, and the rules that did exist, for two tables nothing rendered, are gone, along with the tokens nothing read: a copper spectrum, a type scale and a line-height scale no rule used. Reading the Map explained outlined circles twice, under "Solid vs outlined" and again under "The states", the second time mentioning a halo the map no longer draws; it is one section, in the legend's order. The glossary's entry for maturity pointed at a ring around each node that has not been drawn for two releases.

The Vite dev proxy follows AMBIT_API_PORT, the variable the API server already reads, so the two can move together when 3000 and 3001 are taken.

One word per thing, defined where you meet it

An audit of the interface for house shorthand found five concepts with four names each. The circle whose prerequisites are met was Next step in the legend, one step away in the header, next in the glossary and the frontier in the README. The ● node was a Combo in the legend, a Possibility in the detail panel and a Tech tree node in the docs. The ◈ node was a Server, a Tool server and an MCP server. The panel on the left was List on its button, Capabilities on its tab and the console in the code. None of it was wrong, and all of it defeated the learning a reader had already done. There is one word for each now, and src/client/vocabulary.test.ts fails the build if a retired synonym reaches a user-facing string.

The glossary was a dictionary behind a button: twelve definitions, in no particular order, disconnected from the screen that raised the question — and its own overlay opened by saying "Nine terms carry all the meaning here". The count is read from the file now, the file is ordered the way a reader meets the terms, and the words carry their definitions with them: a dotted underline in the header and the detail panel opens a card with the same sentence ambit help <term> prints, and the map's legend keys and era headers — SVG, where a popover cannot go — say it in a tooltip. Six terms that were on screen and in no glossary were written: keystone, tool server, attention, proposal, and the two halves of required-versus-optional.

Keystone and bottleneck were one idea under two names counted two ways: the map marks three or more dependants, ambit status counts the combos a capability unlocks. The glossary now states both rules under one term rather than implying they agree, and ambit help matches a concept's whole text, so looking up either word finds it.

Reached was the word for an MCP server that is switched on: the detail panel read "Tool server · Reached" directly above a switch labelled "Enabled", two words for one fact, disagreeing. My Setup says Enabled and Disabled, which is what its statuses mean; the Tech Tree says Reached, Next step and Blocked, which is what the legend beside it says. The two filters that had the same five words a few pixels apart — one over the map, one over the list — now each say which they act on.

The half of the product you could not get to

An audit of the web app for things that worked and could not be found turned up two kinds of problem: state the URL could carry and nothing could produce, and endpoints and actions with no control anywhere on screen.

Time & cost — where a person's hours went, what they cost, and what would pay back fastest — rendered only when the store held demo data, so the tab did not exist for anyone running Ambit on their own machine. GET /api/loop now composes it out of the reports the CLI already prints (status, attention, opportunities, roi) plus the one query none of them makes: hours in the loop month by month, annotated with the proposal that landed. A ledger with nothing in it says so and names the two telemetry bridges that fill it, rather than drawing a page of zeroes that reads as "your time costs nothing".

Every view was linkable and no link could be made. The address bar now follows the view — graph, selected node, lens, filter — and Share copies it. ?treeFilter= had been readable from a URL and settable from nowhere; it is a control over the map, and compact, which was in the accepted list and in no renderer, is gone: it fell through to "frameworks only", a filter that hid the graph and could only be reached by typing it.

The legend's keys were buttons that spotlight a kind of node, with nothing to say so — they carry the affordance now, and a row that says what clicking does and how to clear it. A node's tooltip says which simulation a click will run, because the unlock simulation lives on faded nodes that read as scenery. The map says when it is live, since it redraws itself whenever the graph is rebuilt elsewhere. The attention lens explains itself when there is nothing to colour.

/api/repos/scan and /api/infrastructure/scan had no caller in the client: the work was done on request and thrown away. Both are tabs in the side panel now. toggleMcpEnabled had no button, so the one edit the browser may make to a real config — enabled: true|false on an entry that already exists — was unreachable; it is a switch on an MCP node when an engine is behind the page. loadFromJSON had no drop target, so "what does this look like for my setup" meant cloning the repository; the welcome screen takes an opencode.json and maps it in the tab, uploading nothing.

Two things were broken rather than hidden. Every "Map" button on the opportunity table matched the row's id against three hardcoded strings and fell through to the same fallback node, so all three led to Wrangler; each row now names the capability it prices, and the button switches to the map with the unlock simulation running. The demo's attention counts were seeded into the store's initial state, so a live machine briefly coloured its map from the fixture.

The docs say each thing once

The README had two install sections that named Homebrew, Codespaces and the MCP registration twice each, and a nine-bullet reference on delegation records sitting in what is otherwise an introduction; the install is one section now and the record reference moved to the deep dive. The deep dive carried five "real-world walkthrough" scenarios whose console output the engine has never printed — cascade risk: CRITICAL, Available Frontier: 142 capabilities — in a voice that belonged to a brochure; they are gone, and the three honest cases in the README stand. Its canvas section, written in set notation with glowing emerald green, is a paragraph on what the map may and may not touch. The roadmap's §12 and §13 were specifications of things since built, three thousand words of them, and are now the decisions each part made and why, with the deep dive as the reference; its status table stopped saying nothing promotes a capability on evidence and nothing records use, both of which have shipped. The why essay's postscript, which listed what existed as of a version that no longer does, points at the roadmap instead. CONTRIBUTING had its list of good first contributions twice and the security invariants a third time; once each now, with the invariants in the agent guide alone. Roughly five thousand words fewer, and no claim in any of them that another file contradicts.

The front door shows measurements

The landing page drew four circles labelled LLM, MCP, Tool and Goal for a product whose whole claim is that it measures things. It now draws the two measurements the demo makes, from the demo's own example data and labelled as such: a year of a person's hours with the two acquisitions named beside the drops they caused, and seven eras as small multiples on one scale with the reached fraction beside each bar and the next step drawn hollow. The sparkline moved into a shared figures.tsx so the landing and the dashboard are one drawing hand, and its annotations — which the dashboard had been marking with an unlabelled tick — are now named, staggering onto a second row where two would overprint.

The map's era columns say Era 5 · 0 of 5 under the name with a three-pixel fraction bar, all seven on one scale so a short bar is a small era rather than a poorly filled one. The status pill draws the same fraction it states, where a green dot had stood for nothing. The dashboard's money column gained the scale header the other columns already had, the interruptions column names its unit, payback marks say mo, the assurance caption carries 38 of 49 proved · 78%, and acquisition options put the dollar figure first in a fixed column so two alternatives compare at a glance. The capability list's filled, bordered status pills — three encodings for a two-valued fact, sixty times over — became a dot and a word.

The map looks like one product

The client had two palettes — the slate-and-indigo tokens, and a cyberpunk cyan/magenta/neon-green the tree painted its nodes with — and fifteen CSS classes that no rule defined, so the zoom controls, the toast, the small buttons, the dashboard's filter tabs and the sidebar toggle all rendered as browser defaults. Every kind of node now has one colour, read from App.css by the map, the detail panel and the docs alike; the missing rules exist; the favicon and the social card are drawn in the same indigo as the app. The landing page renders on its own, without a capability list reading "(0)" behind it. The time-and-cost view sits beside the capability list instead of under it. The lens switcher moved from the top bar onto the map it colours. The map opens fitted to the window, so the seventh era and the runtime column are on screen instead of past the edge; the first-run card sits top right instead of on the legend; the legend describes the view it is under. The proposals panel says "waiting for your approval" and "approve and sign" where it said "AWAITING OPERATOR RATIFICATION" and "Ratify & Sign Policy", and signs as the web surface rather than as the author. Muted text passes AA contrast, every control shows a focus ring, and motion respects the reduced-motion setting.

The README's line is now also the page title and description, the package description, the citation title, the agent guide's first sentence and the social card. Thirteen unreferenced brand PNGs and a byte-identical duplicate of the social preview are gone; npm run assets:generate draws the card and the favicon and nothing else, and the README screenshots are captured with the first-run guide off. ambit status draws its per-domain rows as bars, so the first-run report and the command print the same thing; bootstrap.sh no longer keeps its own copy of that rendering, nor a tt alias. The launch kit moved out of docs/ into .github/.

The numbers are drawn, not narrated

The demo's Time & cost page said "Today: 43 interventions a month, 8.6h of your time, $2150/mo. After: 0.8h a month, saving $1935/mo. Pays back in 0.6 months" — five figures in a sentence, three times over, with no way to compare one row against the next except by holding them in your head. The figures have not changed. They are now on shared scales.

What to set up next is a table with a graphic in each cell: the hours as a dumbbell from where they would land to where they are, every row on one 0–9h axis drawn once in the header; the money recovered as a bar on one scale; the payback as a mark against the month it has to beat. The old payback bar filled in proportion to nothing at all.

The headline saving is drawn as the thing it is. Twelve months of hours run under it with January held as a dashed reference, and the shaded band between them sums to exactly the 41 hours the number claims. Two months carry the acquisition that caused the step down. The forecast tile shows 37 predicted against 41 spent on one axis, with the labels on separate baselines because the two marks sit closest together precisely when the forecast was good. Four status chips became one part-to-whole bar; green and red never share an edge, because under deuteranopia they are the same colour at ΔE 5.6, so the grey segment separates them and every segment carries a written label.

Interruptions worth removing and decisions worth keeping were two lists side by side, which put a gap through the middle of the comparison the section exists to make. One chart now, one hours axis, sorted, with the keeper in the recessive grey under a hatch that survives greyscale and forced-colors.

The attention lens on the map painted a magnitude in two hues split at an undocumented count of twenty, and its legend never mentioned it existed. It is one hue in four steps now, brighter with more, monotonic in luminance and each step clearing 3:1 against the canvas, with bins taken from the data and printed in the legend under the unit they count. Its demo data pointed at three nodes no fixture contains, so pressing 2 on the view the demo link opens dimmed the map and warmed nothing; the counts now name real capabilities and are the same four the Time & cost page prices, so the two surfaces read one ledger.

One fact, written down once

An audit for redundancy turned up two defects and a set of places where the same rule had been copied until the copies disagreed.

Recording a spend against a capability with no budget created a budget with a ceiling of zero, so a single recorded cent refused every later spend for ever — through a row ambit budget did not list, because a report of standing budgets reasonably skips the ones with no ceiling. Spending where nothing was delegated is now an absence of a budget and says so.

ambit sync carried human interventions and not the work runs they hold a foreign key to, so every intervention was silently skipped on import and counted as skipped. Runs, capability use and outcomes now travel, which matters more since successful work became the evidence that earns authority: a rebuilt container was losing exactly the record that had been earning it.

The graph-summary query had five copies, two of them byte-identical inside the MCP server, and the visualiser's counted reached as not-locked where every other surface counted unlocked-or-active — agreeing only because a third state has never been added. There were four wordings of "the graph is not seeded" offering three different fixes, two identical intervention-kind vocabularies under different names, a third partial copy of the same list, and five copies of the default config mapping differing only in the two words before "{type} server". All of it now comes from src/engine/vocabulary.ts and one parameterised mapping builder.

The formatter dropped nested objects entirely, so ambit can printed six of its nine fields and the governing grant and per-target evidence were visible only with --json. Asking over MCP recorded the deficit behind a refusal and asking on the CLI did not, so the same question had different consequences depending on where it was asked. A sandbox and a standing budget both widen what runs without a person and neither appeared in ambit authority or ambit status. ambit goal warned about a conflict with the alternative a proposal would not choose. Deficits captured from a runtime attribute to the provider that failed, which the curriculum could never see, so they roll up to what the provider supplies. An unseeded graph made the MCP server move every tool's answer under a nested key, changing the typed surface's shape according to the state of a database; the notice now rides on the text half only.

The tracker plugin honoured only one of the two database environment variables and then fell back to the session's working directory, wrote without the busy timeout the engine sets for four concurrent writers, and omitted kind — so every combo it created was stored as a provider and was invisible to half the product. Seven dead files are gone: two plugin aliases that re-exported names the canonical files already re-export, two component shims of the same kind, and three unreferenced scripts, one of which wrote a fixture that no longer exists.

What makes the loop widen, not just report

The previous entry gave an agent a way to notice a gap, name it and ask. Nothing in that made the environment more capable: an agent could be told no all week and end it exactly as able as it started. Nine changes to the part that decides whether anything comes of the asking.

A threshold now counts successful work as well as passing checks, so an environment where the job succeeds daily stops having to run synthetic self-tests to earn trust it has already demonstrated. A failed run still counts against nothing, because attributing a run's outcome to every capability it touched would demote whatever a bad afternoon went near.

ambit authority promote with no arguments now names the grants that have earned a threshold nobody set. The interruption a threshold would end is exactly what stops anyone noticing it could, so the graph says it rather than waiting to be asked.

Scope decides. Two rules, in order: a forbidden grant wins outright at any specificity, and among the rest the most specific covering scope governs. Under narrowest-wins alone a grant saying "autonomous on staging" could never beat a standing "confirm everywhere", which made the only trade anyone actually wants inexpressible. --scope on a promotion writes that grant and leaves the standing one untouched. ambit authority sandbox declares somewhere acting does not matter, which is where a scoped threshold gets met cheaply; it relaxes confirmation and never a refusal.

ambit budget set <cap> --amount=$20 --by=<person> grants standing spend. Budgets existed and could only be written by the code that recorded spend, so the ceiling was real and the delegation was not.

ambit reject <id> <person> "why" records the half of every decision that used to vanish. ambit preferences --observed reads both halves, and ambit propose drafts the alternative the record favours and says why. A trait needs three decisions to count, and one that has gone both ways reads as contested rather than settled by majority.

ambit proposals --pending and the approval push carry the decision rather than announcing that one exists, and ambit approve takes several ids and one name. Every acquisition costs one interruption; a week of drafts read together is one sitting.

ambit reversible publishes what could be acquired without a person and what could not, which is the same list read backwards. ambit objects <target> and ambit verify --target= give authority and evidence an object, so committing to one repository forty times stops being a claim about the next one.

Five more MCP tools, twenty-two more tests.

Ambit reaches the agent at the moment friction happens

Every command until now waited to be asked, which is no use to the agent that hits a missing binary mid-task, works around it, and hits the same one next week.

ambit briefing, served to a client on connect as the MCP resource ambit://briefing, says what is reached and proven, what is configured but failing, what is waiting on a person, what blocked work in the last week, and what is worth reaching next — prose, capped near 1,200 tokens, trimmed from the bottom because the order is the order of usefulness.

The ledger fills itself. The telemetry bridge reports failed tool executions and the engine classifies them from the signals a runtime states outright — a shell's message for a missing binary, EACCES, ECONNREFUSED, a 401, an MCP error kind — into the classes the deficit reports already used. A failure that only means work went wrong is left alone; one that cannot be attributed to any capability is kept and named, because that is a gap in the model rather than in the environment.

ambit_can now answers yes, ask or no with a sentence a person can act on, and files the deficit itself on a refusal, so asking before an unfamiliar tool costs one round trip rather than two. The README and llms.txt carry the two lines that make it a habit.

ambit next answers what to reach next and why — by what has actually blocked work once the ledger has observations, by leverage per hour of setup before then, and it says which. ambit record skill:<name> --provides= --verify= puts a skill the agent wrote on the map, refusing any registration without a read-only check and running it on the way in. ambit authority promote <cap> <action> --after=N --by=<person> is the person saying stop asking me once this has proved itself: the grant widens when the evidence arrives and narrows on a single failing check, with nobody asked. ambit sync export|import moves the graph and the ledger between machines, carrying no commands and no grants, so a rebuilt container gets its history back without importing someone else's permissions.

Seven MCP tools and one resource; twenty-seven new tests. The remaining piece is the npm publish, which is why every path in still starts with a checkout.

The rest

  • Every query names its row. src/engine/rows.ts declares what a row of each table looks like, matching schema.sql plus the columns migrate.ts adds, and the handle's all and get take that type at the call. Sixty-odd reads that used to cast to any now say which columns they select, which is how the compiler found a report reading a goal_id column the proposals table does not have, and an intervention with no capability being used as a map key.
  • The client store is ambitStore.ts. The guide said so; the file was a 12-line re-export of toolchainStore.ts. The names now match the guide, the demo fixtures live in store/demo.ts so the store reads as the product's state rather than its sample data, and the old name stays as a shim.
  • App.tsx is a shell. The viewport, hotkeys, first-run guide, toast, and the AG-UI stream are hooks; the top bar, welcome screen, guide card, and toast are components; the URL rule is a pure function the demo-landing test imports instead of copying. Two hundred and sixty lines where there were seven hundred, and the stream and key listener mount once instead of re-binding on every render.
  • The API server lives at src/server/api.ts, beside its config, scanners, and tests, rather than at the repository root.
  • The roadmap opens with a status table: eleven sections, what each has built, and what each still lacks, so a reader does not have to find the "unbuilt" paragraph eleven times.
  • Getting to a first run is shorter. The README opens with a four-row "try it" block (hosted demo, Homebrew, Codespaces, MCP registration), Homebrew is documented as an install path for the first time even though the tap has shipped every release, and a .devcontainer seeds a graph and starts the map so Codespaces is a one-click checkout. ambit web no longer insists on Bun, which nothing else in the repository needs. New readers get a FAQ, a SUPPORT.md, a comparison table against tool-RAG, workflow graphs, and package managers, and an llms.txt on the demo site for agents deciding whether to recommend the tool. A server.json and mcpName are ready for the MCP registry once the npm package exists, and the launch kit lists the directories to submit to. A first issue or pull request gets a reply that says what CI will run.
  • The CLI groups forty-two commands under five nouns — graph, plan, check, govern, report. Grouping is presentation, not a rename: every flat verb still dispatches, so ambit impact x and ambit graph impact x are the same command and nothing anyone has scripted stops working. ambit help covers a first session and ambit help --all is the full surface.
  • The MCP server advertises each tool once. Every tool was listed twice, as ambit_* and as the legacy tt_*, so tools/list returned 96 entries for 48 tools and spent about 3,600 tokens of every agent's context on duplicates. A tt_ name written before the rename still dispatches; it is no longer advertised. Results now carry MCP structuredContent alongside the text block, so an agent reads a field rather than parsing a string.
  • An approval is only spendable on what it authorised. verifyApproval checked that an artifact was intact, unexpired and signed by the recorded approver, and nothing else — so any approved proposal was a bearer token for any gated action, and an approval to install a linter would authorise a production deploy. approvalCovers closes it: an approval is valid only for a capability that appears in the steps of the proposal that was approved.
  • CI measures coverage with a floor under it, and runs on Node 24 as well as 22.
  • Autonomous Control Plane & Safety Interception: Active runtime interceptor (src/control_plane/proxy.ts, src/control_plane/cli.ts) that enforces capability DAG prerequisites, lifecycle checks (degraded/broken), and authority policies before tool execution.
  • AMBIT_BLOCKED_UNAUTHORIZED & State Invariance: Rogue or out-of-order agent tool executions (such as unapproved production rollouts) are blocked at the control plane boundary with exit code 2, guaranteeing pre_state == post_state.
  • Human-in-the-Loop HMAC Remediation: Blocked executions generate structured remediation proposals and cryptographic HMAC challenges (ambit approve <proposal-id> <person>), minting signed artifacts verified prior to state mutation.
  • OpenTelemetry Trace Instrumentation: Spans and structured event records capture DAG evaluations, missing authorization nodes, HMAC challenges, and verification receipts.
  • Verified Incident Trace & Demonstration Suite:
    • Published Incident Report: docs/incidents/INCIDENT_TRACE_001.md
    • Automated suite: src/control_plane/proxy.test.ts — eight cases in the suite CI actually runs, replacing four pytest cases that no job had ever executed.
    • 90-Second Reproducible Demonstration: scripts/demo_incident_trace.py (demo:incident) and docs/incidents/demo_intervention_trace.cast.