Skip to content

Releases: weftbase/weft

v0.21.0

Choose a tag to compare

@wajihtba wajihtba released this 03 Nov 12:48

Changed (breaking)

  • Repository split (phases 2–3): the platform is weftbase. The module path is now github.com/weftbase/weft (was github.com/weftgo/weft) and the loop's github.com/weftbase/weft/core (was github.com/weftgo/weft/core); every import, the facades' generate lines and the instrumentation scope names (github.com/weftbase/weft/core, …/otel/slog, …/thread) follow. The platform packages moved, with their history, to their own repository and module, github.com/weftbase/weftbase (not released yet; its own version line; it imports weft, weft never imports it): studio (+ studio/ingest, studio/runtime, the web source and the embedded dist), obsdb (+ obsdb/sqlite, obsdb/clickhouse, obsdb/obsdbtest), runtime, scope, exporter, the CLI (cmd/weft → github.com/weftbase/weftbase/cmd/weftbase, the binary weftbase), the examples studio-local, devtools-vite and thread/examples/approvals (now weftbase's examples/approvals), ADRs 0018 and 0024 (one-line pointers stay here), the weft mcp recipe and the platform's testing sections. weft studio and weft dev leave weft with the platform: traces go to any OpenTelemetry backend (OTEL_EXPORTER_OTLP_ENDPOINT, otel.OTLP), and Weftbase's console is coming (weftbase is not released yet); WEFT_STUDIO_URL / WEFT_STUDIO_TOKEN / WEFT_STUDIO_ADDR / WEFT_DB / WEFT_DISCOVERY are WEFTBASE_URL / WEFTBASE_TOKEN / WEFTBASE_ADDR / WEFTBASE_DB / WEFTBASE_DISCOVERY (no fallback), the local data directory ./.weft/ is ./.weftbase/ (default database ./.weftbase/weftbase.db), panel tokens are wb_pt.…. otel.Local and friends, which phase 1 moved to weft/exporter, are github.com/weftbase/weftbase/exporter. The framework drops clickhouse-go and fsnotify from its requirements. make layering now fails when anything here imports github.com/weftbase/weftbase; the Makefile's studio, vacuum and mcp-check targets and CI's clickhouse, studio and devtools-vite jobs moved with the platform. Step by step: MIGRATION-0.21.md.
  • otel (repository split, phase 1: weft emits; the platform stores, shows and controls): weft/otel no longer knows Weft Studio or its store. It configures from the standard OpenTelemetry variables only (OTEL_EXPORTER_OTLP_ENDPOINT and _HEADERS, OTEL_SERVICE_NAME, OTEL_RESOURCE_ATTRIBUTES, OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT) plus WEFT_ENV for the development gate, and no longer imports obsdb or the discovery file. Removed from otel: Local, Studio, LocalDB (package function and Pipeline method), StudioEndpoint (both), NoDiscovery, FromSDKSpans, FromSDKRecords, the WEFT_STUDIO_URL / WEFT_STUDIO_TOKEN / WEFT_DB environment destinations, the discovery-file join and Install's zero-config fallback to the local sink — otel.Install() with nothing configured now records nothing, says so in one INFO line and returns a no-op. Several destinations at once, each with its own content policy, raw bodies, the dev detail and the slog bridge are unchanged.
  • exporter (new, platform; moves to the weftbase repository with studio, obsdb and runtime): the local developer platform's destinations on top of otel. Migration: otel.Local(p) → exporter.Local(p), otel.Studio(url, tok) → exporter.Studio(url, tok), otel.LocalDB() → exporter.LocalDB(), p.LocalDB() → exporter.LocalDBOf(p), otel.StudioEndpoint() → exporter.StudioEndpoint(), p.StudioEndpoint() → exporter.StudioEndpointOf(p), otel.NoDiscovery() → exporter.NoDiscovery(), otel.FromSDKSpans / FromSDKRecords → exporter.FromSDKSpans / FromSDKRecords; for today's zero-config developer behaviour (WEFT_STUDIO_URL / WEFT_STUDIO_TOKEN, the discovery file of a running weft studio, WEFT_DB, the local sink when nothing is configured) use exporter.Install(...) / exporter.Start(ctx, ...) in place of otel.Install / otel.Start; they take the same otel options. exporter.StudioEndpoint (what runtime.Install dials) is known for any installed pipeline with an exporter.Studio destination, a plain otel.Install included. Studio, the runtime link, the weft CLI and the examples use it.
  • Every framework package (everything but studio, obsdb, runtime, scope, exporter, cmd/weft, their internal packages and the platform examples), its tests included, no longer imports a platform package; the adapters', otel's and thread's tests that read records back from obsdb run against in-memory exporters, and their store round trips moved to exporter's tests.

Fixed

  • thread: a decider's context no longer hides what the parked turn ran under. A resume takes the parked turn's context values only where the deciding call's context has none, and metadata is one value: an inbox handler that tagged its request context with core.WithMetadata and called Decide ran the resume without the parked turn's tenant pairs (likewise an inherited ParkAllExcept list under one of the decider's own). The settings recorded on the requests now ride every resume, in-process too; the parked metadata wins per key, the decider's merges under it (ADR 0021, amendment 2026-11-03). A deferred steer's follow-up had the same hole against the turn it aimed at, and keeps that turn's metadata and park rule the same way (ADR 0019).
  • thread: a call parked under Quorum(n) needs n approvals however the session is reopened. Quorum is a session option and was not recorded: a process that opened the session without it (another worker, another build) resolved the call on one approval. The request now records its quorum (ApprovalRequestEntry.Quorum, JSON quorum; Request.Quorum on Pending), and the stricter of it and the session's own decides; a pool mirror carries the child's and records the parent's (ADR 0021, amendment 2026-11-03).
  • thread: an approved call no longer runs on a build whose tool changed under it. A parked call resumed after a deploy or a restart onto new code ran its approval against whatever tool now had its name: a tool whose input schema changed in a way the old arguments still decoded into ran the call under a contract nobody approved, and a removed or renamed one read NO_SUCH_TOOL with nothing in the audit trail. The request now records its tool's input schema hash (ApprovalRequestEntry.SchemaSHA256, JSON schema_sha256); on a resume whose agent lacks the tool or has another schema, the approved call is denied with a model-visible reason and audited (resume step, outcome drift). Denied and resolved calls, and a change to the approval mark alone, are unaffected (ADR 0021, amendment 2026-11-03).
  • core: an agent's manifest — and weft.manifest.hash, its sha256 — now changes when any agent below it in the delegation tree does. A Subagent tool named its child only, so a child's (or grandchild's) prompt, tools or model could change while the parent kept its hash, and a version pinned by that hash did not pin what the parent delegates to. Each Subagent tool entry now carries subagent_hash, the child's own manifest hash (named or unnamed child), which pins its children in turn; the manifest still does not recurse, and the hash is still the sha256 of Manifest(a) (ADR 0012, amendment 2026-11-03).
  • thread: a deny-grant's decision is recorded with Via grant (it had none); an approval grant's always was.
  • thread: a resume after a restart no longer drops the park rule the parked turn ran under. A turn sent with thread.RunOptions(core.ParkOn(...)) or core.ParkAllExcept(...) (or one inheriting ParkAllExcept from a delegating run, as a thread/pool child does) parked a call; the process restarted; the reopened session was decided — and the resume ran without the rule, so the next call to a tool the rule parks ran unasked. The rule is now recorded on the boundary's request entries and put back on a resume that lost the parked turn's options. New: core.RunResult.Park / weft.ParkRule, the rule a run ran under as data, and ParkRule.Options() to re-apply it.
  • thread: a resume after a restart, and a queued send or steer restored by Open, no longer drop the rest of what they were sent under. The parked or queued send's OnlyTools narrowing, a lowered MaxSteps or Parallelism, its Instructions, Thinking, ToolChoice and Params overrides, and its caller metadata (thread.RunOptions(core.Metadata(...)) and the context's core.WithMetadata pairs, a tenant among them) were process state: after a restart the resumed run offered tools the send had narrowed away, spent past the lowered budget and lost its tenant tag, and a send queued behind an open boundary under ParkAllExcept ran every tool unasked. They are now recorded as data — ApprovalRequestEntry.Run on the boundary's requests (replacing the unreleased ApprovalRequestEntry.Park, JSON park → run.park) and ReceiptEntry.Run on queued and accepted receipts — and put back on the restored run. A restored narrowing is never dropped to let the run start: an OnlyTools name the reopening agent no longer registers, or a budget above its own, fails the run with ErrInvalidRunOption before any model call. UseModel and OnMessages are not data and stay lost: a run restored after a restart runs on the agent's own model (ADR 0021).

Added

  • thread: Grant.Who and GrantEntry.Via — a grant records its grantor and the door it came through (user for Session.Grant; an ApproveAlways grant takes the minting decision's Who — its signing key id when the signature named no one — and Via). Every decision a grant makes is recorded with the grantor as its Who, so an automatic approval is attributable to the person who made the grant; the grant.mint record carries who in its content (ADR 0021, amendment 2026-11-03).
  • thread: Decision.RequestID and Decision.ArgsSHA256 — a decision can name the occurrence and the arguments i...
Read more

core v0.21.0

Choose a tag to compare

@wajihtba wajihtba released this 03 Nov 12:48

See CHANGELOG.md, 0.21.0 — 2026-11-03.

v0.20.3

Choose a tag to compare

@wajihtba wajihtba released this 31 Oct 20:22

Fixed

  • thread/pool (F1, found by the weft-arena torture tier's kill loop): Recover settling a child as over (failed with "no turn was recorded", or finished) now ends that child's own unsettled delegations first, depth first, as Cancel and Close do — a parked sync grandchild is canceled at its boundary (its pending calls denied in its session) and settles canceled in the child's ledger, its requests there denied; a grandchild whose run is over settles from its file. Before, a kill while a middle child was mid-run left its delegation running/parked for good and the grandchild's approval offered nowhere. A parked async grandchild is left parked (D4); a child whose subtree cannot be reopened (no agent) stays unsettled and is reported with ErrNoAgent; a kill inside the cascade (between a grandchild's settlement and the denial of its requests) is finished by the next Recover. ADR 0022 amendment 2026-10-31.
  • thread/pool (F2, same): a parked child whose parent's delegating turn never wrote its turn entry (the kill landed between the mirror and the park record) is reattached by Recover as a delegation by no call: its request stays offered, a decision resumes it, the approved call runs and its answer settles the receipt. Before, it was reattached under the call, which nothing had parked or ever would: the decision was recorded but the child never resumed, and Close stalled for its 30 s bound. The parent's delegating call is answered as interrupted by its next run's input repair, as any call a crash cut off; the child's answer is on the receipt (ADR 0022 §J's rule for an abandoned delegating call).

Docs

  • core, README, docs/agent-loop-deep-dive.md: the Subagent godoc, CodeSubagentPending, the README's subagent section and the deep dive point at weft/thread/pool where they state that a bare Subagent never propagates a child's pending approvals (the deep dive still deferred it to runtime); thread/pool has propagated them since ADR 0022 §7. The model-visible SUBAGENT_PENDING text is unchanged.

core v0.20.3

Choose a tag to compare

@wajihtba wajihtba released this 31 Oct 20:22

See CHANGELOG.md, 0.20.3 — 2026-10-31.

v0.20.2

Choose a tag to compare

@wajihtba wajihtba released this 27 Oct 20:33

Fixed

  • Studio, weft export --wefttest (P020.2): save as test on a run that offered no tools writes a test that compiles — it declared a tool-result handler it never used (declared and not used: answer); now it declares and imports only what the test uses.
  • Studio (P020.2): the runs list fits its frame at 1280 to 1800 px — a run that delegated shows its subagents' tokens on a second line under its own instead of beside them, a token pair breaks only at its dot ("20 in ·" over "10 out", never "10" apart from "out"), the agent column holds a longer name ("procurement-desk", it read "procurement…"), the run column is 14 rem wide, 16 rem from 1360 px and 20 rem from 1680 px (14 rem again from 1536 px, where the session and model columns join), and the refresh button stays at the end of the first row instead of wrapping onto a row of its own.
  • Studio (P020.2): a derived run's fix says what to set without a repository path ("(docs/record-contract.md §9)" is gone), and its waterfall starts under the view's controls — the layout toggle ends that row instead of standing alone above the waterfall.
  • Studio (P020.2): the run page's waterfall keeps a duration on one line ("21.3 ms", it wrapped "21.3" over "ms"), shows a span's whole name when its row has room ("execute_tool lookup_order"), and an attempt's name beside its error chip ("attempt 1 · script" with stream_idle, it read "attempt 1 · s…").
  • Studio (P020.2): the sessions list breaks "awaiting a decision · 1 call" and "1 exchange · 2 turns" only at their dot, and its find box shows "find title, prompt, id" whole; the Agents page's numbers end every bar at one place, whatever the width of its figure.
  • devtools panel (P020.2): the header shows each of its figures — the tokens, the cost, the unpriced count — whole or not at all, never cut mid-figure ("≥1.0M in …", "2… $.."), the title keeping its room (a page of runs' title keeps the space before its count, "170907 · 928 runs"); the narrow dock's run picker puts the status, steps and duration before the title or a replay's lineage, so a cut title no longer takes them ("· 3 st"); a running turn's duration reads "…" instead of "—"; a parked call's arguments read on one line, as in the step's call list, instead of a pretty-printed block hanging off the tool's name; the decision field's placeholder is "result or deny reason", shown whole (0.20.1 said "result to resolve with" was shown whole; the placeholder was longer and cut).

v0.20.1

Choose a tag to compare

@wajihtba wajihtba released this 27 Oct 17:44

Fixed

  • Studio (P020.1): the run page's waterfall draws only the time-axis ticks its track's width holds — a short run's or a narrow track's labels no longer print over each other; a derived run's root row counts its steps as its header does ("2 steps", it read "0 steps"); an attempt's error chip (model_error, stream_idle) stays whole beside its name instead of under its bar's marker.
  • Studio (P020.1): the runs list and the sessions list fit a 1280 px window with the sidebar open — the runs list's model column shows from 2xl (1536 px) and its status, tokens and cost wrap, so the compare pick no longer pushes tokens off the edge; the sessions list's status and turns wrap and its agent truncates, so tokens, cost and last seen stay in the frame.
  • Studio (P020.1): /agents?agent=<name> keeps the named card in view while the numbers above it load and lay out (with many agents it ended far down the page); the Agents page says a registration time as Studio says times (relative, the stamp on hover — not the server's raw RFC 3339 with nanoseconds), points at keeping a proposed prompt in user words (it named an internal design doc), and pads the selected card inside its ring.
  • Studio, devtools panel (P020.1): a plain Go error a tool layer hid (mw.MapErrors over an errors.New value) reads INTERNAL ← plain error, not INTERNAL ← *errors.errorString (an errors.Join or two-%w error, joined errors); the recorded weft.tool.error_masked is unchanged and stays on the hover.
  • Studio (P020.1): the call graph sizes its nodes and column gaps to their words — an edge label no longer prints over a node, and an MCP server node's failure line ("1 unreachable · 1 refused · 1 tool error") is no longer cut; a node grows to 420 px at most, a longer name cut with an ellipsis and whole on hover.
  • devtools panel (P020.1): a page of runs' header reads "batch · N runs" at every dock width — the id is cut before the count, and the token, cost and unpriced lines give way before the title; the Timeline axis names its two ends instead of five overprinted ticks; "has error" is a normal checkbox on its label's line; the replay drawer's engine options no longer wrap into two overlapping columns in a 640 px dock; a parked call's "result to resolve with" field shows its whole placeholder.

v0.20.0

Choose a tag to compare

@wajihtba wajihtba released this 27 Oct 14:52

Added

  • core, mw, otel, obsdb (U23.1; UX-S2-01, UX-E-08, UX-E-09, UX-E-10, UX-E-15, UX-A-43, UX-S2-04): the middleware onion recorded. The manifest lists each agent's model_middleware and tool_middleware outer to inner (a tool's own middleware) and its hooks (prepare_step, tap, on_run_end, tool_source), each {name, params?, source?, opaque?}: a model layer describes itself with the optional Describe() core.MiddlewareInfo (mw.Retry its budget and delays, mw.Fallback its backups, mw.Log, mw.RepairJSON), others by type — a layer without Unwrap ends the walk opaque — and functions by name, with their source when not weft's own. A manifest source (a tool's, a hook's, a middleware's) is one form on every host, never an absolute path: the defining package's import path and the file's base name (github.com/x/app/tools.go:12; a module-cache file by its module path, the linker's %2e unescaped). The spans say what a layer decided: core.ToolReportFromContext (ToolReporter.Denied, Masked) puts weft.tool.denied_by (mw.Allow: allow) and weft.tool.error_masked (mw.MapErrors: the hidden error's kind — a standard sentinel through errors.Is, an optional ErrorKind() string — never its text; a mapping that only wraps reports nothing) on execute_tool; core.Reporter.Repair (mw.RepairJSON) a weft.repair event on chat and weft.tool.args_repaired on the call; core.AttemptLayerRetry marks mw.Retry's retried requests; core.DenyBy names a deny grant on a resume's denied span (weft.tool.denied_by grant, weft.grant.id; else approval), which weft/thread passes. otel.DevDetail() (dev-gated like RawBodies; LoggerProvider.DevDetail) adds one layer <name> span per layer per call (weft.layer.name / kind / index; a panic through a layer ends it Error / panic), the repair's original arguments and a contained tool or middleware panic's stack (a weft.panic event on execute_tool, 16 KiB), for the Local and Studio destinations with content on only; never by default. The request record gains wire {server_address, extra_body_keys, extra_headers, dropped} (UX-S2-09): the adapters' endpoint host[:port], ExtraBody keys and ExtraHeaders names — names only, bounded — through the optional WireFacts() method (openai, anthropic, google; the primary's behind an mw.Fallback). obsdb derives the repaired counter. Record contract §3 (the layer span, the weft.repair event, the execute_tool rows), §6, the manifest's onion, the onion conformance fixture.
  • mcp, core, obsdb (U23.2, O8; UX-S2-08, UX-A-43, UX-S2-04): MCP identity. mcp.Server(name) names the server an import comes from — every imported tool's Origin reads mcp:<name> — and mcp.Transport(kind) how it is reached; core.OriginDetail(core.SourceDetail{Server, Transport, Prefix, RemoteName, Skipped}) carries the parts. The tools record gains source_detail per tool and skipped[] {name, reason, source} (the tools the listing could not import — a server none of whose tools imported included, through core.SkippedImports and mcp.ImportError.Option() — once per source, at most 64, skipped_more the rest, names cut at 128 bytes; neither in the catalog hash); execute_tool carries weft.tool.origin (the tool's Origin when not local: subagent, mcp:<server>). An MCP call's failure is one of three error.types, contract rows: mcp_transport (no answer: a closed session, a stdio stream at EOF, the go-sdk's refused, dropped or TLS-failed streamable-HTTP connection or a 5xx / 429 without a JSON-RPC body — ErrRejected −32005, −32004, −32003), mcp_rpc_error (the server's own JSON-RPC error answer: its handler failed, the tool is not listed) and mcp_tool_error (an isError result). obsdb reads it all (ToolsRecord.Skipped, ToolEntry.SourceDetail); /api/runs/{id}/tools serves it. Record contract §3, §4, §10 (mcp_mesh); ADR 0015 and 0028 amendments.
  • Studio, devtools panel (U23.2; UX-S2-08, UX-A-43, UX-S2-04): the onion shown. The drawer's "what wrapped this call" — a model or tool call's chain outer to inner from the run's stored manifest (its Timeout outermost), each layer's decision from its spans (Retry's attempts, waits, HTTP status and give-up, Fallback's served model, RepairJSON's "repaired 1 arg", Allow's "✋ denied by policy", MapErrors' "INTERNAL ← io.EOF"), a decision shown once per layer kind (a second mw.Retry says the record cannot tell which), an idle layer greyed, a click opening its attributes; without a stored manifest only the layers that reported, said so. The Agents card draws each agent's model and tool chains; the story's step chips (↻ retry ×2, ⇄ fallback, ✎ repaired 1 arg) and tool-row chips (✋ allow, ⇣ io.EOF, ✎ repaired), on the panel too, with its MCP server; a denied call reads "denied by policy", an MCP failure "MCP unreachable", "MCP refused the call" or "MCP tool error" on both renderers; the call graph draws mcp:<server> from the span's origin — a child run's calls included — with its calls, slowest call and failures by kind ("1 unreachable · 1 refused · 1 tool error"), and a caller-linked run under its caller; the Request pane's catalog names each tool's origin in parts and lists the skipped tools greyed (the panel's Request tab too). lib/onion.ts serves both renderers. Glossary: middleware, MCP server.
  • mcp, core, obsdb (U33.1; UX-S2-08's remainder): a served agent's run is linked to its MCP caller. Under the new mcp.CallerMeta() option (off by default — the weft run and call ids and the trace state name your runs to the server, so send them to servers you run, not to third parties) mcp.Tools' imported tools send their caller in params._meta: the execute_tool span's W3C traceparent (and tracestate) and mcp.MetaCaller ("dev.weftgo/caller": {run_id, call_id}). mcp.Serve (and AddTools' tools) read it back: the served run's invoke_agent span starts its own trace with an OpenTelemetry link to the caller's span — never the caller's child, so a client's "not sampled" flag never decides whether the server records the run — and records weft.caller.run.id / weft.caller.call.id with weft.caller.remote "true": a link, never weft.parent.* (O11; record contract §2, the remote_caller fixture). A remote link is the caller's own statement, so readers never place a run by it: obsdb leaves caller_run_id / caller_call_id empty for it on both backends, the run stays top-level, and Studio says "started over MCP by run … in another process (not linked)". A missing or malformed _meta links nothing and refuses nothing. New core.WithCaller(ctx, core.Caller{RunID, CallID, Span}) (and weft.WithCaller, weft.Caller): every run started directly on ctx (not inside a tool call) records that caller; no run started inside it inherits it. The mcp package now imports the OpenTelemetry trace API (no SDK).
  • core, mw, obsdb, Studio (U30.3; UX-S2-02's remainder): core.AttemptInfo.GiveUp (AttemptGiveUpBudget / AttemptGiveUpNotRetryable / AttemptGiveUpRetryAfter, re-exported by weft) becomes the attempt span's weft.attempt.giveup_reason: mw.Retry says why it stopped there — its retries spent, a failure its classifier does not retry, a retry-after past MaxWait (behaviour unchanged). It is that layer's stop: a Retry under a Fallback marks where it stopped and the call may go on to the backup; a Retry over a self-reporting model gives no reason. The step route's attempts[] carry giveup_reason. Studio's attempts list words each cause ("rate limited (429)") and ends a failed call with "gave up: retries spent (2 attempts)" ("gave up after N attempts" on an older record; never when the last attempt ended on the run's cancellation or deadline); the run tree draws an attempt's weft.attempt.wait_ms as a hatch before its bar.
  • core, obsdb, Studio (U30.3; UX-S2-07's, UX-S1-06's remainders): the request record's body carries parallelism, the step's Parallelism dial (the agent's, a run's lower one, 1 under Sequential()), drawn "parallelism N" beside a step's tool lanes, a Sequential tool's row "▮ sequential". An Output agent's run names its typed answer's type on invoke_agent — weft.output.type, package-qualified (main.Invoice), at most 128 bytes cut at a rune boundary — and the manifest's submit_output tool carries it as output_type; Studio's OUTPUT card shows it and the Agents card marks the tool "typed output · main.Invoice". The manifest carries an agent's configured sampling knobs as params (its weft.Params, by the request record's names; absent when it sets none).
  • obsdb, Studio, devtools panel (I6; UX-H-01, UX-H-02, UX-H-03, UX-H-04, UX-S4-02, UX-S4-03): derived runs from the producer's content (docs/record-contract.md §9). A top-level invoke_agent span without gen_ai.conversation.id (no other agent span stored above it) is a derived run with no session; one nested under another agent still folds into the enclosing run, and a derived row an export folds later (an inner agent exported before its parent) is dropped — on ClickHouse reconciled against a fresh read of the trace, so the last of two racing writers leaves no stale row, and announced live as a run frame whose row is removed (obsdb.RunRow.Removed), which the runs list and the Live page drop. A derived row's message_count counts its chat spans carrying message content (obsdb.StockHasMessages). steps/{n} serves a derived call's args and result from gen_ai.tool.call.arguments / gen_ai.tool.call.result when the producer captured content (obsdb.StockToolContent; else args null and no result), and the run page's story draws each step's USER / ASSISTANT / tool-call rows from its chat span's gen_ai.input.messages / gen_ai.output.messages / gen_ai.system_instructions (the trace page's chat renderer, components/studio/genai-chat.tsx). The derived badge...
Read more

core v0.20.2

Choose a tag to compare

@wajihtba wajihtba released this 27 Oct 20:33

See CHANGELOG.md, 0.20.2 — 2026-10-27.

core v0.20.1

Choose a tag to compare

@wajihtba wajihtba released this 27 Oct 17:44

See CHANGELOG.md, 0.20.1 — 2026-10-27.

core v0.20.0

Choose a tag to compare

@wajihtba wajihtba released this 27 Oct 14:52

See CHANGELOG.md, 0.20.0 — 2026-10-27.