Skip to content

v1.4.13

Latest

Choose a tag to compare

@github-actions github-actions released this 05 Nov 07:43
· 7 commits to main since this release

Release Notes v1.4.13

Released: 2026-11-05

Agents get their own identity in this release. Bot accounts are organization members sponsored by a person, capped at that sponsor's live permissions on every request, so an agent no longer has to run on an engineer's API key. Alongside that, the SurrealDB backend picks up the wire-format and query-planner fixes needed for SurrealDB 4.0, all of them valid on 3.2.4 and several of them faster there.

Highlights

Bot accounts capped by their sponsor

A bot is a users row with kind = 'service' and a sponsor_user_id (auth migration 12), a member of exactly one organization that authenticates only with API keys. The cap is applied where every project role is decided, in _effective_project_role_from_records: a bot holds the lowest of its own grant, its sponsor's current role on that project, and contributor. Org-visibility defaults count as a grant, so a bot reads org-visible projects like any member, capped the same way. Because the cap is read per request, demoting a sponsor demotes their bot on its next call.

Sponsors who leave take their bots offline

The resolver reads the sponsor's live org role next to the bot's own and raises SuspendedPrincipalError, surfaced as 403 bot_suspended with remediation text pointing at sponsor transfer. Every key the bot holds is refused until an org admin transfers it. No path signs a bot in interactively: create_session refuses a service principal, both local password checks skip one, and a bot never gains an email or password through a profile edit.

SurrealDB 4.0 wire compatibility for datetimes and durations

SDK 2.0.0 sends every datetime as CBOR tag 0 (RFC 3339 text) and every Duration as tag 13, both of which SurrealDB 4 refuses, so any write binding a timestamp failed. The new compact_cbor shim replaces surrealdb.data.cbor.dumps with an encoder that maps datetime, the SDK's Datetime wrapper, and Duration to tag 12 and tag 14 [seconds, nanos] pairs, read by every server since 1.5. Values that 3.x previously answered with an unroutable error frame (which stranded the socket) now fail locally before anything is sent.

Index-served reads on both server versions

SurrealDB 4.0 compiles a whole statement before running it and only builds an index candidate from a literal or client param, so operands bound by a block LET, a FOR variable, or a closure argument fell back to table scans: an 8-item closure lookup went from 8.7 ms on 3.2.4 to 3.4 s on 4.0. Hot read sites now bind every index operand as a client param and split IN lists in Python through the new in_clumps.clumped helper.

Bot Platform

REST surface

/bots is a new router in apps/api/src/sibyl/api/routes/bots.py, reachable only by a person holding an unbound credential:

  • GET /bots, POST /bots, GET /bots/{bot_id}, DELETE /bots/{bot_id}
  • POST /bots/{bot_id}/transfer (org owners and admins only)
  • POST /bots/{bot_id}/projects, DELETE /bots/{bot_id}/projects/{project_id}
  • GET /bots/{bot_id}/keys, POST /bots/{bot_id}/keys, POST /bots/{bot_id}/keys/{api_key_id}/revoke

Any owner, admin, or member can create a bot they sponsor; a viewer cannot, and the request body has no sponsor field, so nobody else can be named. Sponsors grant their own bot project access up to min(their effective role there, contributor), which is the one way to add a project member without being that project's owner or maintainer, and it reaches only bots you sponsor.

Escalation paths closed

A bot cannot climb by any other road. Org and project member routes refuse admin, owner, maintainer, and project-owner roles for a bot target. A bot joins no team and no second organization, and cannot create a project (REST or MCP), an organization, a session through org switch, an API key, or another bot. Bot management also refuses a credential narrowed to projects or memory spaces, since a key minted through one could otherwise reach past its own narrowing.

Deletion and purge

delete_bot revokes the bot's unrevoked api_keys, deletes its project_members and organization_members rows, soft-deletes its private raw captures with a purge_after stamp, and marks the user deleted. The bot.delete audit event records bot_user_id, name, api_keys_revoked, private_memories_scheduled, and purge_after.

Attribution

A bot's writes already carry its own principal, so this release adds the labels where people read them. Org member listings now return each member's kind and, for a bot, its sponsor_user_id; the Team page carries those into people and actor_kind into recent events. The admin audit listing and its export read each actor's kind from the users table at query time, so a row written by a bot reads as a bot's and a row a sponsor wrote about their bot reads as the person's. In the web app, person-card.tsx renders a Bot badge beside the role and activity-feed.tsx repeats it beside the name.

CLI

New commands in apps/cli/src/sibyl_cli/bot.py drive the whole surface:

  • sibyl bot create | list | show | grant | revoke | transfer | delete
  • sibyl bot key create | list | revoke

Every command that takes <bot> accepts a bot ID or the exact name of a bot you can see, and grant takes contributor or viewer. Most commands accept --json for scripting; sibyl bot key create has no flag and always emits JSON, since its secret prints exactly once. Bot writes never enter the offline replay buffer, matching /auth/ behavior: a replayed key mint would create a key whose secret nobody ever sees. sibyl bot list shows a bot whose sponsor left as suspended.

SurrealDB Compatibility and Performance

CBOR encoding

compact_cbor.install_compact_cbor() is idempotent and installed wherever Sibyl opens an SDK connection (dedicated_client.py, native_transaction.py, archive_operator_native_capture.py, tools/perf/surreal_vector_index.py), plus both test conftest.py files for raw test connections. Values are validated locally before they reach the socket: naive datetimes are coerced to UTC (matching prior SDK behavior), date objects raise TypeError because SurrealDB has no date type, and ValueError is raised for malformed SDK Datetime text (including a time carrying no zone) and for negative durations. Oversized years, which datetime() reports as OverflowError, now surface as the same cannot read ... as a datetime error as every other bad input. Datetime text keeps its nanosecond precision.

Query shapes valid on 3.2.4 and 4.0

  • Protected bindings renamed. SurrealDB 4.0 refuses LET $value and LET $parent inside a block, closure, or FOR body. Three statements were renamed: the archive graph compiler's source-guard loop, the native-capture describe closure, and the projected-entity replace guard. On 3.2.4 the describe closure produces byte-identical descriptors and fingerprints under the new name, so stored archive fingerprints do not move.
  • Compatibility level pinned. A fresh 4.0 datastore creates databases at compatibility level 4, which refuses the widen/backfill/narrow field migrations Sibyl runs on populated SCHEMAFULL tables. Each schema bootstrap (graph, content, auth) now reads INFO FOR DB and sends ALTER DATABASE COMPATIBILITY 3 only when the server reports a different level. A 3.x server reports none and never receives the ALTER.
  • Missing-table reads. schema_version record reads on a namespace that never defined the table go through one helper using SELECT .. FROM (IF record::exists(id) THEN [id] ELSE [] END), which answers no rows on both versions where 4.0 errors on a bare record id.
  • Validation promotion guards stop setting LET $parent inside the caller's block and bind parent as a query variable like every other producer of the same snapshot SQL.

Measured improvements on 3.2.4

Two shapes already scanned on 3.2.4 and are now index-served:

  • Link snapshot reads of entity and source_states through a block LET: 328 ms to 3.5 ms
  • EntityManager.update's expected-revision disjunction, which turned the entity write into a table scan: 331 ms to 2.8 ms. The guard is now spelled only when a revision is given.

in_clumps.clumped splits an IN list into client params of at most 32 values (IN_CLUMP), de-duplicating first so a value split across clumps cannot answer twice, and wraps one statement copy per clump in array::flatten([...]). It covers the raw-capture bulk guards on every remember, the link snapshot and link write, relationship endpoint reads, and the exact-key search lane, whose CONTAINSANY moves into an inner select because 4.0 drops the element index beside ORDER BY and LIMIT.

Fixes

  • lane_clock no longer freezes the asyncio clock. The fixture patched readiness.time.monotonic, which is the stdlib function, so every caller in the process read 5000.0 and the event loop's timers could never fire. A not-ready first INFO FOR INDEX poll during graph bootstrap would hang the test forever while network I/O kept the loop looking alive. embedding_lane_readiness now imports monotonic by name and both fixtures patch that module-local name. The hang reproduced on SDK 2 against 3.2.4, so it was never a 4.0 or SDK 3 bug.

Testing

New live-runtime coverage drives bot accounts end to end on SurrealDB 3.x (apps/api/tests/test_surreal_live_bot_accounts.py): grants capped at the sponsor, a sponsor demoted then the bot on its next request, a sponsor removed then every bot key refused, transfer and delete, and each role route refusing a bot target. apps/e2e/tests/cli/test_bots.py walks the CLI path: create a bot, grant it contributor on a project whose default scope is project, issue a key, capture under that key, and confirm the Team page credits the bot and labels it. Unit suites add test_compact_cbor.py, test_in_clumps.py, test_bot_sponsor_cap.py, test_routes_bots.py, and CLI test_bot.py.

Upgrade Notes

  • Auth migration 12 adds kind and sponsor_user_id to users. It runs during the normal auth schema bootstrap; existing accounts are treated as people.
  • Existing databases are unaffected by the compatibility pin. Only a server reporting a level other than 3 receives ALTER DATABASE COMPATIBILITY 3, so 3.x deployments see no change. A fresh SurrealDB 4.0 datastore is held at level 3 so Sibyl's field migrations stay parseable.
  • Binding date objects now fails fast. If any integration code bound a datetime.date to a query, it raised an unroutable error frame before and raises TypeError now. Bind a datetime instead.
  • Custom SDK entry points need the shim. Anything opening its own surrealdb connection outside Sibyl's clients should call install_compact_cbor() before its first query; the repo's own entry points, including tools/perf/surreal_vector_index.py and both test conftest.py files, already do.
  • Replace agent keys with bot keys. Agents currently running on a person's API key should be migrated: create a bot, grant it project access, and issue it a key. Review docs/users/bot-accounts.md for the permission rule and docs/cli/bot.md for the command reference.
  • Chart and deployment versions move to 1.4.13 across charts/sibyl, charts/surrealdb, docker-compose.quickstart.yml, and the Ansible role defaults.

Install

Local server

curl -fsSL https://raw.githubusercontent.com/hyperb1iss/sibyl/main/install.sh | sh -s -- --version 1.4.13

Remote CLI

curl -fsSL https://raw.githubusercontent.com/hyperb1iss/sibyl/main/install.sh | sh -s -- --remote --version 1.4.13
sibyl init --remote https://sibyl.example.com
sibyl auth login

Homebrew

brew install hyperb1iss/tap/sibyl
sibyl up

Arch Linux (AUR)

paru -S sibyl
sibyl up

Headless server

curl -fsSL https://raw.githubusercontent.com/hyperb1iss/sibyl/main/install.sh | sh -s -- --version 1.4.13 --no-open

Kubernetes (Helm)

helm repo add sibyl https://raw.githubusercontent.com/hyperb1iss/sibyl/gh-pages
helm repo update sibyl
helm upgrade --install sibyl sibyl/sibyl --version 1.4.13

Artifacts

This release includes Python wheels and sdists, the generated
Homebrew formula, the generated AUR PKGBUILD, Helm charts, Docker
SBOMs, aggregate dual-registry cosign receipts, and a SHA256 checksum
manifest.