🌳 v1.0 is here. loomcycle 1.0 is released — the feature set is complete and the runtime is hardened and distribution-ready. The core primitives stabilised across the v0.8 → v0.23 line (multi-replica HA; the substrate Defs Agent/Skill/MCPServer/Schedule/Webhook/MemoryBackend/A2A; A2A interoperability; inbound webhooks; pluggable memory + a memory layer; the synthetic code-js provider; and OSS multi-tenant authorization in v0.17.0 — per-principal bearer tokens + a role-aware Web UI). The v0.24 → v0.37 line was a hardening + operability run: the interactive terminal, pause/resume/snapshot + cross-instance resume, the context-compaction subsystem, context-transform plugins, external fan-out on every transport, and slow-local-model robustness. 1.0 itself is a pure hardening + distribution milestone — no new primitives — wired to Homebrew, multi-arch Docker, and the Claude Code plugin. 8-hour stability soak: 1.27M circuits, 3.8M agent runs, 100% completion across 468 waves, zero leaks. Apache-2.0. We welcome bug reports, security disclosures, feature contributions, downstream consumers, and forks. See CONTRIBUTING.md.


What it is

The agentic runtime, in a sidecar. loomcycle is one Go binary, ~50 MB. It runs alongside your application, not inside it. Your app calls loomcycle over HTTP, gRPC, MCP, the TypeScript adapter, or the Python adapter. The agent loop, multi-provider routing, memory and channel primitives, MCP server identity, OpenTelemetry traces, and multi-replica coordination all live in the binary. Your application stays in whatever language you wrote it in.

The shape that's different. Today's agentic-systems market gives you three options. One: embed a Python or TypeScript library inside your application process. Two: rent a managed cloud service tied to one vendor's IAM. Three: proxy your model calls through a gateway that doesn't actually run agents.

loomcycle is a fourth option. A lightweight self-hostable runtime that owns the loop and speaks every wire format your stack already uses.

What's shipped

Release Highlights
v0.4 → v0.26.x foundation Everything the runtime is built on, condensed. Seven inference modes: Anthropic, OpenAI, DeepSeek, Gemini, Ollama (cloud + local), plus the synthetic code-js provider and a mock provider. The hardened model → tool_use → tool_result loop. 19 built-in tools with Claude Code parity (Read, Write, Edit, Grep, Glob, NotebookEdit), plus HTTP, WebFetch, WebSearch, Bash, Agent, Skill, Memory, Channel, AgentDef, SkillDef, Evaluation, Interruption, Context. The content-addressed, runtime-mutable substrate (Agent / Skill / MCPServer / Schedule / Webhook / MemoryBackend / A2A defs). Vector Memory (sqlite-vec / pgvector) on a pluggable MemoryBackend, with a memory layer above. MCP on both sides. The LLM Gateway + OpenAI-compatible shims. A2A interop. Input webhooks. Ensemble-sync primitives (RFC S). OTEL + per-tenant fairness + Pause / Resume / Snapshot + multi-replica HA. Per-run named credentials + tool-use hooks. OSS multi-tenant authorization (RFC L, v0.17.0) across both the state and definition planes. The embedded React Web UI + the interactive terminal. TS + Python + n8n adapters. Homebrew + Docker distribution. Per-version detail: REVISIONS.md.
v0.27.0 Interactive runs survive leaving the terminal. Background-goroutine execution under context.WithoutCancel, re-attach via GET /v1/runs/{run_id}/stream (replay-from-?from_seq + live-tail). Context op=self reports the resolved provider + model.
v0.28.0 Per-agent LLM sampling (temperature / top_p / top_k / penalties / seed / stop, set via yaml or AgentDef overlay or per-run). And pause cooperative quiesce: the loop parks at an iteration boundary and Pause() waits for in-flight runs, so a mid-run snapshot is reliable.
v0.29.0 Web UI + operability. Agent-editor sampling controls + a collapsible advanced JSON/YAML overlay, terminal message-echo + a context-size gauge, and soft reclaim of a retired agent name (no new runtime primitives).
v0.30.0 Cross-instance resume of a snapshotted mid-run (RFC X Phase 2). A paused run is re-dispatched by reconstructing its loop from the transcript, fired after a snapshot restore and at boot (crash recovery, cluster-gated).
v0.31.0 Park + resume a fan-out parent blocked in Agent.parallel_spawn (RFC X Phase 3). Pause-watcher + a no-schema-change spawn ledger, gated behind LOOMCYCLE_RESUME_FANOUT.
v0.32.0 Context-compaction subsystem. Replace older turns with a summary + keep-last-N verbatim (clean user-turn boundary, non-destructive). Manual / auto / self triggers and a per-agent compaction block that flows down the spawn tree.
v0.33.0 External fan-out and the run-mutation surface on every transport. POST /v1/runs:batch, a spawn_runs MCP tool (≤32 server-concurrent), a SpawnRunBatch RPC. compact_run / CompactRun. Per-run sampling + compaction on MCP/gRPC. @loomcycle/client 0.33.0.
v0.34.0 Context-transform plugins (RFC Z Phase 1a, with the redact outbound-secret-scrub plugin), an exp7 self-review hardening pass, and a cross-provider thinking-model fallback downgrade (reasonerchat).
v0.34.1 Hardening + branding (no new features). A central tenant-scoping store accessor closing three live cross-tenant read gaps (security review S2), plus the new loomcycle brand logo and favicon in the Web UI.
v0.34.2 Web UI design system + theming. A tokenized --lc-* design system (spacing / type / radius / shadow / fonts + semantic colors), light + dark themes (OS default + a persistent topbar toggle), bundled brand fonts (Outfit / Inter / JetBrains Mono), and the loom-wood #56c596 accent. Plus a loop fix (interactive runs unbounded by default; no more stop-at-16) and a -race test de-flake.
v0.34.3 Patch. Context op=self reports a fresh context footprint right after a compaction. It had kept showing the stale pre-compaction size (e.g. 164k / 82%) for one turn even though the wire request had already shrunk. The loop now refreshes lastCtxTokens at every compaction site.
v0.34.4 Patch. Interactive-terminal UX + local-model fixes: a collapsible interactive-sessions switcher on the run page (and interactive tags on the runs page), a flashing waiting indicator in the terminal, the Ollama context gauge backed by num_ctx, generous ollama-local timeouts (300s), relative sandbox paths anchored to the root (not the process cwd), and the static agent base re-surfaced in the Library when no dynamic version is active.
v0.34.5 Patch. Ollama now reports the model's actual loaded context window (read from /api/ps at the stream's done frame) instead of only a pinned num_ctx — so the gauge is truthful for local models loaded at the server's OLLAMA_CONTEXT_LENGTH; plus docs (the architecture diagram + ARCHITECTURE.md show the context-transform plugin layer; a README v1.0 voice refresh).
v0.35.0 Model aliases in tier candidates. A models: alias (e.g. local-gemma) now resolves anywhere a tier candidate is accepted — per-agent models:, user_tiers, and the library tiers: — not only in an agent pin, closing the "pin expands aliases but tier candidates don't" 503. A candidate can be written as a bare alias string (- local-gemma); config-load validation and the Web UI Library editor know aliases too.
v0.36.0 Jailed agents can see their sandbox. The Read/Write/Edit/Bash path docs now instruct relative paths (they wrongly said "Absolute file path," so agents passed host paths that resolve outside the jail), and Context op=self reports the sandbox roots (read_root/write_root/bash_cwd) + the effective host allowlist. Plus a collapsible left column on the run page so the live terminal can use the full width.
v0.37.0 Slow-local-model robustness. Two loop fixes that keep an interactive run on a slow local model alive for hours: a run-lifetime heartbeat (a long prefill / retry no longer gets reaped as a crashed run) and a compaction window cap (auto-compaction always fits the model's window instead of "succeeding" yet still overflowing). Plus an aliases-first loomcycle.example.yaml, a "Local models (Ollama)" config guide + a loomcycle.local-interactive.example.yaml. Validated by a 133-min standalone local run.
v1.0.0 🌳 1.0 — feature-complete, hardened, distribution-ready. The milestone the roadmap pointed at: no new primitives, the culmination of the v0.8→v0.37 primitive + hardening line. Apache-2.0, wired to Homebrew / multi-arch Docker / the Claude Code plugin. Same codebase as v0.37.0, tagged as the stable 1.0.
v1.0.1 🔐 substrate:tenant — the tenant-operator scope (RFC AF). A security patch: a new closed-catalog scope giving a tenant FULL power WITHIN its own tenant — authoring all 8 substrate Def families (incl. the _mcpserverdef dynamic-MCP-ingestion surface) + registering tool-use hooks — WITHOUT substrate:admin (the cross-tenant superuser), so two tenants never both need admin. The route gate (HTTP + gRPC) moves the def + hook plane to substrate:tenant; minting (_operatortokendef), the MCP-server transport (/v1/_mcp), and runtime admin STAY admin-only. Includes a hooks tenant-isolation pass (the registry was global: a hook fired on every tenant — now Tenant-stamped, tenant-filtered Match, scoped List/Delete; migration 0047).
v1.0.2 🌐 Permitted host-widen grants work with no static allowlist. A patch: when the operator runs no static LOOMCYCLE_HTTP_HOST_ALLOWLIST and relies on a permitted Pre-hook (hooks.permit_host_widen.owners) to grant hosts per call, the grant now takes effect. The HTTP/WebFetch tool used to short-circuit on an empty allowlist before consulting the per-call grant, so permit_host_widen was dead without a static floor; it now refuses only when the floor is empty and there's no permitted grant (the per-host check + dial-time SSRF guard still apply). Surfaced by the JobEmber VPS deploy.
v1.1.0 📁 Filesystem Volumes — per-agent ro/rw filesystem scopes (RFC AH, Phases 1→5). ⚠️ BREAKING: the legacy single jail (LOOMCYCLE_READ_ROOT/WRITE_ROOT/BASH_CWD) is retired — an agent gets filesystem access only via a volumes: binding (sandbox-by-default, mirroring "no allowed_hosts → no egress"); a default rw volume restores the old single-jail behaviour, and a deploy still setting the retired env vars now fails at config-load with a migration hint. Adds named, per-agent ro/rw Volume roots for Read/Write/Edit/Glob/Grep/Bash (the resolveInsideRoot confinement reused unchanged; spawn narrow-only child⊆parent); a runtime-mutable, tenant-scoped VolumeDef substrate (flat create/delete/purge, persistent + run-scoped ephemeral auto-purged on run completion, fenced os.RemoveAll); a Web UI Volumes tab; and cross-transport parity (gRPC / MCP / TS @loomcycle/client / Python). Plus two exp8 finding fixes: Context op=tools now lists the Agent tool (F45), and POST /v1/runs accepts a top-level prompt string + rejects empty input with a clear 400 (F47).
v1.1.1 🗣️ Interactive agentic sessions over gRPC + TS (RFC AI). The interactive session — a run that parks at end_turn for operator steering, survives client disconnect, and is re-attachable by run_id — was wired only over HTTP+SSE and driven only by the Web UI. v1.1.1 surfaces it on the official adapters: an interactive flag + RunInput/StreamRun (gRPC) → run_input()/stream_run() (Python) and sendRunInput/streamRunByID + a high-level InteractiveSession driver (TS). Steering + re-attach are lifted onto the Connector so a gRPC steer reaches the same in-process registry an HTTP run uses; the re-attach stream now replays the operator's own turns so a cold client (another device) reconstructs the whole conversation. Additive wire surface. Adapters realign to 1.1.1: @loomcycle/[email protected] (npm, on this tag — also shipping the v0.35.0 volume surface) + loomcycle 1.1.1 (PyPI, on python-v1.1.1).
v1.2.0 🗃️ SQL Memory — a per-scope SQL database for agents (RFC AA). A third facet of the Memory tool: authorized agents run arbitrary SQL (sql_query/sql_exec) against a per-scope database the runtime hosts, isolated from the main store — relational tables/joins/aggregates without the Bash + sqlite3 process-isolation hole. Default-deny sql_scopes gate; durable per-(tenant, scope, scope_id) + ephemeral per-run scopes; statement timeouts, byte quotas, optional TTL/size GC, full audit. sqlite tier (file-per-scope, statement-allowlist hardened) + postgres tier (schema-per-scope in a SEPARATE aux DB, isolated by a per-scope least-privilege LOGIN role). Plus explicit + nested transactions (sql_begin/commit/rollback, SAVEPOINT nesting); vector columns (pgvector + server-side $embed); snapshot/backup integration (tier-tagged logical per-scope dump) + a per-scope cap; operator-defined read-only shared schemas. Additive + off-by-default — no breaking changes, no new wire RPCs (SQL is an in-band Memory-tool capability; the MCP memory tool exposes it directly). Adapters unchanged since v1.1.1.
v1.3.0 🧰 Bashbox — a TRUE in-process shell sandbox + operator host-command fallback (RFC AJ). A new opt-in tool that runs shell commands in-process via gbash (pure-Go): no OS process, every path rooted at the bound volume, no network. Unlike the Bash tool ("restricted, not isolated" — os/exec), its isolation is real, so it honors read-only volumes — a ro binding mounts under an in-RAM write overlay, so writes succeed in-run but never touch the host (the asymmetry RFC AH left open). Opt-in like Bash (LOOMCYCLE_BASHBOX_ENABLED=1 + tools:[Bashbox]); bundles pure-Go awk/jq on top of gbash's coreutils (~97% /bin/sh parity). Plus an operator host-command fallback (§13): allowlist specific host commands gbash lacks (LOOMCYCLE_BASHBOX_FALLBACK_COMMANDS=git,gh) to fall through to the real host shell — only those names escape, everything else stays sandboxed (no smuggling); rw-only, cwd-contained, with credentials via LOOMCYCLE_BASHBOX_FALLBACK_ALLOWED_ENV injected into the host child only (model-invisible). gbash is alpha + pinned; the per-agent gate is the escape hatch. Additive + off-by-default — no breaking changes, no new wire RPCs. Adapters unchanged since v1.1.1.
v1.4.0 🗂️ Path + 📄 Document — a Unix-like VFS and chunked-graph documents, on every transport (RFC AL + RFC AK). Path is a tenant-rooted, scope-aware VFS over Memory / Volumes / Documents: address resources by paths (/docs/launch) via a dirents inode/dirent table; six ops (resolve/ls/stat/mkdir/mv/rm), .. rejected, tenant-isolated; a dirent is a name, not an authority grant. Document is a chunked graph — each chunk a first-class unit (UUID, hierarchy, type, fields, edges, Markdown body); bodies in Memory, structure in SQL Memory (queryable), 13 ops with optimistic revision concurrency and atomic/bidirectional deletes; named in the Path tree. Both are now first-class off-run on all surfaces — POST /v1/_path + /v1/_document, gRPC Path/Document RPCs, MCP path/document meta-tools, and client.path()/client.document() in the TS + Python adapters — so a human or UI co-authors the same namespace + documents agents build; scope + tenant are resolved server-side from the principal, never the wire. Additive — no breaking changes. Adapters bump: @loomcycle/[email protected] + loomcycle 1.4.0 (python-v1.4.0).

| v1.5.0 | 🔑 Per-tenant MCP transport + config-declared principals + config layering (RFC AG + AO + AN). The loomcycle-as-MCP-server transport (/v1/_mcp) is now per-principal: it authenticates as the bearer's (tenant, subject) (was a fixed global operator), applyPrincipal overrides the wire identity on spawn, a per-tool gate withholds the admin-only meta-tools (minting / runtime admin / snapshots) from a non-admin session, and the route moves substrate:admin → substrate:tenant — so a substrate:tenant token drives a fully tenant-confined MCP session, and an MCP agent's user-scoped documents/memory land where the Web UI reads them. Config-declared principals: (RFC AO) declare static (tenant, subject) logins bound to env secrets, so ONE token serves both the Web UI login and an MCP thin client. Config layering (RFC AN) makes --config repeatable + deep-merged (last wins, LOOMCYCLE_CONFIG_STRICT for fatal-on-conflict), so a bundle stacks onto a local config without copy-paste. Plus a Web UI Document-viewer refinement (per-chunk markdown view + scroll). Additive — no breaking changes, no new wire RPCs; adapters unchanged since v1.4.0; the Claude Code plugin bumps to v1.5.0. | | v1.6.0 | 📦 Embedded config presets + the document-agent bundle, a Web UI Settings hub, and loomcycle-on-TrueNAS (RFC AQ + AR). The binary go:embeds provider/tier presets (base/oauth/local) and agent bundles (document-agent — the Document Assistant + its skills inline) — LOOMCYCLE_PRESETS=base,document-agent layers them as the config base, so a deployment no longer restates the provider matrix or wires a skills root (loomcycle presets / env-template introspect them; opt-in). Config layering gains inline skills:, opt-in !prepend/!append sequence-merge (one-provider-per-file presets, no restatement), and LOOMCYCLE_CONFIG_DIR (a dir of layers). A top-right Settings hub (admin-only) web-reaches the critical CLI for no-shell deployments: tenant-token generation (RFC L), a presets viewer, runtime pause/resume, health — plus topbar sign-out. And loomcycle on TrueNAS SCALE (RFC AR): deploy/truenas/ ships a validated paste compose + install walkthrough, a catalog-app source with an install wizard (its full env coverage generated by loomcycle truenas-questions), and docs/TRUENAS.md. Additive — no breaking changes, no new wire RPCs; adapters unchanged since v1.5.0. | | v1.6.1 | 🔌 Patch — the MCP thin client self-heals on upstream session expiry. A long-running loomcycle mcp --upstream session would wedge after an idle gap: the upstream reaps idle MCP sessions after a 30-min TTL, so the proxy's cached Mcp-Session-Id goes dead (404 / -32001) and — since it never refreshed — only a subprocess restart (/exit + relaunch) recovered. The proxy now caches the initialize frame and, on the session-expiry signal, transparently re-handshakes (fresh session) + retries the frame once — the client never sees it. Single-flighted, one-retry-bounded, runtime-only; binaries otherwise identical to v1.6.0. | | v1.6.2 | 💬 The chat bundle + a GPU-offload knob for local Ollama. Two embedded conversational agents for the interactive /run terminal (LOOMCYCLE_PRESETS=base,chat): chat (tier: middle, cloud-capable) and chat-local (model: local-medium, pinned ollama-local, local-only). Both grant a broad working toolset — Read/Write/Edit/Grep/Glob, WebSearch/WebFetch, Bashbox + Bash, Document, Memory, Path, Skill (+ Context) — with max_tokens left to each provider's default. New LOOMCYCLE_OLLAMA_LOCAL_NUM_GPU (sibling of NUM_CTX, global to the ollama-local driver) sets options.num_gpu to force GPU offload where Ollama's auto-detection falls back to CPU (e.g. integrated/APU boxes). Plus a Web-UI fix (the agent name sits beside the status badges in run rows) and a docs pass (Postgres ≥ 14 is a floor, not a 16 pin; the TrueNAS install keeps secrets in an external env_file). Additive — no breaking changes, no new wire RPCs; adapters unchanged since v1.5.0. | | v1.6.3 | 🔑 The tenant-operator Web UI (RFC AS Phases 1+2). A substrate:tenant operator now sees and manages its own tenant's surfaces, not just run/runs. The def-management list endpoints (/v1/_library/*, /v1/_*def/names) were tenant-blind — any token could enumerate every tenant's def names; each is now scoped to the caller's tenant (admin/legacy/open still see all + the ?tenant= focus), and the Library routes were opened to substrate:tenant (they were 403'd at the /v1/_* admin catch-all before the scoped handler could run). The left-nav swaps its binary adminOnly flag for an all/tenant/admin visibility class: library, integrations, volumes, paths, schedules, interrupts show for a tenant operator; channels + memory stay admin-only (no tenant_id column to scope on — a tenant token would 403). Plus a lockout fix: the token-mint UI no longer offers substrate:admin (minting one disables the legacy LOOMCYCLE_AUTH_TOKEN login). Byte-identical for admin/legacy/open; no new wire RPCs; adapters unchanged since v1.5.0. | | v1.6.4 | 🔑 The tenant-operator Web UI, completed (RFC AS). Extends v1.6.3 to the remaining surfaces, same posture (admin sees all + ?tenant= focus; a substrate:tenant operator confined to its own tenant): bundled/static agents are now shown to tenant operators as the shared catalog floor (#580 — they previously saw zero); the schedules surface (/v1/_schedules/*) is tenant-scoped with per-def opaque-404 (#581); Paths & Documents can be browsed by subject — the topbar user-picker's manual subject entry drives an authorized ?scope_id= / ?tenant=, so an operator (or admin) can reach a document an MCP agent created under another subject (#582 + #583); and the audit log (/v1/_events) is tenant-scoped via the event's owning session, with the nav item un-gated (#585). Plus a TrueNAS deploy-doc fix: SQL Memory's Postgres role needs CREATEROLE (#584). Byte-identical for admin/legacy/open; no new wire RPCs; adapters unchanged since v1.5.0. | | v1.6.5 | 🩹 Patch — bundled skills in the Library + documents never orphaned. Inline bundled skills (RFC AQ bundles' top-level skills: map) now appear in the Library for admin and tenant operators — they previously showed 0 for everyone because the skills handler read only the SkillsRoot set, not cfg.Skills (#587). And a document is never orphaned from the Path tree: create_document now always registers a dirent (defaulting to /documents/<title>), and a new Document op=set_path attaches/re-homes a Path-tree name for an existing document — the cure for a doc created without a path (reachable only by id, invisible in the Path/Library browser) (#588). Additive; no new wire RPCs or schema change; adapters unchanged since v1.5.0. | | v1.6.6 | 🩹 Patch — sub-agent session inherits the parent's tenant. A sub-agent spawned via Agent.spawn/parallel_spawn got its run under the parent's tenant but its session with an empty tenant_id (runSubAgent passed ""), so once transcript reads became tenant-gated the Web UI 404'd ("session not found") when a substrate:tenant operator opened a sub-agent run — while the run stayed visible and admin/open were unaffected. runSubAgent now passes parentIdentity.TenantID so the session and run share the parent's tenant (matching POST /v1/runs). Also closes a latent isolation gap. Additive; no new wire RPCs or schema change; adapters unchanged since v1.5.0. | | v1.6.7 | 🩹 Patch — Context op=self reports the agent's tenant, credential + server. An agent (especially over MCP) couldn't identify which tenant it acts as, what its credential is, or which instance it's connected to. self now also returns tenant_id, a principal block (subject / tenant / scopes / is_admin / token_def_id / token_suffix — never the bearer; present on authed paths), and a server block (listen_addr + url from a new LOOMCYCLE_PUBLIC_URL, falling back to the A2A advertise URL). The TrueNAS deploy compose + INSTALL.md surface the new knob. Additive tool output (all transports); no wire/schema change; adapters unchanged since v1.5.0. | | v1.7.0 | 🖼️ Image / vision input across every provider and transport (RFC AT). loomcycle was text-only; v1.7.0 adds one image content block (user-segment only; media_type whitelist png/jpeg/gif/webp; inline bytes, no URL form — SSRF) serialized natively by Anthropic / OpenAI / Gemini / Ollama (DeepSeek text models gated). The loop validates + capability-gates it — an image to a non-vision model errors before the call, never a silent drop. Reachable over HTTP, MCP (spawn_run schema), gRPC (media_type+data bytes — native, no base64 inflation), the TS adapter (PromptContent image variant, @loomcycle/client 1.7.0) and Python (raw-bytes dict). The run-ingest body cap is now LOOMCYCLE_MAX_REQUEST_BYTES (default 16 MiB, was 1 MiB; over-cap → 413). Additive; out of scope: image output, audio, URLs. | | v1.7.1 | 🩹 Patch — the vision gate now covers the provider-fallback target (RFC AT §4.4). The v1.7.0 gate checked only the initially-resolved provider; a vision-bearing run that failed over mid-flight to a text-only provider (e.g. DeepSeek) leaked the image_url part and got a raw 400. tryProviderFallback now re-checks SupportsVision against the re-resolved target — if the run carries an image and the target isn't vision-capable, the swap is refused (no EventProviderFallback; an EventFallbackSuppressed is emitted; the original error propagates), never leaking. Additive; no wire change; adapters unchanged since v1.7.0. | | v1.8.0 | 🧠 Ollama thinking traces via the effort hint. loomcycle couldn't enable a local reasoning model's thinking trace. Ollama's /api/chat think boolean (qwen3, deepseek-r1, gemma-thinking, …) was never sent — so the model ran on its own default and effort on an Ollama agent was logged as "dropped". buildRequestBody now maps effortthink (medium/hightrue, lowfalse, unset→omit), matching the cloud drivers; the trace returns as EventThinking. Capabilities() reports SupportsThinking/SupportsEffort=true. Behavior change: effort on a non-thinking Ollama model now errors (was a silent no-op) — consistent with Anthropic/OpenAI/Gemini. Applies to ollama + ollama-local. No wire change; adapters unchanged since v1.7.0. | | v1.8.1 | 🩹 Patch — CLI config-layering fix + Ollama think diagnostic. validate / agents list / doctor loaded a single file and ignored LOOMCYCLE_PRESETS/CONFIG_DIR/CONFIG_FILES, so an agent using a preset-defined model alias (e.g. deepseek-pro) showed a false no provider resolved — while the running server resolved it fine. They now assemble the same layered stack the server does. Plus an opt-in LOOMCYCLE_OLLAMA_DEBUG_THINK=1 that logs each Ollama request's model/effort/think flag for debugging local thinking. Server-side only; adapters unchanged since v1.7.0. | | v1.8.2 | 🩹 Patch — the loop now forwards EventThinking to clients. The loop's event switch had no case for EventThinking (and no default), so a driver's streamed reasoning trace was silently dropped at the loop — no client (SSE/gRPC/adapters) could render "thinking…" for any provider, even though the driver emitted it and (for Ollama) think:true was sent + the trace generated. Fix: case EventThinking: emit(ev) — forwarded live, still not echoed into history (the full trace stays on EventDone.Reasoning). Provider-agnostic; gRPC clients now receive it too. Server-side only; adapters unchanged since v1.7.0. | | v1.9.0 | 🧭 A routing view + a model-alias API, plus a fallback-downgrade fix. GET /v1/_routing + a Web UI "routing" page show, for each user_tier × tier, the provider/model cascade a consumer resolves to right now (top → fallbacks) — an admin sees live availability + which entry is selected (what runs now) + an active-providers header; a substrate:tenant operator sees the config cascade only (RFC AS posture). Backed by a new lock-free resolve.Resolver.Cascade that can't drift from Resolve's order. GET /v1/_models exposes the configured models: aliases (tenant-readable, non-secret) so a fork can track an operator's local override instead of pinning a concrete model. 🩹 Fix: the R2 thinking-model fallback downgrade now also drops the effort hintdeepseek-v4-prodeepseek-v4-flash still 400'd with "reasoning_content … must be passed back" because the surviving effort=high maps to reasoning_effort, which re-enables thinking mode on the hybrid V4 flash model regardless of the name; clearing it lets the sibling stay non-thinking. Server-side + Web UI; adapters unchanged since v1.7.0. | | v1.9.1 | 🔒 Security + robustness hardening patch — a whole-repo security review plus provider-driver and MCP-resilience fixes. Security: gRPC tenant/subject isolation on read + channel RPCs (#611); A2A peers authenticated via the operator-token substrate, not just legacy (#612); mem9 SSRF + API-key exfil blocked on a model-authored base_url (#613); run cancel + interrupt-resolve tenant-gated on every transport (#614); a runstate send-on-closed-channel panic fixed (#615); a Grep symlink-escape re-checked before opening walked files (#616); four secret-exposure gaps closed (#620); a per-user-cap read locked + terminal-error channel drained (#621); an opt-in DNS-rebinding guard on the MCP-HTTP client, default-allow (#622). Provider drivers: Anthropic replays the thinking block on tool-use continuations (#617); OpenAI reasoning models use max_completion_tokens (#618); Ollama surfaces in-stream error frames instead of a silent success (#619); DeepSeek thinking-model fallback downgrades to deepseek-chat, not the hybrid -flash (#624). Robustness: the webhook user_tier is pinned to the def, not the payload (#623); the ollama-local default header/idle timeout is 600s for slow local models (#625); and the loomcycle mcp --upstream thin client now self-recovers from dropped upstream connections (#626) — a bounded-backoff reconnect on any transport error, composing with the session re-handshake, a sub-server-idle IdleConnTimeout so it reopens before the server severs an idle socket, and a stall guard that carves out agent-run/LLM tools so a slow run is never retried into a double-execution. Server-side + thin-client binary; adapters unchanged since v1.7.0. | | v1.10.0 | 🔑 Tenant credentials + 📊 per-scope token-usage & cost attribution — two feature lines. RFC AR tenant credentials: a secure per-tenant/user encrypted CredentialDef store (#630, AES-256-GCM + per-tenant HKDF key from LOOMCYCLE_SECRET_KEY, AAD row-binding, fail-closed, metadata-only reads); $cred:<name> header consumption for http MCP servers so a user-authorized agent posts via each user's own Telegram/Slack channel (#631); and a provider/tool key override by env-var name — a tenant's own ANTHROPIC_API_KEY/BRAVE_API_KEY overrides the operator's host key for that tenant/user's requests, all five LLM drivers + WebSearch (#632). RFC AV usage/cost attribution: a per-call token_usage ledger tagged with which key paid (operator vs tenant/user) + a per-run cost summary (#633); GET /v1/_usage grouped report, tenant-scoped (#634); a rollup-and-prune retention sweeper + compact usage_archive (#635); a Web UI Usage page with the operator-vs-tenant split (#636); an opt-in old-run archiver (#637); and gRPC + TS + Python adapter parity (UsageReport RPC / usageReport() / usage_report(), #638). loomcycle owns a pricing: table (provider-reported cost wins). Plus RFC AU Web-UI self-service import of Claude Code skills + MCP servers into tenant substrate (#628) and a Path one-level ls implicit-dir fix (#629). A 15-finding post-merge code review hardened the line (#639/#641): per-tenant ledger identity, UTC archive bucketing, prune-by-session, redactor registration of resolved secrets. @loomcycle/client 1.10.0. | | v1.11.0 | 🎚️ Per-scope token budgets (RFC AW) — dynamically-configured soft + hard token limits per operator / tenant / user, no row = unlimited (calendar-month window, UTC). A hard limit refuses new runs at admission (HTTP 429 / gRPC ResourceExhausted) across every trigger surface (they all route through RunOnce); an in-flight run that crosses hard warns but finishes (no mid-run abort); most-restrictive scope wins. A new EventLimit warn event carries each crossing to SSE + gRPC + the TS/Python adapters + MCP spawn_run + the run transcript, rendered as a banner in the terminal (#642). Budgets are managed via a tenant-scoped GET/PUT/DELETE /v1/_limits + a Web UI Limits console, with a gRPC TokenLimit RPC and listLimits()/setLimit()/deleteLimit() on the adapters (#643). A post-merge review closed four findings (#644): a cross-tenant oracle (the operator scope's platform-wide used/limit no longer leak to a tenant's refusal/events), an O(1) ceiling-cache update so a persisted budget is never left unenforced, webhook 429 parity, and blocking-spawn_run crossing surfacing. Counters seed from the RFC AV usage ledger; enforcement is advisory (per-replica, fail-open). @loomcycle/client 1.11.0. | | v1.11.1 | 🩹 Web UI patch. The Usage page no longer blanks on an empty report — GET /v1/_usage returned a Go nil slice (JSON null) and the UI's resp.rows.length crashed the view to a blank overlay on any no-usage window (fresh deploy / tenant with no spend); the endpoint now returns [] and the page guards defensively (#647). The RFC AW token-limit editors now accept K/M/G shorthand500K, 5M, 2G (also B/T, decimals, separators) — with a live = 5,000,000 recognition hint, so a multi-million-token budget isn't eye-counted zero-by-zero (#648). Also realigns the Python adapter → 1.11.0 (#646) so its RFC AV/AW gRPC methods publish on python-v1.11.0. Binaries otherwise identical to v1.11.0; @loomcycle/client unchanged at 1.11.0. | | v1.12.0 | 🔑 providers:operator-key scope (RFC AX) — gate the operator-API-key fallback for cost-isolated, bring-your-own-key tenants. RFC AR let a tenant override with its own key; the fallback to the operator's host key on a miss was unconditional, so any tenant could spend the operator's LLM/search budget by not bringing a key. Behind a default-off gate LOOMCYCLE_OPERATOR_KEY_RESTRICTION (gate-off ⇒ byte-identical), a run whose principal lacks the tenant-implied providers:operator-key scope is restricted: Layer 1 credential-aware routing sends it only to providers it can key itself and refuses 403 (operator_key_restricted / gRPC PermissionDenied) if none; Layer 2 a mandatory driver backstop never serves a restricted run the operator's key (covers pinned agents). Universal + backward-safe via a negative OperatorKeyRestricted bit + a runs column (migration 0055, both backends), stamped at every run-start, inherited by sub-agents, restored on resume, captured on Schedule/Webhook defs (anti-bypass), derived from the authenticated peer's scopes for A2A, and fail-closed on the LLM gateway/embeddings/compaction (#650). Grant it at token mint (Web UI scope list). @loomcycle/client + the Python adapter unchanged (server-side auth feature). | | v1.12.1 | 🧩 @loomcycle/client 1.12.1 — Path/Document browse-by-subject + the full Document op set. A lockstep adapter-surface release (binary functionally identical to v1.12.0) that extends the TypeScript client so the upcoming @loomcycle/explorer reusable React component (RFC AZ — the Path/Document twin of @loomcycle/library) can drive the tools through the SDK: path()/document() gain an optional { scopeId, tenant } browse override sent as ?scope_id=/?tenant= query params (the RFC AS off-run browse the server reads from the URL and re-authorizes; omit both → byte-identical request), and DocumentToolInput.op now mirrors the backend enum 1:1 (16 ops — adding set_path/export_md/import_md + the include_metadata/markdown fields) where it previously exposed only 13. Additive; no server/wire change, no behavior change for existing callers (#655). | | v1.13.0 | ⚠️ Breaking — the tool-allowlist key allowed_tools is renamed to tools. The agent tool ceiling, the per-run caller filter, the MCP-server filter, and the skill list — plus the YAML schema, the gRPC/HTTP/MCP wire, and the TS/Python adapters — all now use one canonical key, tools (which also aligns the agent field natively with Claude Code). The old allowed_tools key is dropped entirely (no alias). The Claude Code skill-import kebab allowed-tools stays as an import fallback, and the skill loader now honors the canonical tools key in SKILL.md frontmatter (it was kebab-only, silently dropping tools: requirements). Content hashes change (the canonical JSON key changed); proto field numbers are preserved. @loomcycle/client + the Python adapter 1.13.0. Downstream callers sending allowed_tools on run requests must switch to tools (#659). | | v1.13.1 | 🩹 WebUI patch. Cleans the last allowed_toolstools residuals in the WebUI — the Library edit-modal camelCase identifiers + the lib-tools DOM id, and bundling @loomcycle/client 1.13.0 so the SPA no longer carries the old client's allowed_tools wire-mapping dead-code (the visible field label was already "tools") (#661); Settings→Routing now shows live provider availability to tenants (filtered to their keyable providers when LOOMCYCLE_OPERATOR_KEY_RESTRICTION=1; raw last_error stays admin-only); and the Settings→Credentials key-name field is a combobox of the known provider/tool keys (#662). Server + WebUI only; TS/Python adapters unchanged at 1.13.0. | | v1.14.0 | ⚠️ Breaking — RFC BA: skills go on-demand + one skills: pattern allowlist. Skill bodies are no longer bundled into the system prompt; every agent that may use a skill gets the Skill tool auto-added and loads bodies on demand (Skill(op=list) to discover, Skill(name=…) to load — the tool is now op-discriminated; {name} still invokes). **skills: is now a pa