Skip to content

Roadmap

Phases were gated: a later phase did not start until the governed core was solid. Every phase is shipped; the releases that follow them close this page.

Phase 0 — skeleton. Shipped. Monorepo, schema package, design system, site, docker-compose skeleton, CI with lint/typecheck/tests/license gate + CLA bot. @getlibero/cli placeholder published with provenance.

Phase 1 — the governed core. Shipped. Slack gateway and agent loop — a hello-world agent answers a mention in a real channel — with a mock Slack harness for tests. Proxy end-to-end: vault, team-sheet enforcement, one real MCP server (GitHub), HITL approval cards, budget meter, audit log + CLI. Channel router, attribution, live checklist. FTS message store.

Definition of done: a prompt-injected agent in a test channel cannot exfiltrate a secret, call an unlisted tool, exceed budget, or act destructively without a human click — demonstrated by e2e tests that try. Those tests live in e2e/, one file per property, and they pass against every change.

Phase 1.5 — consolidation. Shipped. What phase 1 built, made solid and operable before memory lands on top: pool discipline against a hostile or broken upstream, a bounded shutdown drain, spend-denominated budget caps that fail closed on an unpriced model, the CLI’s host-authored half — init, channel add, doctor — OAuth for MCP upstreams, and deployment guides for GCP and AWS.

Definition of done: a hostile or broken MCP upstream cannot wedge the pool, bypass the listing bounds, or widen a channel’s grant; SIGTERM loses at most one turn’s spend, within a stated bound; a channel can be capped in dollars, failing closed on an unpriced model; the quick start’s first command either works or fails loudly; an OAuth-secured MCP upstream can be declared in a sheet and called, with the proxy minting and rotating the token and the agent never seeing it; an operator can follow a guide from a fresh GCP or AWS account to a working mention-and-reply.

Phase 2 — memory. Shipped. Curation inner loop with tests, MEMORY.md tooling, sqlite-vec semantic recall.

Definition of done: an agent curates MEMORY.md through a post-reply inner-loop turn with size-capped writes a later task reads back; semantic recall answers over thread summaries from the same per-channel file; both layers hold the one-file-per-channel isolation boundary and the curation write path survives the e2e suite’s attacks.

Two things landed differently from how this phase was first written down, and both are decisions rather than shortfalls. The writes are not locked — a lock file outliving a killed process is a worse failure than the one it prevents, so what replaces it is an atomic rename and a synchronous interface with no point at which a second operation could interleave. And recall answers over thread summaries and not over curated facts: MEMORY.md is already injected whole into every task’s opening context, so retrieving over it would replace all of the corpus with some of it. Summaries are the corpus too large to inject, which is what makes them the one worth searching.

Phase 3 — skills. Shipped. Author turn, retrieval-based loading, lifecycle job, curator-as-diff.

Definition of done: a qualifying task leaves a skill that a later task on the same subject retrieves and loads, and an unrelated task does not; retrieval records use, so the lifecycle clocks run on real signal; the files are the source of truth — a skill the team hand-edits is re-indexed and one the team deletes is gone; and the skill layer survives the e2e suite’s attacks: authoring cannot escape the channel’s skills directory or its size caps, and a poisoned skill loaded into a later task widens nothing — every call it induces still meets the proxy’s gates.

Three things landed differently from how this phase was first written down, and all three are decisions rather than shortfalls.

The curator does not produce a diff, which is this phase’s own name for the item. A merged playbook is a rewrite rather than an edit, so hunks over two rewritten documents are unreadable — and a diff format would imply a patch tool that does not exist here. What a proposal shows instead is three whole documents: the merged file as it should read, and both originals beside it. Applying one is a paste over one file and a delete of another, which is one unambiguous act rather than a surgical edit.

Where a proposal goes was forced rather than chosen. The obvious surface is the channel, and this process cannot reach it: postThreadReply is deliberately withheld from the composing app so that a handler cannot post out of band, and an approval card needs a thread from an inbound event that a background pass does not have. A proactive post is ambient mode’s mechanic, and phase 4 wired it (#320): a waiting proposal is now named in the channel once, while the file stays the review surface. So a proposal is a markdown file in the channel’s own state root, and declining one is still deleting it.

The lifecycle job runs on channel activity rather than weekly. The clocks are absolute dates, so the job is idempotent: running it more often moves nothing sooner than its threshold and running it less often only delays. “Weekly” is a statement about how often a status needs revisiting, and any interval at or below it satisfies that — where a cron would mean this process growing a timer and an enumerator over every channel, neither of which anything else here needs.

Phase 4 — ambient. Shipped. Heartbeat, proactive posts and their rate window, schedule_task and the clock that fires it — all behind budgets, and all off unless a channel’s sheet says otherwise.

Definition of done: a channel that opts in gets a heartbeat that posts only when something merits it and stays silent otherwise; a question younger than the answer threshold is never answered proactively; the rate window and the one-post-per-firing bound are enforced deterministically; the model gets a future check only through schedule_task’s governed create, a fired task fires at its due time and widens nothing — every call it induces still meets the proxy’s gates; every ambient turn draws from the same meter and a capped channel goes silent rather than over; a channel that never opted in sees nothing; and the layer survives the e2e suite’s attacks, with positive controls proving a merited post landed and a scheduled check fired on time before any silence is asserted.

Five things landed differently from how this phase was first written down. Four are decisions; the fifth is a clause that is true in a weaker way than it reads, and saying so is better than ticking it.

Silence is calling no tool, not a SILENT sentinel. Every other background turn in this tree expresses declining as an empty tool list, and under that idiom the requirement that follows — an answer which is neither the sentinel nor a postable finding is treated as silent — holds by construction rather than by a branch somebody has to write correctly. A malformed call, an invented tool name and a paragraph of prose all produce no finding.

The rate window bounds spend, not only speech. It is consulted before the evaluation, so a heartbeat that could not post does not evaluate and does not advance its watermark — which is how a shut window came to defer a finding rather than lose one. Evaluating first would have forced a choice between losing the finding and paying for the same turn every tick until the window opened.

A capped channel does not always go silent, and the line above is the wording this phase changed. A capped heartbeat is silent: nobody asked, so nothing is owed. A capped channel’s due check is not — it fires, spends nothing, and posts once to say it did not happen. The reason is that somebody approved that check and is expecting it, and a reminder that silently slips is worse than one that says it could not run: the team can still act on the timer themselves. That decision also removed a queue, a backoff and a staleness rule, all of which existed only to keep a check alive until the meter reset.

A fired check fires once, whatever it produced. It posts an answer, it runs and has nothing to say, or the channel is told it did not happen — and in all three the ticket is done. There is no retry, so a check cannot arrive days late, and no state that leaves one pending, so nothing can consume a check that never ran. [ambient] off is the one silence: that switch means do not speak here, and a failure notice would be the agent speaking after being told not to.

“Every call it induces still meets the proxy’s gates” is true because a fired check induces none. It is one bounded turn over the channel’s recent messages with a single tool that posts, and no tool proxy client at all — so it can steer what it says and can reach nothing. That is the conservative shape and it is what makes the containment claim structural rather than enforced, but it is a narrower thing than the sentence implies. Giving a fired check the governed tool path is #348, and it is a design question — an approval card with nobody to click it, and a pending cap chosen against a much cheaper unit of work — before it is an implementation one.

Phase 5 — hardening. Shipped. Hardening pass on the proxy, audit hash-chaining for tamper evidence.

Definition of done: the audit log is tamper-evident — rows are hash-chained, an operator command verifies the chain and names the first broken row, and the e2e suite proves a rewritten row is detected after a positive control proves an untampered log verifies clean; audit argument capture is decided — built behind its flag with a redaction set the design argues is complete, or declined with the reasons recorded; the MCP path survives a hostile upstream at the transport level — responses stream through redaction rather than buffer behind it, and pooled clients have a lifetime and idle eviction sized against the token lifetimes OAuth gave them; and the sheet-store’s false error on a mid-write read is fixed or documented as expected.

Argument capture was declined, which is the second arm of its own clause rather than a shortfall: the reasons are recorded where the code is, and the gap it leaves — that a blocked call records nothing about what it attempted — is #364.

Three things landed differently from how this phase was first written down. Two are the drops below; the third is a clause whose stated reason turned out to be false while the thing it asked for landed anyway.

Idle eviction was not “sized against the token lifetimes OAuth gave them”, because a pooled client never held a token. That clause was written expecting the OAuth work to put an expiring credential inside a pooled client, which would have given eviction an obvious deadline. It did the opposite: the token engine introduced a credential source, so the client holds the source and mints per request, and can therefore outlive any token — which is a settled reason not to evict rather than a reason to. What made eviction necessary instead was the legacy-protocol fallback, after which a client holds a session at the upstream that was released only at shutdown; and key drift, since a sheet edit that moves a url or renames a credential strands an entry nothing will ever ask for again. The window is sized against what eviction costs — re-running the version ladder on the next call — and held above the catalog’s own, so a client is never dropped underneath a listing still citing it. The same collection was then owed one level down, in the catalog cache, where the rule had to be per resolution rather than per entry.

It was also first written down as “breadth” — a second platform adapter (Discord) and a durable multi-day orchestration option (Temporal) beside the two items that shipped. Both are dropped rather than deferred.

Discord: a second chat surface widens adoption, not the governed core, and every phase 1–4 feature has a Slack-shaped rendering — cards, checklist, proactive posts, the rig’s fake gateway — so an adapter is a re-answering of all of it, not a gateway swap. The one thing it would prove, that the gateway seam is real rather than Slack-shaped, is worth proving when a real team asks. Platform adapters are a v1 non-goal in the architecture’s scope section.

Temporal: the two long-lived things this tree has are already durable rows — an approval ticket waiting for its click, a scheduled check waiting for its instant — and phase 4 deliberately removed the retry-and-continue machinery an orchestrator exists to provide, on the argument that a reminder retried into arriving days late is worse than an honest “it did not happen.” A workflow engine would also put every step’s arguments and results into one shared history database, which is the wrong shape against the one-file-per-channel boundary. What “multi-day” turned out to gesture at is scheduling rather than orchestration, and that is parked as its own work (#358, beside #348) rather than gating this phase.

Phase 5 was the last phase; the list above is complete rather than paused. Delivery is milestone-gated per release now: each release gets one milestone whose description is its definition of done, and the open milestone is what lands next.

v0.3.0 — shipped. The release that made releases real: both service images published to GHCR on every tag with provenance attestations, a changelog an operator can upgrade by, and a written release procedure — plus the correctness items beside them: the approval card shows the exact call being approved, a cancelled scheduled check leaves a record, and a blocked call’s arguments land in an off-chain, deletable store the audit row’s own hash binds, without reopening the decision against argument capture in the chain.

v0.4.0 — shipped. Code execution, governed: the ephemeral container the proxy invokes (#368), which gave [egress] its first live caller (#219), and a third service holding the Docker socket and no credential (#393). The architecture page carries no designed-not-built marker any more, and the changelog has the operator’s account.

Three things landed differently from that milestone’s own wording, and the differences are recorded here rather than a box being ticked against a sentence that turned out to be untrue. It said the sandbox would be “approved by default”, which reads two ways; the default is approval = "required", argued in builtin.ts’s header rather than assumed, because the destructive-verb heuristic would have answered "none" for the one built-in that runs arbitrary code. The socket did not stay off the deployment — it moved, to a runner service that holds no credential, so what “the proxy still never mounts the Docker socket” now means is that the privilege and the credentials live in two different processes rather than that neither exists.

And the milestone closed with three of its correctness items moved out rather than delivered. Reconciling the proxy’s cost against a sidecar’s (#239), a measured distance cutoff for semantic recall (#283) and segmenting long threads (#284) are each gated on data or a deployment shape that does not exist yet — there is no LiteLLM sidecar to report a cost, nothing recorded a recall hit’s distance, and thread-length figures need a workspace that has had time to accumulate them. Parking them is the honest answer; what would have been dishonest is closing them, or holding a finished release open behind measurements nobody has taken. The work that would make the first two buildable is filed rather than left implicit (#427, #428), because a parked issue whose precondition is nowhere is a parked issue nobody can pick up. #427 has since landed, so recall’s distances are recorded and #283 is now parked on the analysis rather than on the data.

One thing landed that the definition of done did not ask for. The sandbox shipped with every bound on a run being the channel’s and none being the operator’s, so a sheet could ask for 64 GB and nothing capped how many runs a host held at once (#405). That is a gap the workstream named before it closed rather than one found afterwards, and it was filled inside the same milestone.

v0.5.0 — shipped. Shared skills — the changelog entry carries the upgrade notes. An operator publishes playbooks once into a third root, mounted read-only to the agent and to neither the proxy nor the channels directory, and each channel’s team sheet names which of them it gets with [[shared_skill]] (#373). Two load modes, because retrieval cannot serve the consistency case: load = "always" stands in every task’s system prompt, where a house voice has to be, and load = "retrieved" joins the channel’s own retrieval pool. [skills] enabled = false switches off neither — that switch governs what a channel grows for itself, and these were decreed rather than grown. Shared skills do not age, the lifecycle job and the merge curator never touch them, and the model has no verb over the root. A marketplace mechanism was declined rather than deferred: auto-updating text that enters a model’s context is an injection subscription, a runtime marketplace client is a new egress surface, and retrieval over content optimized to be retrieved is a contest the grown-only corpus does not have. Vendoring through git is the answer, and libero skill vendor is parked as #439.

Three sub-issues landed differently from their own wording, recorded here rather than ticked against sentences that turned out to be untrue. #436 asked that a body edit to a shared file re-embed it; it does not, because the vector stands for the skill’s description — so a body edit re-indexes the full-text side, keeps the vector and keeps the use counters, which is the whole of what that clause was protecting. Making it re-embed would charge every channel that named the skill for a vector identical to the one it replaced, on one operator’s typo fix. #437 asked for a fake embedder to place a hostile skill nearest and for an [egress] exfiltration leg; the attack suite answers on the lexical leg instead, because its one fake embedder deliberately ranks nothing — a ranking fake is the hand-built vector space that rule exists to keep out from between an attack and the thing it attacks — and the egress leg is attacked at the tool gates, because [egress] needs a real sandbox runner and this suite confines a Docker daemon to exactly one file. #450 proposed that the standing region reach the task and the proactive post; it reaches five turns, because the heartbeat’s decision and its sentence are one call with no seam between them, and because a shared skill is arbitrary operator text rather than only a voice — house rules about how a runbook is written belong at the skill-author turn and the merge curator, which that issue’s own reading excluded.

One thing landed that the definition of done did not ask for, and it came out of a cost the milestone chose to record rather than fix. Retrieval’s two legs are blind to which half of the library a playbook came from, so a channel that has since turned its own skills off keeps index rows that can crowd out the shared skills its sheet names. Purging them automatically on that switch would let one unparseable channel.toml destroy a channel’s use counts and first-seen stamps, since a sheet that fails to parse falls back to skills being off — so the answer is an operator-run command rather than a config side effect (#452).

Planned 2026-08-25. Four releases remain before 1.0, and the plan’s aim is that 1.0 is a validation release rather than a feature release: by the time v0.9.0 closes, every open issue is decided — shipped, scheduled, or recorded as post-1.0 with its reason — and what 1.0 adds is proof, which is the success criterion below rather than a feature list.

The ordering has one structural argument. Validation needs deployments, and deployments generate exactly the data three parked issues are gated on — the recall distance cutoff (#283) wants a real corpus, thread segmentation (#284) wants real thread lengths, and cost reconciliation (#239) wants a sidecar reporting figures. So the arc front-loads what makes deployments possible and lands the data-gated work last, once pilot usage has produced its inputs.

v0.6.0 — scheduling. Shipped. Recurring turns at a clock time, operator-authored (#358): [[ambient.rule]] in the team sheet (#460), a third DueEntry.kind on the ambient clock (#461), the attack suite reaching it (#462), and a heartbeat switch for rules-only channels. The example-sheet suite learned to tell a documented figure from an inherited default (#445).

Definition of done: a rule fires at its next occurrence and posts once; a rules-only channel gets rules and no heartbeat; injection cannot plant a rule and a rule’s turn induces no served calls — proven by the e2e suite after positive controls; #348 is decided; and the example-sheet suite tells a documented figure from an inherited default.

One clause of that landed differently, and it is the load-bearing one. “A rule’s turn induces no served calls” is true of every sheet that has not said otherwise, and is no longer unconditional: #348 was decided by being built, so a channel that writes [ambient] tools = true gets the ReAct loop over the allowlist its sheet already carries. Both of that issue’s blocking questions resolved against machinery that already existed — an unattended turn is handed no prompter, so a held call is refused rather than waited on, which draws a read-yes-write-no line off the destructive-name default; and the bound moves from the pending cap to daily_tool_calls, which the proxy counts from calls it served and which therefore holds against a compromised agent process. What the issue did not ask for is the part that decided the shape: by the time it was picked up the fired turn had two callers, so the capability landed behind a switch that is off by default rather than arriving in every sheet that already listed a tool. What survives unconditionally is the narrower claim — injection cannot plant a rule, because the sheet is the only write path and the model has none.

Three things shipped that the definition of done did not name, and the release was better for refusing to defer them. Rules gained an IANA timezone (#470), with absent meaning UTC so nothing written earlier changed meaning, and with the two days a year a wall clock is not a function of an instant decided rather than left to the arithmetic. The heartbeat evaluation joined the two fired turns behind the same switch (#471) — the argument for excluding it turned out to rest on a frequency the pregate already prevents. And a filesystem-watch test that had been given a longer timeout twice was rebuilt on a seam (#474), which is the difference between fixing a flake and postponing it a third time.

v0.7.0 — deployment shapes. Shipped. The release pilot deployments run from. Reaching a model is three chosen shapes with no default among them (#428 is the tracker) — directly against a provider, through a LiteLLM the operator already runs, or through the sidecar deploy/docker-compose.yml starts behind a litellm profile, with a worked model_list (#479), the four TokenUsage counts proven through LiteLLM’s envelope (#480), and a section of the docs an operator can stand any of the three up from (#481). That gave #239 its input: what a gateway charged is recorded beside what the price table computes, queryable per model, and never enforced on — enforcement stays deterministic and stays in the proxy. And the vault and token store run on a custody contract behind a backend seam (#261 is the tracker; #482 the seam, with the two encrypted files as the default backend), with Google Secret Manager (#483) and AWS Secrets Manager (#484) as managed alternatives an operator selects with one variable, and the master key arriving from the environment or from a file, exactly one of the two (#495).

Three things landed differently from the milestone’s own wording, recorded in the trackers’ closing comments and summarized here. Two shapes are three — a LiteLLM the operator already runs is a distinct shape from the sidecar this deployment starts, and the likelier one in production: it starts no service here, the provider keys sit with whoever runs the gateway, the hop leaves the machine, and the price-table alias is a spelling the operator may not control. The conformance proofs each needed a package rather than the files the plan named — a recorded fixture is a claim about a third party’s wire format, and the third party is the one who changes it — so packages/litellm-conformance runs the real adapters against the real image and packages/aws-conformance runs the custody contract against LocalStack, both daemon-gated in the sandbox CI job. And the proof found live bugs rather than confirming working paths: LiteLLM’s prompt_tokens is a sum the adapter was adding the cache tiers back on top of, so every cached token was charged twice — the order-of-magnitude metering error on a cache-heavy agent that the four tiers exist to prevent — and LocalStack found two defects in the AWS client that the repository’s own fake had mirrored.

One clause of the definition of done landed narrower than it reads, and the difference is recorded here rather than the box being ticked as though it had not. “Both stores run on a managed backend under an IAM policy the docs state” is true of what was built and not yet of what was proven: the GCP backend was verified against this repository’s own fake — real sockets and real version semantics, but the published REST reference as this repository read it — and the AWS backend against LocalStack, an independent implementation that still cannot check the signature, IAM, quotas, or KMS. The single largest unverified claim across both is therefore AWS SigV4, checked only against a verifier written from the same specification by the same hand, and deploy/README.md says exactly that where an operator will read it. Standing both backends up against a live project and a live account is #496 — parked rather than milestoned because it is gated on cloud accounts nobody has yet, which is the same gated-on-deployments shape as the data-gated items, and it is scheduled below rather than left to the disposition pass.

v0.8.0 — richer tools, wider adoption. Shipped. Tool results stopped being a string: text, image, audio and embedded resource cross as themselves, vouched by the proxy against the schema the agent parses them with and degraded to the placeholder — never to base64 inlined in text — wherever a provider or a protocol revision cannot take them (#160). A channel got a persona, appended to the standing region and never substituted for it (#270). And OAuth upstreams got sender-constrained tokens — RFC 9449 DPoP, with the signing key in a third store on the custody seam, so theft of the token store plus the master key yields credentials a thief cannot present (#260). Beside the three, the published CLI was found unable to start at 0.6.0 and 0.7.0 and fixed with a check that executes what is packed (#514), and the compose file stopped shipping :latest for all three images (#519).

#270 was written as a name, an icon and a persona, and landed as the persona alone; the other two were declined rather than deferred, which is the second arm of the milestone’s own clause rather than a shortfall. Its definition of done said a sheet’s identity should change what a reply “looks and sounds like”, and only sounds was delivered. The reason is that the first two are not the service’s to control. chat.update accepts no username or icon_*, and every approval card and live checklist this system paints is a chat.update — so a per-message override would apply to replies and not to cards, or would rename a live checklist halfway through a task. The @-handle is workspace-wide regardless, so a per-channel display name could never give a channel its own handle, which is the thing an operator asking for one actually wants. So chat:write.customize is not requested and the app’s name and icon stay the Slack app config’s, argued in packages/gateway/README.md where the next person to reach for that scope will meet it.

The clause about settling the chat.update question by testing it before the feature’s shape was fixed was answered by the shape no longer depending on it: with no per-message override anywhere, what an edited message displays decides nothing here. Slack’s own reference — which lists chat.update’s full argument set and does not include the three — is what closed it.

Two things landed that its wording did not name. The field is [channel] persona rather than a new [identity] block: one field does not earn a section, and [channel] description is its sibling in every respect that matters. And the agent now learns its own name from its installation — the gateway reads its display name from the same auth.test that tells it its user id, so an operator who renames the app in Slack renames the agent. That was not in the issue at all, and without it the release would have shipped a model still introducing itself as Libero under somebody else’s avatar, which is the half-delivered version of the thing #270 was for.

Two of the three landed wider than the milestone’s wording rather than narrower, and the difference is worth naming beside #270’s. #260’s definition of done said the exchange should require “a key the store does not hold”, which named no store; where that key lives turned out to be the whole of whether the sentence means anything, so it went into a third store on the custody seam — not the token store, which would make the claim vacuous, and not the vault, whose design is that the serving process cannot write to it. And #160’s clause about restating the result bound for multi-part results was answered by one bound over the whole result rather than a second byte-denominated one beside it, with result_bytes changing what it measures rather than gaining a sibling.

The release also carries a fix to the two before it. @getlibero/cli 0.6.0 and 0.7.0 could not start from npm at all — two writers of one filename, published in the order that lost — and the gates that should have caught it were both blind: the tarball check ran before the step that clobbered the file, and its one grep could never fail. That is a self-hoster’s report against the published artifacts rather than anything this repository’s own suite found, which is the shape of bug wider adoption is supposed to surface, arriving in the release named for it.

v0.9 — close-out. The data-gated items, now buildable against pilot data (#283, and #284 if the numbers say so); the managed custody backends proven against a live GCP project and AWS account (#496), buildable once a pilot has the accounts — SigV4 being the one claim no emulator checks; the pass over every hard-coded limit (#465), whose own text parks it for this close-out and which cannot slip past it, because loosening a cap later is a release while tightening one breaks sheets that already parse, and 1.0 hardens every one of those one-way doors; libero skill vendor (#439); the native adapters pilot demand actually named, from #56#58; and a disposition pass over whatever remains, so each surviving parked issue carries an explicit post-1.0 reason. “Done” here means no open issue is undecided, not that all of them were built.

Planned 2026-09-06, and narrower than the paragraph above. The milestone was opened against what the pilot window had actually produced, which was none of the three inputs the lane assumed. No cloud accounts exist, so #496 stays parked with the deploy/README.md warnings standing and the 1.0 validation release inheriting them as a stated limit. The one live deployment is a test one, so #283 and #284 stay parked too — a cutoff tuned against a test corpus is a guess with a number on it, which is exactly what #283 refused to be, and the collection path #427 built means the validation pilots produce the distribution with nothing further to build. And no pilot named an adapter, so #56–58 stay parked under #211. What the milestone builds is the two things that gate on nothing external: the limits pass, with #465 promoted to its tracker (#538 the inventory and per-limit decision, #539 the deployment-level surface) and beside it the compatibility surface 1.0 freezes, stated on one page (#540), so that “1.0 hardens the one-way doors” is a list rather than a sentence; and libero skill vendor (#439), with the Agent Skills SKILL.md convention as the format it normalizes from. The disposition pass is an issue of its own (#541), blocked on the rest so it runs last, and every issue it leaves open carries a post-1.0 label beside parked — the label is what a filter finds, the comment is what a reader finds.

Some things stay demand-driven, decided rather than drifted: Windows support for the CLI (#249), the adapters no pilot asked for, and event-driven ambient — MCP subscriptions (#155), which is now a post-1.0 workstream on its own. It was paired here with tool access for fired checks on the argument that a subscription wake that can look nothing up is thin; #348 having shipped, that half is answered and what remains is the subscription itself.

Not stars: unaffiliated teams self-hosting the governed core in real workspaces, and the e2e security suite passing against every release.