Changelog
Every release, newest first. The @gnldev/* packages are versioned and released together, so one version number covers all of them.
Each summary says what changed and, for a minor, why it is one — in 0.x a minor is where a breaking change goes. The full entry, with every change and its reason, is in CHANGELOG.md.
0.18.0
A yes answers a question that was asked, a tool call keeps its own identity, and what a run's end means reaches the client the same way through every door. Approvals are written only for a question that is waiting, and split into confirm (the requester may answer) and approve (an authorised person must): the requester's own yes on an approve question is refused (403 approval_not_permitted) and a yes sent before its question existed is ignored. Every tool call gets its own id, so a provider that repeats call_0 no longer merges two calls. A canceled run answers 409 run_canceled; a memory object works per organization or refuses by name (memory_not_splittable); a model step has a default time limit of 120 s, and the model's reasoning stays off the wire until asked for. This is the one release of what was prepared as 0.17.1 and 0.17.2; neither was published. Upgrading from 0.17: no store step, so gnl upgrade has nothing new to run; do not run an old and a new process on one database at once; ask with confirm, or let an operator or approvers answer, where the requester used to say yes; a Slack guard's approve needs approvers; code that answers as the requester passes caller: STAFF to answer an approve question; turn on stream.reasoning if your UI renders the model's thinking; raise timeouts.modelStepMs for a model that stays silent longer than 120 s; a canceled run is 409, not 400; a memory object that cannot be split by organization is refused; a workflow run that threw is listed failed.
Full entry →0.17.0
A store's process liveness, a job's and a fire's right to run, and a store's upgrade are each one rule. The queue takes jobs fairly across organizations from per-organization lanes; the scheduler decides a fire under its lock and no longer makes due triggers wait for each other; cancel, a lost lease and a time limit reach a handler as one signal with its reason (ctx.signal, maxHoldMs); packages register their upgrade steps on the store, and gnl upgrade finds them. A cut model stream no longer answers as finished: it ends run_unfinished (502), and the same runId calls the model again. Also new: enqueueMany, listAllJobs, maxCatchup and ctx.scheduledFor, and limits in gnl.config.ts. Upgrading from 0.16: run gnl upgrade before this version's queue workers start (it moves the queue's jobs into per-organization lanes, and is safe while 0.16 workers still run; gnl upgrade --only queue/lanes-cleanup follows once every 0.16 process is gone); a store's liveness goes to the storage, not to createGnl or createWorker, on the five built-in stores; listJobs returns a page and, without orgId, lists only jobs that have no organization; misfire: 'skip' is gone and the default is 'latest'; custom work stores need read, appendMany and the ready set; the floors are ai 7.0.149, @ai-sdk/openai 4.0.100 and @ai-sdk/anthropic 4.0.86.
Full entry →0.16.0
Why an agent did what it did, and what to change so it does what you meant: Studio gets a Lab, opened from the Inspector — each part's share of a run, a thread turn diagnosed against a written expectation (a guess first, then played to prove it), the words that decided a call, a changed copy of a turn played beside the run as it was, and whether a prompt section can go. Two new packages carry it: @gnldev/experiment (what the optimizer runs on) and @gnldev/lab, which is experimental — its diagnosis was measured on planted faults with one model (39 of 40 named, no wrong cause in 55 turns) and its API will move. Upgrading from 0.15: the Lab has three tabs (Improve, New agent, Scenarios) and the old modes and their URLs are gone; replayCounterfactual, DEFAULT_SUITE, runSuite, checkScenario and normalizeAnswer moved from @gnldev/optimizer to @gnldev/experiment, with no alias; Studio's Regression tab, POST /runs/:id/regression and the regressionModel option are removed, and the Lab's capsule replaces them; bestForm's best is absent when no form passed a scenario; :memctx counts describe what the model was sent. No store step.
Full entry →0.15.0
What an agent costs, measured on its own runs: a new package, @gnldev/optimizer, reads where an agent's money went from what the provider billed (the opening re-sent on later steps, the same instructions re-sent by runs that start within 5 minutes of one another, old tool output), offers two levers (prompt-cache marks and leaving old tool output out of the context), and replays recorded runs with a cheaper model — every tool answered from the recording, none executed — to show whether the cheaper setting does the same work. It recommends and changes nothing; Studio shows it as an Optimizer tab. Measured on a paid key: a cache key took a third off four runs sharing their instructions. Upgrading from 0.14: Anthropic cache writes are now priced at the write rate, so runs that write to the prompt cache cost slightly more in GNL's numbers — the amount the provider bills; RunCost gains cacheWriteTokens; no store step.
Full entry →0.14.0
Agents where people talk: a new package, @gnldev/channels, runs an agent on Slack and Telegram — a message is a durable job named by the platform's id for it (a redelivery is the same job), the agent runs once, the reply is posted once on Slack (at most once on Telegram, which cannot read a chat back), and approval buttons only the person who asked can answer. Also: Elasticsearch and OpenSearch vector stores; end-user tokens straight from Auth0, Clerk, Supabase or Firebase (endUsers.jwks, ES256); an eval gate for CI (gnl eval); the REST API tested inside Express, Fastify and Koa; @gnldev/durable building inside Next.js with no config; a deadline on every vector-server call; and a Weaviate collection race fixed. Upgrading from 0.13: vector-server calls fail after timeoutMs (30 s by default); endUsers takes one of secret, publicKey or jwks; no store step. Fixed: a fresh Postgres store's first lookup by prefix no longer answers "none" on an en_US.utf8 database — an erasure that came first in a process left the person's thread behind.
Full entry →0.13.0
Two more vector stores, measured like the rest: ChromaVectorStore and WeaviateVectorStore (@gnldev/rag) keep documents on a Chroma or Weaviate server with no extra package, and pass the same 20-scenario vector kit as every other store. Both servers' searches are approximate and miss some copies of one vector (Weaviate: 2,974 of 3,000 found with a limit of 4,000), so a tie at the top-K cut is read whole and ordered by id. Upgrading from 0.12 needs no step.
Full entry →0.12.0
A store states its guarantees and is measured against them (ADR-0009): the few atomic operations every promise rests on are now the store contract, a store without them is refused where it enters, and one kit measures every backend. New stores measured by it: MySQL/MariaDB, MongoDB, libSQL/Turso, and Qdrant for vectors. Upgrading from 0.11: no store step on Postgres or SQLite; a custom store must provide the listed guarantees; a crashed process's work is taken over after reconnectGraceMs (5 s) instead of at once — measured, a cut liveness session no longer lets a second process run a tool body beside the first.
Full entry →0.11.1
A schedule's fire, a queued job and a batch item belong to the owner they were made for: on 0.11.0 an end user who spelled such a run id first could take it (measured: the owner's trigger failed, their job failed every attempt, their erasure deleted the stranger's run). Now whoever composes the id begins it. And a model step is answered once: the first recorded answer is the one every attempt returns. No store step; renamed exports are listed under Breaking.
Full entry →0.11.0
A run no longer lives inside the request that started it: a closed tab, a dropped connection or a timeout leaves the run going, reconnecting from any process reads on where the reader left off, and a dead process's runs are taken over by a live one. Long tool work can run in the background. Measured with real processes on Postgres: exactly once where the tool's provider accepts the idempotency key, otherwise at most the retry ceiling. A new thread no longer reads the store's other runs. Upgrading from 0.10: stop every process and run gnl upgrade first (not a rolling deploy); deleting a thread now deletes its runs; there is no Redis store any more.
Full entry →0.10.0
A sub-agent keeps every guarantee the agent keeps, and the request is the unit: "once", limits and approvals hold across every run a request starts. A new package, @gnldev/options, holds the rule for unknown option names. Upgrading from 0.9: a repeated side effect with no idempotency key, or one after untrusted content, now asks a person instead of running (sideEffectDuplicates and taintedSideEffects default to suspend; warn restores the old behaviour); a tool with idempotencyKey must declare idempotencyWindow; every options object refuses a name it does not take, naming the closest one; a sub-agent returns status, text, interrupts and, when it failed, failed — its approvals are addressed delegation call / child call, and delegatedTo says which sub-agent asked; a request may not send a system message (400 caller_system_message). Fixed: a payment handed to sub-agents paid 30 employees 40 times, now 30; 200 parallel calls with a tool-call limit of 50 run exactly 50 on memory, Sqlite, Postgres and Redis; the Redis store no longer walks the whole database to list a prefix — a 10-call run beside 1,000,000 unrelated keys went from 2983 ms to 37 ms. Known limit: a Redis store that 0.9 wrote keeps listing by SCAN until, with every process on 0.10, you call storage.rebuildKeyIndex() once.
Full entry →0.9.0
The paid auth package stops trusting four things: an expired licence, a token signed for another application, a login callback without a state check, and JWKS keys cached without limit. Each refusal names the one line that restores the old behaviour. The standalone chat and AG-UI routes can now check roles. The repository moved to github.com/gnlhq/gnldev.
Full entry →0.8.0
A misspelled Principal field or error-code name is now a compile error, and every package declares sideEffects, so bundlers drop what an app does not use — the durable core went from 61.5 to 57.4 KiB gzip. Type changes only; nothing changes at runtime.
Full entry →0.7.0
End users are isolated by default, at one point, for free, and every package asks who the caller is the same way (identify). Many entries are breaking — read the upgrade list at the top of the entry first.
Full entry →0.6.0
McpServer.listTools is now async and takes the caller, and Studio no longer starts on a non-loopback host when its only credentials are the development ones.
Full entry →0.5.0
Validation tightened in three places — the runId record separator, JWT nbf and the tool test form — and StudioAgentRunner gained a fourth parameter.
Full entry →0.4.1
A guard's warning overstated what it did. The message changed; the behaviour did not.
Full entry →0.4.0
Two ways a delete could take somebody else's data, most of all with ids that contain a colon. A ^0.3.0 range does not pick this up on its own — upgrade deliberately.
Full entry →0.3.0
A thread id could rename its own keyspace, and one sweep then deleted every thread. Two words that used to be accepted are now refused.
Full entry →0.2.0
One change to a persisted key schema — read Changed before upgrading.
Full entry →0.1.0
The first release on npm.
Full entry →