Repository navigation
v1.4.8
Release Notes v1.4.8
Released: 2026-10-09
This release adds a Team page: a per-organization view of who captured, created, and completed what, scoped strictly to what the viewer is already allowed to read. Supporting it required server-owned completion attribution on tasks (completed_at, completed_by) and a new activity rollup that excludes derived, projected, and migrated rows so one act counts exactly once.
🌟 Highlights
✨ Team activity page at /team
New route under apps/web/src/app/(main)/team/ showing a people grid and a newest-first activity feed grouped by day. Each teammate card carries role badge, per-kind counts (captures, tasks created, tasks completed, decisions, notes, procedures), relative last-active time, and an activity bar. Clicking a card narrows the feed to that person without changing the counts.
✨ GET /api/activity/team endpoint
New router in apps/api/src/sibyl/api/routes/activity.py returning TeamActivityResponse: resolved window, people with counts, and up to 100 recent events with a truncated flag. Query params are window (24h | 7d | 30d, default 7d), repeatable project_id, and optional actor_id. Requesting a project the caller cannot read returns 403 project_scope_denied; empty project strings return 422.
🔒 Visibility is the existing read policy, not a new grant
The rollup in activity_rollup.py resolves a ReaderScope once, pushes a private-scope clause into the SurrealQL window scans, then re-checks every row with entity_matches_list_filters() and raw_capture_visible_to_reader(). Private rows surface only to their own principal; API-key readers stay bound to their memory_grants. No count or feed item comes from a row the caller could not already list.
✨ Server-owned task completion attribution
New sibyl_core/tasks/completion.py centralizes transition rules. Any path to done (REST complete, PATCH /tasks/{id}, MCP update_task, manage) now stamps completed_at and completed_by from the authenticated caller. Re-completion is a no-op, reopening clears both fields, archiving preserves them. modified_by is likewise stamped server-side on every status edit.
♻️ One act counts once
Derived entity types (topic, passage, community, document), projection categories, reflection surfaces, and migration-carried rows are dropped before classification. mark_raw_memory_projected_entity() links an MCP capture to the graph row it became, mirroring the REST sidecar behavior, so a remember that also creates an entity is not counted twice.
🤖 Activity API
activity_rollup.pyruns three reads concurrently:list_org_members(), the graph window scan, andlist_raw_memories_created_between(). Row filtering and capture lifecycle checks run off the event loop.- Windows are half-open
[since, until)intervals resolved in UTC; creation events usecreated_at, completion events use the server-ownedcompleted_at. - The
recentfeed sorts by(at DESC, kind, id)for stable ordering and caps atRECENT_LIMIT = 100, settingtruncatedwhen more existed. - Each event carries a navigable
href(/tasks/{id},/epics/{id},/projects?id=,/memory/captures?id=) with URL-safe encoding. - A task that reached done without a
completed_atrecord stays uncounted rather than being credited to a guess. - Response includes both
project_id(set only for a single-project request) andproject_ids(every requested UUID, in request order).
🖥️ Web
- Components under
apps/web/src/components/team/:TeamActivityView,PersonCard,PersonAvatar,ActivityFeed,WindowSwitcher,ProjectScopeSelect. windowand project scope persist in the URL (?window=,?projects=); the person filter is session-local and resets on navigation.useTeamActivity()inlib/hooks/activity.tsdedupes and sorts project IDs for a stable query key, useskeepPreviousDataso the prior answer stays visible during a scope switch, and drops the placeholder after a failure so a retry shows a clean load instead of stale data.- First visit with no URL scope defaults to the first active project rather than "all", avoiding an unbounded historical scan.
- Error states distinguish
project_scope_denied(offers "Show every project") from generic failures (offers Retry), and a partial refresh failure keeps the last good answer behind a warning banner. /teamadded tolib/constants/navigation.tsin the overview section alongside Dashboard.
🔒 Permissions and policy
WORK_ITEM_ACTOR_METADATA_KEYS(completed_at,completed_by,modified_by) is stripped from client payloads inmemory_policy.py, so a request cannot reassign work to another member or backdate a completion.- New
work_item_actor_metadata()lifts those stored values out before a policy strip and reattaches them afterward, keeping server attribution intact through backup restore and archive import.
⚡ Persistence
list_raw_memories_created_between()scansidx_raw_captures_org_createdover the window, selecting only the fields the rollup needs (no body, no vectors) and excluding sidecars and projected captures at the query level.- Graph window scans use
idx_entity_updatedand drop derived entity types inside the query to keep the scan small.
✅ Tests and fixtures
- New:
apps/api/tests/test_activity_rollup.py,apps/api/tests/test_routes_team_activity.py,packages/python/sibyl-core/tests/test_task_completion.py,apps/web/src/app/(main)/team/team-content.test.tsx,apps/web/src/lib/hooks/activity.test.tsx. team-activity.fixtures.tsprovides a shared dataset (six members across roles, fourteen events across three projects) reused by tests and theteam-activity-view.stories.tsxStorybook scenarios: active, truncated, quiet, loading, and failed.
Breaking changes
mirrors_source_statusrequiressync=true. The new field on the task update request is intended forsibyl migrate to-teamalone: it marks a status write that mirrors a source task so no completion is recorded. Passing it on a queued (async) update returns422, because the completion decision has to happen under the entity lock that holds the read.- Completion fields are no longer client-controlled.
completed_at,completed_by, andmodified_byare stamped from the authenticated caller on every status write. Payloads setting these values are stripped. - Reopening a task clears its completion record. A done to open transition now resets
completed_atandcompleted_by. Integrations that read a historical finisher off a reopened task will seenull. Archiving a done task still preserves both fields.
Upgrade notes
- Pure patch bump across the deployment surface:
VERSION,charts/sibyl,charts/surrealdb,docker-compose.quickstart.yml, the Ansible role defaults, and bothpyproject.tomlpins move1.4.7to1.4.8. No config shape changed. - Docker:
sibyl docker upgrade --tag 1.4.8. Helm: setbackend.image.tagandfrontend.image.tagto1.4.8. Ansible:sibyl_versiondefaults to1.4.8. - Tasks completed before this release have no
completed_atrecord and will not appear as completions in the Team feed or counts. Attribution builds from new completions forward. - If you script task status writes, drop any
completed_at/completed_by/modified_byfields from payloads; they are ignored. - No migration is required. The
idx_raw_captures_org_createdandidx_entity_updatedindexes carry the activity window scans.
Install
Local server
curl -fsSL https://raw.githubusercontent.com/hyperb1iss/sibyl/main/install.sh | sh -s -- --version 1.4.8Remote CLI
curl -fsSL https://raw.githubusercontent.com/hyperb1iss/sibyl/main/install.sh | sh -s -- --remote --version 1.4.8
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.8 --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.8Artifacts
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.