Skip to content

v1.4.10

Latest

Choose a tag to compare

@github-actions github-actions released this 12 Oct 18:01
· 1 commit to main since this release

Release Notes v1.4.10

Released: 2026-10-12

This release makes Sibyl safe to run as more than one replica. MCP now serves stateless streamable HTTP, OAuth and device-code flows keep their state in SurrealDB instead of process memory, settings changes propagate across replicas over a Redis invalidation bus, and validation receipts plus backup archives can live in S3 instead of a single-writer volume. Two performance changes move graph-build CPU work off the event loop and tune GC for long-running API processes.

🌟 Highlights

✨ Stateless MCP over HTTP

mcp_http_app() in apps/api/src/sibyl/main.py now serves the MCP streamable HTTP transport with stateless_http=True, dropping per-process session state entirely. /mcp accepts POST for RPC and lets OPTIONS through for CORS preflight; GET, DELETE and other methods get a 405 with an Allow: POST header instead of parking an idle stream. Any replica can answer any request, so MCP clients no longer need sticky routing. Write tools (add, remember, reflect, manage, and synthesis draft saves) are wrapped with the new @uninterruptible decorator in apps/api/src/sibyl/mcp_tools/completion.py so a client disconnect mid-write does not leave a half-applied change.

✨ Replica-safe OAuth, revocation and settings

MCP OAuth authorization state moved out of per-process dictionaries into the oauth_authorization_requests table (apps/api/src/sibyl/persistence/surreal/auth_runtime/oauth_authorization.py), transitioning pending → authenticated → code_issued through conditional updates. Request keys and codes are stored as SHA-256 hashes only. Device-code exchange, approval and denial use the same conditional-update pattern in device_authorization.py, closing the window where two replicas could redeem one code. Session revocations and settings changes broadcast over a new Redis invalidation bus (apps/api/src/sibyl/cache_invalidation.py, apps/api/src/sibyl/coordination/invalidation.py), with readiness reporting via apps/api/src/sibyl/api/readiness.py.

✨ Receipts and backup archives in S3

New backends in sibyl-core (backends/s3_client.py, backends/archive_store.py, backends/s3_archive_store.py, backends/s3_receipt_store.py) let validation receipts and organization backup archives live in object storage instead of a ReadWriteMany volume. Writes are create-only (If-None-Match), checksummed with sha256, and multipart for large archives; reads use range requests. apps/api/src/sibyl/backup_archives.py picks the S3 or directory store from configuration. Backup cleanup is now org-scoped, so one organization's cleanup job can no longer see or remove another's archives.

⚡️ Graph builds off the event loop

CPU-bound pure Python accounted for about 87% of cold graph-page build time. The new sibyl_core/services/graph_compute.py moves proof, verdicts, enumeration, narrowing, rendering and community detection onto a two-worker build pool, with a separate two-worker request pool for search, traverse and retrieval steps (steps under 64 rows stay inline). The worst event-loop stall on a 6,000-entity graph fell from about 1.1 s to about 0.44 s, and the search step's p99 wait to start during two concurrent builds fell from 0.9–1.4 s to about 55 ms. Output is byte-identical to the previous implementation, and rendering is now deterministic across processes (sorted walks, id tie-breaks), so replicas show the same clusters for the same graph.

⚡️ GC tuning for long-running API processes

apps/api/src/sibyl/gc_tuning.py freezes startup objects (about 522K on a real server) and raises GC thresholds to (10_000, 10, 100). Full collections per cold 6,000-entity graph build dropped from 6.7 (about 3.1 s total, with individual pauses of 137–650 ms) to 0.14 (about 60 ms total), and wall time for that build went from 18–19 s to 15–16 s. RSS stayed within about 15 MB across an 8-minute mixed-load run.

🤖 Multi-Replica Coordination

  • Rate-limit storage now defaults to Redis database 4 with hard 0.5 s socket connect/operation bounds and in-memory fallback (apps/api/src/sibyl/api/rate_limit.py), so a Redis blip degrades to per-replica limits instead of hanging requests.
  • The settings encryption key is generated once at startup and shared across replicas, verified by HMAC fingerprint (apps/api/src/sibyl/services/settings_key.py, persistence/surreal/system_settings.py).
  • Startup recovery and manual source sync consult the job broker before resetting a crawl source (coordination/broker.py:live_crawl_job_id(), api/routes/admin.py, api/routes/crawler.py). A sync against a live crawl returns 409 and recovery reports a still_running count instead of stomping an in-flight job.
  • The Redis coordination readiness check now verifies the Valkey connection, the arq queue and worker heartbeat (coordination/_redis/broker.py).
  • sibyld worker gained --require-redis and --check. With --require-redis the worker refuses to start on the local coordination backend; --check is wired as the compose healthcheck.
  • Session revocation re-announcements are debounced to at most one per 30 s window using a new user_sessions.revocation_announced_at field and a conditional claim (auth_runtime/_common.py).

🐛 Fixes

  • SurrealDB 3.2.x compatibility: queries now project every ORDER BY field in their SELECT list, and compound-index IN cleanups are pinned to avoid SurrealDB issue #7534 silently dropping mutations (auth_runtime/projects.py, graph_community_clusters.py, tools/admin.py). Covered by the new apps/api/tests/test_surreal_live_query_shapes.py.
  • Locked-setting error detail survives the error handler: locked_by_env() in apps/api/src/sibyl/api/errors.py is now shared by the settings and LLM routes and carries fields and deployment_owned through to the client, so the admin UI can mark env-owned settings read-only with the specific field names (apps/web/src/lib/api/errors.ts, settings/admin/ai/page.tsx).
  • Rate-limit Redis URL from numeric ports: the Helm configmap applies toString to numeric port and db values before rendering the URL, which previously produced an unusable connection string.
  • SurrealDB export coverage: exports now discover namespaces and databases at runtime instead of a static list, write a per-run directory with a manifest.json (sibyl-surrealdb-export, version 2), validate row counts before and after, and clean up stale incomplete runs older than a day.
  • Transient export failures retry: export and restore-drill jobs classify deterministic versus transient failures and gained backoffLimit and activeDeadlineSeconds, so a flaky HTTP call no longer burns the nightly run.
  • Version stamps keep Markdown tables aligned: tools/release/sync_versions.py re-pads table cells when a version changes length (1.4.9 → 1.4.10), which previously broke docs:lint at release time.
  • HTTP error responses pass exc.headers through (apps/api/src/sibyl/api/app.py), so headers such as Retry-After and Allow reach the client.

🔧 Backup and Restore Operations

  • The restore drill decrypts per-file, verifies the manifest sha256, rejects exports older than restoreDrill.maxExportAgeHours (default 36), and validates per-table row counts against restoreDrill.rowDrift.percent / .rows tolerances before reporting per-database results.
  • The scratch SurrealDB instance runs as a native sidecar, and podFailurePolicy ignores pods killed by DisruptionTarget so node drains do not count as drill failures.
  • Encryption hooks read $SIBYL_EXPORT_FILE and write $SIBYL_EXPORT_ENCRYPTED_FILE rather than encrypting in place.
  • New jobDefaults.http.connectTimeoutSeconds and jobDefaults.http.maxTimeSeconds bound every curl call in the export and drill jobs.

🔨 Deployment and Packaging

  • Ansible collection: infra/ansible is now packaged as the hyperb1iss.sibyl collection (galaxy.yml, meta/runtime.yml, infra/ansible/README.md), installable from git with a release-tag pin so the role and image versions stay in sync.
  • Frontend service account: the chart creates a dedicated frontend.serviceAccount with automountServiceAccountToken: false by default, so frontend pods carry no cloud role and cannot reach the receipt or archive stores.
  • Chart value validation: the configmap template rejects S3 URLs that are malformed, receipts and archives sharing a prefix, and both url and existingClaim being set for the same store.
  • Compose: docker-compose.prod.yml defaults SIBYL_COORDINATION_BACKEND to local with an opt-in --profile redis, adds explicit SIBYL_REDIS_HOST/SIBYL_REDIS_PORT, generates a shared SIBYL_SETTINGS_KEY in the receipts-init step, runs the worker with --require-redis, and adds a sibyld worker --check healthcheck.
  • CI: a new .github/actions/start-valkey action mirrors the Valkey image to dodge Docker Hub token failures and waits for readiness, letting the suite exercise the Redis coordination backend.

⚠️ Breaking Changes

  • MCP /mcp no longer accepts GET or DELETE. Clients that opened a session stream with GET or revoked one with DELETE receive 405 Method Not Allowed with Allow: POST. POST and OPTIONS are unaffected. Update any client pinned to the session-based transport.
  • backend.service.sessionAffinity and backend.service.sessionAffinityConfig are removed from the sibyl chart and its service template. Remove them from your values file; stateless MCP no longer needs sticky routing.
  • backend.validationReceipts.existingClaim moved under the validationReceipts block, which now also accepts url for S3. Exactly one of url or existingClaim must be set.
  • The sibyl-surrealdb chart requires Kubernetes 1.29+. charts/surrealdb/Chart.yaml now declares kubeVersion: ">=1.29.0-0" for native sidecars and podFailurePolicy. The main sibyl chart declares no kubeVersion constraint and is unaffected.
  • The worker refuses the local coordination backend when started with --require-redis, which is now the default in docker-compose.prod.yml.
  • Settings owned by the deployment environment return 409 with detail.error = "locked_by_env" plus fields and deployment_owned lists instead of being silently accepted. Clients parsing the old error shape need updating.
  • In-flight OAuth and device-code flows do not survive the upgrade, since flow state moved from process memory to SurrealDB. Users mid-authorization need to restart the flow.
  • SurrealDB exports changed format. Old flat export files are not readable by the new per-run manifest layout. Retention and sync scripts that assume flat files need to handle run directories.

📝 Upgrade Notes

  1. Upgrade the sibyl and sibyl-surrealdb charts to 1.4.10. Confirm your cluster is on Kubernetes 1.29 or newer before upgrading sibyl-surrealdb.
  2. Remove backend.service.sessionAffinity and backend.service.sessionAffinityConfig from your values file, and move existingClaim under backend.validationReceipts.
  3. To move receipts and archives to S3, set backend.validationReceipts.url and backend.backupArchives.url (or SIBYL_VALIDATION_RECEIPT_URL / SIBYL_BACKUP_ARCHIVE_URL) using s3://bucket[/prefix][?region=name]. Receipts and archives must not share a prefix. Grant PutObject (with If-None-Match and multipart), GetObject (range), DeleteObject, ListObjectsV2 and AbortMultipartUpload. Install sibyl-core[s3] for the boto3/botocore dependency.
  4. SIBYL_BACKUP_DIR now defaults to ~/.sibyl/backups instead of ./backups. Set it explicitly if you relied on the old relative path. SIBYL_BACKUP_FREE_SPACE_RESERVE_MB (default 512) reserves headroom for the directory store.
  5. For multi-replica deployments, point every replica and worker at the same Redis/Valkey instance and distribute one SIBYL_SETTINGS_KEY. Replicas with mismatched keys fail the HMAC fingerprint check at startup.
  6. Drop any static databases list from your sibyl-surrealdb values; namespaces and databases are discovered at runtime. Review your encryption hook to confirm it reads $SIBYL_EXPORT_FILE and writes $SIBYL_EXPORT_ENCRYPTED_FILE, and set restoreDrill.maxExportAgeHours plus restoreDrill.rowDrift tolerances for your data volume.
  7. Ansible users should install the collection instead of checking out the role directory:
    # requirements.yml
    collections:
      - name: https://github.com/hyperb1iss/sibyl.git#/infra/ansible
        type: git
        version: v1.4.10
    then run ansible-galaxy collection install -r requirements.yml and apply hyperb1iss.sibyl.sibyl.
  8. Expect a latency trade-off during graph builds: with CPU work on worker pools, other requests see a higher median from GIL contention but a lower tail. Remaining event-loop stalls are the SurrealDB SDK's frame decoding and the /graph/clusters Louvain run, both tracked as follow-ups.

Install

Local server

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

Remote CLI

curl -fsSL https://raw.githubusercontent.com/hyperb1iss/sibyl/main/install.sh | sh -s -- --remote --version 1.4.10
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.10 --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.10

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.