Repository navigation
v3.6.0
Breaking
- Secrets file: public runtime helpers and the BEAM start scripts read a secrets file only when
UNITARES_SECRETS_ENVnames it, as an absolute path to a readable file.~/.config/unitares/secrets.envand the legacy~/.config/cirwel/secrets.envare no longer found by default. Credentials supplied directly through the service environment still work. Upgrade note: a service that relied on the default location starts without its credentials (the lease plane, for example, answers 503) until its job setsUNITARES_SECRETS_ENV. Set it before updating. A job that launches through a secrets wrapper must also name the wrapper inUNITARES_SECRETS_WRAPPER;sentinel-plist.py renderrefuses a wrapped Sentinel plist that does not, rather than writing the bearer into it. Parity tests cover explicit selection and the refusal to discover unselected files, and fleet-specific bridge deployment tooling now lives outside the public repository (#2925). - http route packs: the built-in
reference-residentsandautomation-censuspack names are gone. A pack now mounts only whenUNITARES_ROUTE_PACKSselects it and the deployment-owned JSON manifest named byUNITARES_ROUTE_PACKS_FILEdeclares its paths, handlers and methods (docs/operations/resident-roster.md, "Route packs"). Upgrade note: a deployment that setUNITARES_ROUTE_PACKS=reference-residentsor=automation-censusmounts nothing after this change, and only a log line reports it. Write a manifest for the routes it uses before updating.docs/operations/automation-census-setup.mdhas the/api/automationsentry. Extensions mount after every core route, and a pack whose path and method match a core route, or whose path falls under a core mount such as/mcp, is rejected whole and logged (#2925). - exposed deployments: a server configured to listen beyond loopback now refuses to start when an operator credential or continuity secret still has a published default value. Set deployment-owned secrets before updating; loopback-only development retains a warning instead of a refusal (#2957).
- Interface contract 2.0.0 removes implicit vendor selection for omitted
delegate_inference.host_idand thoroughconsultcalls. Select a host
explicitly, configurejobs.consult.thorough.preferthrough
UNITARES_INFERENCE_HOSTS_FILE, or provide one unambiguous eligible host.
Ambiguous/unconfigured requests return actionable refusals; consultation
retains caller-permitted local degradation. Explicit pins and councils keep
their existing authorization and privacy rules. Updated SDKs omit an absent
host instead of sending the old default. The document schema remains v1;
delegate_inference’s input digest and the aggregate surface digest change (#2925). get_workspace_healthandadmin(action='workspace_health')now report
installation diagnostics without desktop client discovery or assumed
checkout documents. Default client mappings and server lists are empty;
configured counts are zero and do not measure liveness. Unperformed
security, executable-permission, live-server and documentation checks return
null, withnot_assessedornot_requestedstatus where applicable.
Clients must accept these unknown/unrequested states instead of treating them
as failure or success. Explicit client and workspace-file checks remain
Python embedding hooks, not MCP file access; usehealth_checkfor running
server subsystem health. Tool inputs are unchanged for workspace diagnostics (#2925).
Added
- The launch profiles generator writes
dialectic.reviewer: the dialectic reviewer takes onlyDIALECTIC_SESSION_IDand reads its thesis and evidence from the session, with the server's non-secret reviewer settings, its host list as the server resolves it (inference hosts file included; regenerate after editing that file), the CLIs found and the server's lifetime cap fixed in the profile. The server's reviewer host selection, lifetime cap and governance URL now live insrc/dialectic_reviewer_config.py, shared by the server and the generator; the server's behaviour is unchanged. The generator takes server settings from--env-fileor the new--launchd-plist, keeping only the names it uses and binding credentials (the MCP bearer, the external host's key) by name only;AGENT_ORCHESTRATOR_PROFILES_SERVER_PLISTpasses the plist from the orchestrator'sstart.sh. No caller sends profile requests yet (inference-hosts-config-v0 step 4b) (#3099). dialectic(action='get')now includespaused_agent_state, the pause evidence the server captured when the session opened, on its fast read as well (its other read path already did). WithDIALECTIC_REVIEWER_FROM_SESSION=1, the orchestrated dialectic reviewer takes onlyDIALECTIC_SESSION_IDfrom its spawn and reads the thesis, the evidence, the situation and the paused agent from that record, exiting 4 without reviewing when the record does not support a review. Nothing sets the switch yet; it is for the reviewer's launch profile (inference-hosts-config-v0 step 4b) (#3096).- install: an opt-in core profile,
docker-compose.core.yml, starts only the server and PostgreSQL (Redis and the lease plane off):docker compose -f docker-compose.yml -f docker-compose.core.yml up -d --wait, orCOMPOSE_FILE=docker-compose.yml:docker-compose.core.ymlin.envso./scripts/unitares updatekeeps it. Check-ins, identity resumption by a keyedclient_session_id, the knowledge graph and dialectic requests work; state Redis would hold falls back to process memory and is lost on restart, onboard pins do not work (clients passclient_session_id), and surface leases and agent messages are unavailable (install manual §2.4). The default stack is unchanged. The Minimal Core Matrix measures this file itself rather than a CI-only copy, and checks that the update script's stop of the lease plane by name does not bring it back (#3032). - dashboard: a new core Timeline tab draws one row per agent across the last 1, 6 or 24 hours from
GET /v1/timeline. Marks: check-ins (shape by policy action; hook-recorded ones hollow, with a toggle to hide them), graded outcomes (✓/✕), knowledge writes, declared lineage (the child's row sits under its parent, labelled subagent, successor or continuation from itsspawn_reason, assrc/identity/lineage_semantics.pyclassifies it), lifecycle changes, and bands for pauses and dialectic reviews. Selecting a mark shows its record in UTC: for a check-in, its report excerpt when the server returns one (otherwise it says the text was not retained); for an outcome, the confidence it grades against the result. Rows are ordered by most recent event, capped at 25 until "show all", with a filter on label or id; kinds the server truncated are named. The 1h and 6h windows refresh on the live tick; the 24h window loads on demand (#3047). - Public surface inventory now includes the Homebrew preview and timestamped Markdown/JSON reports, with source and refresh paths, explicit unknown states for delegated checks, and opt-in deployment observations. The existing weekly Positioning Surfaces workflow retains the report and acceptance evidence (#3065).
- Add
unitares setup --homebrew: inspect native configuration and database readiness, or use--applyto create missing private configuration with a shared lease bearer and optional models disabled. Existing configuration and databases are preserved; an absent migration registry is never treated as proof that a database is empty (#3048). - install: the packaged Apple Silicon Homebrew CLI now gives native installs
the coordinatedunitares updatelifecycle already expected from the Docker
path: a read-only cached-tap--check, explicit package refresh, both writers
stopped and verified, a private custom-format backup verified bypg_restore,
four-formula pin management, full installed-formula inventory evidence around
Homebrew dependency resolution, re-execution into the new keg, migrations,
previous running/stopped-state restoration, identity-count verification,
deep-health verification for restarted writers (schema verification when
stopped), and a private journal resumed with--resume. Unfinished journals
blockunitares start; a safe pre-change--abortand a reviewed
identity-count-mismatch acknowledgement provide explicit recovery paths.
Verified backups are checksum-indexed, and--cleanup-backupsprovides a
dry-run-by-default, journaled retention path that counts only existing,
reverified dumps, preserves the newest set and every update-journal reference,
and explicitly reconciles stale missing-file records. UNITARES never deletes
Homebrew kegs.
Directbrew servicescommands bypass the journal guard. Formula hooks still
never migrate a database. Homebrew is recommended for supported Apple Silicon
desktop Macs; Docker remains the cross-platform/server/CI path. Stable
releases now have a protected deterministic archive-to-draft-tap-PR handoff;
bottle build and publication remain separate operator-gated actions (#3086). - The launch profiles generator also writes
host.codexandhost.antigravity, so every host adapter lane has a profile. Forhost.codex, the Codex app-server client now runs thecodex execfallback itself (HA_CODEX_EXEC_FALLBACK=1) instead of the raw adapter's shell template, with the prompt on standard input and the lane's administrator-configuration and sandbox-probe checks repeated before exec. The raw adapter path is unchanged, and no caller sends profile requests yet (inference-hosts-config-v0 step 4b) (#3092). - The agent orchestrator can spawn named launch profiles (
AGENT_ORCHESTRATOR_PROFILES_FILE): a request names a profile and fills only its declared variables, the child environment is built from the profile (non-inherited variables unset, secrets bound to the profile and redacted from output), and per-profile concurrency and hourly limits apply.AGENT_ORCHESTRATOR_RAW_SPAWNdefaults todenywhenever a profiles file is configured. Without a profiles file nothing changes (inference-hosts-config-v0 step 4a) (#3090). scripts/ops/orchestrator-profiles.pywrites the agent orchestrator's launch profiles file, and the orchestrator'sscripts/start.shruns it before boot whenAGENT_ORCHESTRATOR_PROFILES_GENERATE=1hands it the fileAGENT_ORCHESTRATOR_PROFILES_FILEnames. Its first profile,host.claude, runs the newclaude_cli_client.py: the prompt goes toclaude -pon standard input, the flags are fixed, the caller chooses only the model, and the CLI gets an allowlisted environment in an empty directory. No caller sends profile requests yet, so nothing changes (inference-hosts-config-v0 step 4b, first part) (#3091).- Public site: a read-only demo page,
demo.html, walks through one incident (a claim, the evidence against it, a review challenge, a correction, and a successor's reconstruction) with nothing to install. Its quoted facts, record ids and link names are filled at build time from the accountability-journey test fixture; the build fails on an unfilled placeholder or a fixture link the page never names, and the site redeploys when the fixture changes. The page says plainly that the same rehearsal tied with git plus a structured handoff. Linked from the landing hero and the site nav (#3000). - dashboard / REST: new read-only
GET /v1/timelinereturns every agent's events in one time window, merged and sorted oldest first, with a lookup of the agents they name: check-ins (markedauthored_byagentorsubstrate), graded outcomes (with thereported_confidencethey grade), pauses, resumes and archives, knowledge writes, declared lineage, and dialectic sessions.hours(default 6, up to 168),agent_id,kinds,limitper kind (default 500, up to 2000;truncatednames each kind that reached it),include_substrate(default on) andinclude_trajectory(the server's owntrajectory_validatedre-scores, default off). An unknown parameter or kind is a 400, not a wider read. It is the data source for the core dashboard Timeline tab (#3045). - timeline:
GET /v1/timelinereturns each agent'stags. The live Timeline pane uses them to fold agents taggedautonomousinto a collapsed background group. Each row now shows what its agent last said, a "needs your attention" box names agents paused with no later resume or whose latest check-in drew a pause verdict, and one light replaces the status lines (#3078). - live portal Timeline: the BEAM
dialectic_liveportal now has a live Timeline pane backed by the governance WebSocket, with bounded retained state, reconnect/snapshot recovery, attention summaries, and lane-based rendering for agents and their events (#3061, #3076).
Changed
- check-ins: each
sync_statecheck-in now keeps a bounded excerpt of its report text in its state row (state_json.report_excerpt), so the record keeps what the agent said beside the state it was given. Through v3.5.0 the report text was not kept at all, apart from the 500-character summary an auto-recorded outcome stores when the text matches its completion or failure pattern. The excerpt is the agent's own words, not a verified claim: durable claims still belong in a finding. It is at most 500 characters by default (UNITARES_CHECKIN_EXCERPT_CHARS, capped at 2000;0turns it off), whitespace is collapsed, and a longer text is cut at a word boundary with…, so an identifier is not split mid-token (a single word longer than the limit is cut at the limit). Common credential shapes are redacted before storage: provider tokens,Bearervalues,name=valuepairs whose name looks secret (at any value length; after a barename:only values of 8 or more characters that carry a digit or run to 20), URL passwords and private-key blocks. This is best effort, not a guarantee. The server instructions, product definition, capabilities page, reviewer guide and architecture doc now say this instead of "not retained". Operator decision, 2026-10-23 (#3049). - release: the Promote Release
pinjob now regeneratesCOMPATIBILITY.md's plugin and SDK rows and the public site's SDK pins withscripts/ci/compat_component_rows.py, as it already did the published-server row. The SDK is the newestsdk-v*tag in the release, and the job fails if PyPI does not serve it. The plugin is the release entry'splugin-bundle-recutversion, and the job fails if that plugin release does not exist. A test now requires a Compatibility policy bullet forVERSION. These were hand edits, and v3.5.0 shipped with all of them still naming plugin v0.4.21 and SDK 0.5.0 (#3044). - Drift doctor: the read-only deploy preflights (#3020) run only for services listed in the JSON file named by
UNITARES_DEPLOY_PREFLIGHTS, like the drift surfaces inUNITARES_DEPLOY_DRIFT_SURFACES. No built-in service roster ships.governance_bearer.pyrecognizes a secrets wrapper only through the job'sUNITARES_SECRETS_WRAPPER, and reads only the file itsUNITARES_SECRETS_ENVnames (#2925). - Interface contract 1.56.1: Automatic local model selection requires an explicitly configured endpoint; fresh installs stay model-free (#3043).
- Interface contract 2.1.0: Knowledge details adds bounded response-chain depth, limit, and offset controls with deterministic pagination (#3058).
- Interface contract 2.3.0: dialectic reads gain an opt-in compact projection, and duplicate-session conflicts return executable recovery guidance without exposing PostgreSQL message bodies (#3094).
- Interface contract 2.2.0:
onboardaccepts an optionalhost_sessiondeclaration ({harness, session_id, workspace?}), stored once on a fresh identity;GET /v1/timelinereports each agent'shost_sessionas{harness, session_id, resumable}without the workspace path (portal hub H0) (#3089). - The Docker quickstart runs the AGE knowledge backend.
docker-compose.ymland.env.examplenow setUNITARES_KNOWLEDGE_BACKEND=age, so a new install stores the graph over its findings (AUTHORED,RELATED_TO,TAGGED,RESPONDS_TO,SUPERSEDES) and search results carry asuperseded_bypointer. An existing quickstart install that ranpostgresrebuilds the graph from its rows on the first knowledge-graph access after the switch, including the related links thepostgresbackend kept only in each finding'srelated_tolist; setUNITARES_KNOWLEDGE_BACKEND=postgresin.envto keep the old behaviour. A native install with the variable unset still resolves topostgres. Newdocker-compose.full.ymladds semantic search (bge-m3, CPU torch) and hybrid ranking in a locally built image; the reranker and related-finding expansion stay off. The quickstart CI job now proves that stored findings get their graph edges (#2961). - release tooling: simplify server releases with
scripts/ops/prepare_release.py(make release-prep PART=patch|minor|major),--after-mergeoperator/agent command cards, and--check-ghcranonymous GHCR visibility verification; anchorchangelog_coverage.pyandrelease_series_drift.pyat the release commit with nearest-ancestor tag discovery (git describe --tags --abbrev=0) so post-release commits onmasterbefore tagging do not fail Release Seams; runrelease-seams.ymlonrelease/**branches; include next-command remediation hints inserver_release_sync.pyandsdk_release_sync.py; and rewritedocs/operations/RELEASE_PROCESS.mdas an actor-grouped checklist with release cadence guidance (#3080). server.jsondescription: the MCP registry entry now reads "Accountability infrastructure for long-running AI agents. Shared memory is a provenance graph." It still opens with the product sentence verbatim; the second sentence describes what the AGE backend, now the quickstart default, stores: who wrote each finding, how findings relate, and what replaced what (#2988).- dashboard / REST:
GET /v1/timelinecheck-in events now carrystate_idand the storedreport_excerpt(nullwhere the row has none; agent-authored rows keep one since check-ins began storing it). A row that recorded the check-in's self-report also carriesconfidence_reported, the confidence the agent supplied (nullwhen it supplied none); rows without that record omit the field. Read-only (#3060). - operations: credential resolution, secret-rotation preflights, Sentinel plist rendering, and scheduled deployment checks now share one explicit governance-bearer resolver and fail with the same actionable diagnostics (#3026).
- provenance and review routing: tool responses expose the recorded caller model provenance, relayed agent PRs receive model-family labels, and automated review skips the PR author's model family without requiring an extra operator cycle grant (#3041, #3042, #3052, #3072, #3073, #3084).
- runtime dependencies: the BEAM applications move to the current compatible Hex dependency set, including the Phoenix LiveView runtime pin used by
dialectic_live(#3013).
Fixed
- Installation:
start,stop, andlogssupport configured macOS Homebrew services, selected automatically by the packaged CLI or explicitly with--homebrewfrom source. Startup verifies port ownership and the installed schema before starting writers, reuses existing services, and checks deep health. Stop preserves shared PostgreSQL/Redis services. Native package operations cannot accidentally fall through to Docker update/model commands. Package publication and first-time database/configuration setup remain separate (#3037). - Installation: the managed Docker launcher reports native/Homebrew installation metadata and requires an explicit
--separatechoice with alternate, persisted host ports before creating a Docker stack alongside configured native services. CLI-only packages and reuse of an existing validated managed stack remain supported. Foreign or unlabelled Docker containers are refused before cloning.unitares statusidentifies the endpoint whose health/version it reports, with URL credentials omitted. Installation-choice guidance distinguishes packaging from optional capabilities and records the remaining Homebrew publication/lifecycle gates (#3030). - Stored findings link to similar ones again. The parameter middleware hands the store handler
auto_link_related=Nonewhen a caller leaves the flag out, and the handler read that None as "off", so validated stores skipped linking: in the week of 2026-10-19, 2 of 269 new findings on the reference deployment got related links. An unsent or null flag now means on, on single and batch stores. An explicit false still turns linking off; on theknowledgerouter the schema reads any string other thantrue,1oryes(case-insensitive) as false, and a batch item's string is read as false forfalse,0orno(#3035). - The lease plane warns at boot when its bearer token is the published compose default. It names
LEASE_PLANE_BEARER_TOKEN, never the value, and adds a second warning when the listener binds beyond loopback outside a container, whetherUNITARES_LEASE_PLANE_HTTP_IPorconfig :lease_plane, http_ip:set the address. It does not refuse: inside the compose container it binds every interface by design, so the bind address cannot tell an exposed install from the quickstart. The digest is pinned todocker-compose.ymland to the governance server's copy by tests (#2983). - Native installation now has real PostgreSQL acceptance coverage for fresh bootstrap, interruption after migration 62, migration recovery, checksum provenance and server readiness. The required CI test check and release promotion both depend on acceptance passing against the candidate archive (#3034).
- release tooling:
scripts/dev/changelog_assemble.pynow appends the introducing PR number (#NNNN) from first-parent git history when foldingdocs/changelog.d/fragments;scripts/ci/changelog_coverage.pyclassifieschore(deps)/chore(deps-dev)alongsidebuild(deps)and includes Elixirmix.exs/mix.lockand re-stampedskills/.attestations/*.jsonin its inert-bump filter;scripts/ci/release_series_drift.pyexcludesskills/.attestations/(matching thescripts/dev/skills_manifest.pybundle fingerprint) so attestation-only commits do not require aunitares-governance-pluginrelease recut; andscripts/ops/prepare_release.pysnapshots and restores symlinks (including dangling tracked symlinks and symlinked fragments) on--dry-runor rollback (#3088). - merge queue: preserve a signed agent PR's exact self-reported model as a
[model:…]token in the landed squash subject and carry its provenance signature into the squash body, refreshing an armed subject after title or signature edits so attribution survives branch deletion without replacing GitHub's commit body or co-author trailers (#3067, #3074). - dialectic_live & install docs: the live Timeline pane now follows
TimelineFeed's claimed mark-to-row pairing when a re-read confirms rapid check-ins, preservesresumed_atpause closure across stale/same-snapshot reloads when the provisional cap evicted a resume, and closes a held pause at the retained resume before holding a subsequent pause (#3077); the rootREADME.mdanddocs/manual/02-install.mdnow explain the one-stack-per-machinedocker compose downrequirement (stopped containers still hold fixedcontainer_namevalues, andrestart: unless-stoppedrevives un-stopped containers across daemon restarts), point toscripts/install/launch.shfor ownership checks, and note the harmless shallow-tag clone notice (#3031) (#3079). - identity recovery: refusal text no longer suggests that a continuity token alone can recover an identity; recovery requires the bound session proof expected by the active transport (#2996).
- leases and queue automation: a crashed lease-plane request releases any uncommitted pre-admission file-write lease, and the PR babysitter no longer marks the same merge retry on every poll when GitHub never started the rerun (#3039, #3040).
Security
- Session binding accepts ownership only from the fail-closed transport-source allowlist, rather than trusting an unexpected source label (#3038).
- OAuth enrollment is closed by default outside its bounded flow, provider state is authenticated and expiry-bound, and callback persistence no longer accepts replayed or cross-provider state (#3056).
- Authenticated HTTP writes are bound to the caller's session and request/stream bodies have explicit size limits; the dashboard no longer places operator credentials in URLs (#3057, #3069).
- Lease-plane mutations require principal-bound proofs, including dialectic and effect-custody paths; compatibility fallbacks cannot turn an unproven mutation into an authorized one (#3059).
Documentation
- Aligned
docs/CAPABILITIES_AND_DEPLOYMENT.md,docs/REVIEWER_GUIDE.md, anddocs/manual/03-running-the-server.mdwith the Compose default (UNITARES_KNOWLEDGE_BACKEND=agefor replacement-link edges, full-text search untildocker-compose.full.ymladds embeddings), clarified identity vs. transport auth indocs/manual/06-operating.md, refresheddocs/ROADMAP.mdrecently-shipped items, and surfaced./scripts/unitares update, per-file edit leases, and hosted MCP connectors inREADME.mdanddocs/manual/04-integrating-agents.md(#3098). - docs:
COMPATIBILITY.mdnow names the plugin and SDK that shipped with v3.5.0 (plugin v0.4.22,unitares-sdk0.6.0; the rows still named v0.4.21 and 0.5.0) and gains a v3.5.0 compatibility entry: no callable removed, no migration, theUNITARES_MODEL_IDdefault removed, unproven knowledge writers refused (GHSA-4m7p-grgw-vgmh), the related-expansion flag renamed, and the two search changes without a setting. The public site's SDK link and install command move to 0.6.0 too. The README's install sentence and the docs index point at the core profile and the installation choice (#3044). - Clarified
README.mdinstall guidance by splitting post-install endpoint and single-stack rules into scannable bullets and distinguishing client-side agent/model compatibility (any MCP/HTTP client,unitares-governance-pluginfor Claude Code and Codex,unitares-host-adapter,npx mcp-remote, Python SDK, resident runtime) from optional server-side models (./scripts/unitares modelfor Ollama, vLLM, LM Studio, and llama.cpp, plus the agent orchestrator for cross-family CLI review) (#3093). - Product and installation docs now lead with UNITARES' five operational questions and a concrete supersession example, distinguish interrupted bootstrap recovery from a fresh install, consolidate the Compose security reference and mechanism descriptions, trim duplicated workflow/install prose, and index previously orphaned analysis and operations records (#2997, #3046, #3062, #3064, #3070, #3071).
Compatibility
INTERFACE_CONTRACT_VERSION:2.3.0unitares-sdk:0.6.1
Migrations
None.
Rollback
- Return to
v3.5.0(docker composeimage tags and git checkout atv3.5.0).
Model: gpt-daybreak-blue-latest
UNITARES agent: f5306a3f-4f2a-4a10-9a5f-3df3d3708ce3