Skip to content

Releases

Korely Agents 1.0.26

Choose a tag to compare

@verdana86 verdana86 released this 15 Nov 09:18

1.0.25 with the old secret of a rotated key holding no admin, every
request that comes during the proxy's first name lookup waiting for it, and
a check of the public copy for lines that name what only Korely Cloud has.
From 1.0.25, the one thing to look at is the old secret, in "Before and
after upgrading" below; from 1.0.22, read the 1.0.25, 1.0.24 and 1.0.23
entries too.

Before and after upgrading

  • No migration and no new setting.
  • The old secret of a rotated key holds no admin. During a rotation's
    overlap, a script that still sends the old secret to an /admin route
    gets 403 previous_secret_has_no_admin and nothing changes; one that
    erases with it an end user to whom a key is confined alone (DELETE /v1/users/{id}/memories) gets 403 previous_secret_cannot_manage_keys
    and nothing is erased. Send the key's current secret, the one the rotation
    answered with. Every other call of the key's scopes goes on working with
    the old secret until it ends.

Security

  • A leaked key's holder no longer has admin with the old secret.
    During the overlap (old_key_expires_in_hours, 24 by default) the old
    secret of an admin key was the key in full: its holder could rotate the
    key again with no overlap, end the new secret its owner had just taken
    and receive the newest (the security review of 14 Nov 2026), delete a
    project, which moves its keys, or set the model provider's key, which
    outlives the overlap (the skeptic's review of 15 Nov 2026). Now a request
    let in with the old secret holds the key's scopes but admin, as a key
    Stripe rolls keeps working for API calls (https://docs.stripe.com/keys):
    every /admin route refuses it as it refuses a key without admin, so
    it makes no webhook either (those made before stay), and an erasure that
    would revoke a key as it goes is refused to it. A rotation let in with the
    current secret a moment before the owner's own committed reads the key
    again under its lock, and is refused too. The key's current secret, and a
    person signed in to the dashboard, do all of it as before. For a key that
    leaked, rotate with "old_key_expires_in_hours": 0, or revoke it
    (Rotating a key).
  • The proxy's requests during the process's first name lookup are the
    proxy's.
    Up to 1.0.25 only the request that started the first lookup of
    the names in FORWARDED_ALLOW_IPS waited for it: a request of the proxy
    that came while it ran was served with the proxy's address and http, as
    one relayed by a stranger (one login lock and one cap per network for
    every client relayed so, /health without its details), and the
    one-time [proxy] warning said, wrongly, that the proxy was not trusted
    (found on the Cloud's deploy of 14 Nov 2026). Now every request that
    comes before the first lookup has answered waits for it, until 2 s after
    it began; a lookup that fails is no answer, never an error of the
    requests waiting for it; and the warning is said, once the names have
    answered, of any peer seen meanwhile that they leave out.

What is different

  • docs/guide/operations.md, "Your own proxy in front", says again that a
    proxy of yours keeps the paths it logs, which carry end users' ids, and
    says PUBLIC_BASE_URL, which only .env.example said.

API

  • Every /admin route: 403 previous_secret_has_no_admin for a request
    let in with the old secret of a rotation; their 403 in the OpenAPI says it.
  • DELETE /v1/users/{id}/memories: 403 previous_secret_cannot_manage_keys
    for the old secret of a rotation when the erasure would revoke a key.
  • GET /v1/ping lists the scopes the secret sent holds: the old one's
    without admin.

Tools in the repository (nothing to do for an install)

  • The checks a public copy of this repository passes before it is
    published also fail on a line that names what only Korely Cloud has
    ,
    by the names the code the copy leaves out calls itself and the words a
    sentence says them with, a sense of its own a public file gives one of
    those words allowed phrase by phrase, with why. A test runs the same check
    at the gate, and the ten lines the security reading of 1.0.25's public
    copy found are rewritten.
  • Two lines of the 1.0.23 entry pointed to notes the public copy does not
    carry; they now say what they meant where they are.

Korely Agents 1.0.21

Choose a tag to compare

@verdana86 verdana86 released this 13 Nov 17:05

1.0.20 with three comments rewritten, for the public tree: 1.0.20 runs on the
Cloud but never reached the public repository, because the review of its
public copy found comments that named a Cloud feature not out yet. From
1.0.18, read the 1.0.20 and 1.0.19 entries below: that is the upgrade.

Before and after upgrading

  • Nothing to do. No migration, no setting, no change in what any route or
    tool answers.

What is different

  • Comments in the identity rule's code and tests describe the rule without
    naming a Cloud feature that is not released.

Security

  • No change to who can read or write what: only comments and test
    docstrings changed.

Korely Agents 1.0.18

Choose a tag to compare

@verdana86 verdana86 released this 12 Nov 17:06

Before and after upgrading

  • Nothing to do: no migration, no new setting, and no answer of the API,
    the MCP or the dashboard changes.

Security

  • Dependencies with published advisories, moved to the first fixed
    release
    (each one is at least two weeks old; nothing else moves):
    • certifi 2026.7.22 -> 2026.10.23, in the image: the CA bundle no longer
      trusts the emSign roots whose intermediate keys were compromised
      (GHSA-6q48-6c7w-9gw2). Korely's calls to model providers and webhooks
      verify TLS against this bundle.
    • pyasn1 0.6.4 -> 0.6.5, in the image: four quadratic-time decodings of
      crafted ASN.1 (GHSA-2qm8-x737-gmw5, GHSA-9m75-vc3g-4x39,
      GHSA-fm9c-5rcf-2q5p, GHSA-mm57-4v3p-rchx).
    • The dashboard: vite 6.4.3 -> 6.4.5 (the tool that builds it and its
      development server, not part of the built pages), and, through
      overrides in dashboard/package.json, follow-redirects 1.16.0 ->
      1.16.2 and re2js 2.8.6 -> 2.8.8.
    • scripts/diagrams (the tool that draws the README's diagrams, not part
      of an install), through overrides: nanoid 3.3.3 -> 3.3.18 and
      4.0.2 -> 5.1.16, lodash-es 4.17.21 -> 4.18.0, katex 0.16.47 ->
      0.18.2.
  • docs/DEPENDENCIES.md lists the new versions.

Tools in the repository (nothing to do for an install)

  • scripts/release.sh refuses a release whose CHANGELOG entry is not on
    top under its own heading
    (## <version>, with no (Unreleased)),
    before it changes anything. The script writes VERSION,
    pyproject.toml and install.sh, never the CHANGELOG's text, and
    v1.0.17 was tagged with ## 1.0.17 (Unreleased) on top; the 1.0.17
    entry's heading is now ## 1.0.17 on main (the tag stays as it is).
    Tested in tests/test_a_release_carries_its_own_heading.py.

Korely Agents 1.0.17

Choose a tag to compare

@verdana86 verdana86 released this 11 Nov 20:52

Before and after upgrading

  • Nothing to do: no migration, no new setting, and no answer of the API,
    the MCP or the dashboard changes.

What is different

  • The README says what is in this repository: the memory, projects,
    agent notices, the MCP server, the REST API and webhooks, the dashboard
    and the benchmark scripts. Korely Cloud runs the same code with sign-up,
    plans and payments and spending limits on top, which are not here.
  • Comments in the code and in the tests that named who reviewed or decided
    a change now give only the date of the review or of the decision.
  • The tests' made-up data no longer copies the rules of the team that
    builds Korely (a policy, a deploy procedure, a server), and their made-up
    addresses are on example.test and example.com. What the tests check
    is the same.
  • memory_core 6.8.1: the same change in two comments of the facts engine,
    text only. No code, prompt or printed line of the engine changes.

Security

  • No change: only the README, the CHANGELOG, comments, test data and the
    test suite change. The suite's test databases are dropped as before, only
    after waiting for the sessions that are leaving.

Tools in the repository (nothing to do for an install)

  • The test that failed on the CI's runs of 10 Nov 2026.
    test_a_workers_drop_ends_none_of_the_applications_connections
    (tests/test_the_suite_runs_in_several_processes.py) counted 2 sessions
    left open where it had left 1, on main and on v1.0.15, and passed on the
    Mac. drop_a_runs_database (tests/conftest.py) closed the pools of the
    application's engines and counted the database's sessions on the server at
    once; a connection the client has closed stays in pg_stat_activity until
    its backend process ends, a moment later. With the test and a Postgres of
    its own on two processors, as on the CI's runner, it failed in 4 runs of
    15 (3 times 1 for 0, once 2 for 1, the CI's message); in 15 more runs with
    a probe, the 12 sessions seen right after the close beside the one left
    open were all the application's two connections the drop had just closed.
    On the Mac's shared Postgres it passed. No thread or engine of an earlier
    test was involved: in the suite run in the CI's order on the Mac, up to
    this file, the threads left were idle pools, with no connection on the
    database. The drop now asks PostgreSQL first for a drop without FORCE,
    which waits up to 5 seconds for the backends still in the database and
    refuses (object_in_use) only when one stays; only then it counts the
    clients' sessions (backend_type client backend) and drops WITH (FORCE). A closed session is no longer counted as one left open, and
    FORCE no longer ends it, with the "terminating connection" line in the
    server's log that the closing was there to avoid. The count leaves out the
    other processes in the database: during the longer wait an autovacuum
    worker came in and was counted in 3 runs of 10 on two processors, and the
    parallel worker of a session's query is that session's. A drop with a
    session left open now takes 5 seconds, PostgreSQL's wait: the file's tests
    pay it three times, and a worker of a parallel run pays it at its end only
    when a test left a connection open. The test also runs with every closed
    connection reaching the server half a second late (closed_late), which
    fails the old count every time; a session left open in the middle of a
    parallel query counts once; and a drop without FORCE given up on for
    another reason (cancelled while it waits) is raised, never forced. On two
    processors the test now passed in 15 runs of 15, both cases; it has not
    run on the CI's runner yet.

Korely Agents 1.0.11

Choose a tag to compare

@verdana86 verdana86 released this 06 Nov 12:20

Before and after upgrading

  • On an install of your own, nothing to do and nothing changes: no
    migration, no new setting, no API answer of a different shape.

Security

  • On an install of your own, the webhooks' address check is the same: it
    gains a hook (webhook_destination_problem, core/extensions.py) that
    only the Cloud fills, and that does nothing here.

Korely Agents 1.0.10

Choose a tag to compare

@verdana86 verdana86 released this 06 Nov 00:51

Before and after upgrading

  • On an install of your own, nothing to do: no migration, no new setting,
    and nothing in the API changes shape. A client that reads the 401's words
    instead of its code invalid_key reads new words, and a key revoked while
    its request waits is refused where the request writes (both below).

What the store answers differently

  • The 401 for a key that does not authenticate names expiry. Up to
    1.0.9 every /v1 and /admin route answered an unknown, a revoked and
    an expired key alike with "Invalid or missing API key: no live key
    matches it (a revoked key stops at once); your keys are at …/keys", so a
    key past its date was told of a revocation nobody made. Now the same
    three get "Invalid or missing API key: no live key matches it (unknown,
    revoked or expired); your keys are at …/keys". They still get one answer,
    and the code is still invalid_key: branch on the code, not on the
    words. The MCP door (/mcp, /agent/mcp on the Cloud) is not one of
    them: it still answers its library's own 401, invalid_token
    "Authentication required".
  • A request that waits before it writes reads its key again.
    POST /admin/webhooks and POST /admin/webhooks/{id}/enable look the
    webhook's address up (up to 5 seconds) before they write, and
    POST /admin/keys makes the key after its other checks. Up to 1.0.9 they
    wrote with the key as it was when the request came in, so a key revoked
    during the lookup still made or switched on the webhook. Now each reads
    the key again in the transaction that writes: revoked or expired
    meanwhile, 401 invalid_key with the words above; without admin
    meanwhile, 403 forbidden. A request made with a dashboard session is
    not read again.

Security

  • The new words of the 401 change nothing about which key is let in: an
    unknown, a revoked and an expired key still get one answer and one code,
    so a caller cannot tell the three apart.
  • A key revoked while its request waited no longer writes with the rights
    it had: the three routes above read its admin again where they write,
    under a share lock of the account's row.

Korely Agents 1.0.9

Choose a tag to compare

@verdana86 verdana86 released this 05 Nov 17:53

Before and after upgrading

  • The public repository is now korely-ai/korely (it was
    korely-ai/korely-agent), and its image with it: the image its CI publishes
    is named after the repository, so the releases from this one on are
    ghcr.io/korely-ai/korely:<version>, and only there. install.sh, the
    compose files, .env.example, the README and the guides name the new
    addresses; install.sh holds the name once, as PUBLIC_REPO.

  • The old addresses still open, through GitHub. github.com/korely-ai/korely-agent
    sends you to github.com/korely-ai/korely, and the old installer line,
    curl -fsSL https://raw.githubusercontent.com/korely-ai/korely-agent/main/install.sh | sh,
    downloads the install.sh on the renamed repository's main, the same file
    as the new line,
    curl -fsSL https://raw.githubusercontent.com/korely-ai/korely/main/install.sh | sh.
    This lasts as long as GitHub keeps the old address pointing at the
    renamed repository: GitHub drops it if a repository is created again under
    the old name. Links and scripts of yours are better on the new address.

  • An install already made from the image keeps working as it is. Its
    .env names the image it runs,
    KORELY_IMAGE=ghcr.io/korely-ai/korely-agent:<version> (with the digest,
    when install.sh had one): that image is on your machine, and the releases
    up to 1.0.7 are still published under the old name. No later release is
    published there, so that line never brings you a newer one. Nothing to do
    now. It is the one line of such an install that names the repository: to
    run a release published under the new name, it must name that release
    there (put the release in place of X.Y.Z, in the install's folder):

    sed -i.bak 's#^KORELY_IMAGE=.*#KORELY_IMAGE=ghcr.io/korely-ai/korely:X.Y.Z#' .env

    The old line stays in .env.bak.

  • A git checkout needs nothing: git pull follows GitHub's redirect, and
    the image is built from the checkout. To name the new address yourself:
    git remote set-url origin https://github.com/korely-ai/korely.git.
    docs/guide/install.md now clones into a folder named korely-agent, the
    name it had before, so that a checkout keeps its Compose project and is
    never the same project as an install.sh install, which is named korely.

  • No migration and no new setting. The changes to the writers and to the
    model settings below need nothing before or after the upgrade.

  • Nothing to do for memory_core 6.3.0: no migration, no setting. A reader
    of the fact.invalidated webhook that matched on invalidated_by being null
    for a past statement now receives the replacing fact's id there.

  • Nothing to do for memory_core 6.2.1: no migration, no setting; one more
    INFO log line, with counts only.

  • Nothing to do for the PATCH with the same text: no migration, no
    setting. Facts closed that way before this release stay closed.

What the store answers differently

Two words used below. A region is where a model reads the memories of a
project to write its facts: eu (a provider in Europe) or global (a
provider anywhere). The drain is the process that takes the stored memories
from the queue and writes their facts; when it rests a region it does not
try that region again for that time.

  • GET /admin/models and PUT /admin/models/{provider} carry
    default_region
    , the region a project that never chose one writes in:
    the region of the writer in force, which is the provider saved last on the
    screen, else the one .env sets (null if neither, or if .env points to
    an address no known provider uses). It says what active already implied.
    The default region is read from the same writer as before; nothing is
    re-routed.

  • by_region of GET /admin/models now names the writer of .env too.
    Each entry has a new field source: env for a writer that comes from
    .env or from the Google key pasted in Settings, and screen for a
    provider chosen on the Models page. Up to 1.0.8 a region written by such
    a writer was null there, and Settings said "No provider: their memories
    wait" for the projects in it while they were written. For example, with
    Scaleway chosen on the Models page and GOOGLE_API_KEY in .env:

    "by_region": {
      "eu": {"provider": "scaleway", "label": "Scaleway", "model": "gpt-oss-120b", "source": "screen"},
      "global": {"provider": "google", "label": "Gemini", "model": "gemini-3.1-flash-lite", "source": "env"}
    }

    A writer of .env at an address no known provider uses belongs to no
    region, and no entry names it.

  • A writer saved after a refusal is tried at the drain's next pass.
    After a provider refuses, the drain rests that region (one minute, then
    longer, up to half an hour). Up to 1.0.8 a writer saved in place of the
    refused one (a new key, another model or provider) waited out the old
    one's rest, and /health went on listing the region under writers as
    refused. The rest now belongs to the writer that refused: saving another
    one, or forgetting it, ends the rest and the line on /health. Saving the
    same writer again keeps it.

  • Two saves of the model settings at the same moment no longer fail.
    Saving two providers at once, or saving while forgetting, could answer
    500 (a duplicate or stale settings row) or keep only one of the two
    choices. Each change of the provider key and of the writers now waits for
    the one before it to commit.

  • A fact a past statement closes now says which fact replaced it
    (memory_core 6.3.0). "Northwind costs 55 now, it used to cost 50": the 50 is
    closed where it was said, and before it had no invalidated_by, though the
    judge's closings always had one. Now invalidated_by on the 50 is the 55,
    GET /v1/facts shows it, and when the 50 was an open fact before, the
    fact.invalidated webhook carries it as invalidated_by where it was null
    (a fact born closed was never announced, and still is not). It is set only when the same write holds
    exactly one open fact about the same subject and relation with another value;
    with none or with several it stays empty. Nothing else closes and nothing is
    judged. Facts closed this way before this release keep an empty pointer.
    Forgetting the memory that said the past statement now gives the old value
    back, as it already did for a closing by the judge: "Northwind is on the
    Basic plan", then "moved to the Pro plan, it used to be on the Basic plan";
    forget the second, and the first still says Basic, so Basic is true again,
    as a new open fact. Before this release nothing was left open in that case.
    The fact given back is of tense current (a fact closed by a past statement
    carries past, and the given-back copy kept it while open; that was
    corrected in the same change).

  • A memory whose every declared fact the quality filter dropped now says
    so in the log
    (memory_core 6.2.1). The filter drops a fact whose name is
    not in the memory's text. Before, that was a DEBUG line per fact, so such a
    memory ended done with no fact and nothing above DEBUG to tell it from a
    text that holds none. Now one INFO line gives the count, never a word of the
    text or of the fact. Which facts are kept is unchanged, and so is what the
    store answers; memories already written with no fact are not re-read (the
    command manage reextract --empty finds them). Limit: the memory is still
    filed done, and a sentence with no subject ("She now lives in Lisbon") whose
    name is in an earlier memory is still dropped; measuring that needs the
    model and is not done.

  • PATCH /v1/memories/{id} with the text the memory already has no longer
    reads it again.
    Before, it queued the extraction once more, and if the
    model answered no fact that second time, every fact the memory stated was
    closed and the memory was marked done, though nothing had changed:
    "Anna works in Milan" and "Anna has two children" were gone after a PATCH
    that repeated the text. Now the reply is the memory as it is, with
    facts: [] and no model is called, so no cost either. A text that changed
    is read again as ever and retires the facts it no longer states: editing a
    value out of a memory still removes it. Limit: a text that changed by a
    comma and whose second read answered no fact still closes the facts, because
    the store cannot tell it from an edit that took the value away. Measured on
    the tests of this change, not on LoCoMo or LongMemEval.

Security

  • No change in what the installer trusts: it downloads only the release
    files of the repository it names (now korely-ai/korely), over HTTPS,
    checks them against the sha256 it carries, and stops when the answer comes
    from another address.
  • The changes to the writers and to the model settings change nothing about
    who can read or change them.
  • A PATCH with the same text changes nothing about who can read or write
    what; its one log line carries the memory id and no text.
  • The INFO line of memory_core 6.2.1 carries a count and never a word of the
    text or of a fact.
  • The pointer of memory_core 6.3.0 is an id of a fact of the same memory, same
    account and same project: nothing crosses a tenant.