Repository navigation
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 | deletesibyl 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 $valueandLET $parentinside a block, closure, orFORbody. 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
SCHEMAFULLtables. Each schema bootstrap (graph, content, auth) now readsINFO FOR DBand sendsALTER DATABASE COMPATIBILITY 3only when the server reports a different level. A 3.x server reports none and never receives theALTER. - Missing-table reads.
schema_versionrecord reads on a namespace that never defined the table go through one helper usingSELECT .. 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 $parentinside the caller's block and bindparentas 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
entityandsource_statesthrough a blockLET: 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_clockno longer freezes the asyncio clock. The fixture patchedreadiness.time.monotonic, which is the stdlib function, so every caller in the process read5000.0and the event loop's timers could never fire. A not-ready firstINFO FOR INDEXpoll during graph bootstrap would hang the test forever while network I/O kept the loop looking alive.embedding_lane_readinessnow importsmonotonicby 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
kindandsponsor_user_idtousers. 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
dateobjects now fails fast. If any integration code bound adatetime.dateto a query, it raised an unroutable error frame before and raisesTypeErrornow. Bind adatetimeinstead. - Custom SDK entry points need the shim. Anything opening its own
surrealdbconnection outside Sibyl's clients should callinstall_compact_cbor()before its first query; the repo's own entry points, includingtools/perf/surreal_vector_index.pyand both testconftest.pyfiles, 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.mdfor the permission rule anddocs/cli/bot.mdfor 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.13Remote 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 loginHomebrew
brew install hyperb1iss/tap/sibyl
sibyl upArch Linux (AUR)
paru -S sibyl
sibyl upHeadless server
curl -fsSL https://raw.githubusercontent.com/hyperb1iss/sibyl/main/install.sh | sh -s -- --version 1.4.13 --no-openKubernetes (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.13Artifacts
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.