Skip to content
OpenScribe brand logo Docs

Living knowledge and entity evolution design

Updated 2026-11-10. This document describes the implemented people and project backend and the interactive workspace and the proposed company, topic and temporal graph extensions. The status table distinguishes tested source from installed and deployed acceptance.

Implementation status summary

AreaCurrent status in repositoryVerification boundary
Local active-meeting session bundle (manifest.json, streams/*.pcm, streams/*.f32, streams/*.packets.jsonl, streams/*.wav, transcript.jsonl, notes.jsonl, attachments.jsonl under ~/Library/Application Support/OpenScribe/recordings/<sessionId>/)Implemented in core store, coordinator, and CompanionView.swift (LocalRecordingStore.swift, RecorderCoordinator.swift, CompanionView.swift)Verified in Swift unit tests (OpenScribeCoreTests.swift) and --self-test (SelfTest.swift). The actual native picker saved the supplied test file locally. Note text sheets and file Quick Look were verified in the app and isolated SwiftUI checks. The picker bypass was removed. Backend attachment receipt, drag-and-drop and final installed acceptance remain pending.
Account-scoped people_profiles, meeting_series (owner + calendar_account + calendar_id + recurring_event_id), tags, meeting_notes, and meeting_attachments (kind = "screenshot" | "file" | "audio_chunk" | "replay_bundle")Implemented (server/schema.go, server/meetings.go, MeetingDetailView.tsx)Verified in Go and web unit/integration tests (go test ./..., npm test) and local browser verification of ended-meeting notes, URLs, images, arbitrary files, and attachment download readback.
Hosted OAuth 2.1 MCP server at /api/openscribe/mcp (GET, POST, DELETE) and LLM/vision engine (tagging, profile_enrichment, qa, summary, action_items)Implemented (server/intelligence, server/mcpauth, server/mcpbridge)Verified in Go unit and integration tests (go test ./...). Public deployment to scribe.akyonlabs.com and live browser ChatGPT/Claude MCP connector onboarding remain pending.
Bounded people and projects (knowledge_entities, knowledge_details, knowledge_detail_evidence, knowledge_tasks, knowledge_tag_mentions, knowledge_suggestions), extraction jobs and source-backed projectionsImplemented in backend source (server/knowledge.go, server/knowledge_service.go, server/knowledge_routes.go, server/intelligence/knowledge.go)All five Go packages pass tests and the race detector. Tests cover migration, owner isolation, project-specific roles, arbitrary links, manual precedence, omission, late final segments, source deletion and canonical task roundtrips. A separate isolated PocketBase fixture supplied real APIs for web acceptance and was stopped after verification. The shared development server is migrated with all existing record IDs preserved. Real isolated API and browser checks verified person/project editing, canonical tasks and source evidence navigation. The integrated graph workspace passed isolated browser acceptance. Public deployment remains pending.
Shared People, Projects and Tags workspace with graph/list selection and a document or connection inspectorImplemented in web source (EntityWorkspaceView.tsx, EntityGraphPane.tsx)Eighteen web tests, typecheck and production build pass. Isolated browser checks cover typed search, tag editing, connection selection and evidence navigation at desktop and mobile widths. Meeting nodes open the existing meeting view alongside the graph. Citations select the transcript and highlight the source segment. Reload, browser back and 390px graph navigation were verified. Public deployment remains pending.
Company and concept entities, a general temporal graph and retrieval benchmarksProposedThe later architecture sections remain design references. They do not describe the current person/project schema or claim measured query latency.

Approved people and project page direction

On 2026-11-10, the user authorized implementation of bounded document-like pages for people and projects alongside ongoing capture verification. These pages use formatted editing controls, not raw Markdown. The implemented backend contract is in server/API.md, Section 4.9. Real isolated API and browser checks verify document editing, canonical tasks, arbitrary links and source evidence navigation.

The page shows populated details and an Add detail action. Supported detail types are notes, roles, responsibilities, links, locations, skills, deadlines and connections. Dates and related people, projects or tags belong to each detail where relevant. Links have editable labels and can point to any website, without assuming that its content was fetched.

Roles and responsibilities can differ by project. Tasks share one editable record across meeting, person and project views. Tags retain a name and description and can reference transcript ranges. Each extracted detail keeps its source meeting and evidence. New supported details can appear automatically after a meeting, while conflicts with manual edits remain reviewable suggestions.

Graph formation and selection

The graph reads the same owner-scoped PocketBase records as the document pages. People, projects, tags and meetings form nodes. Structured relations, project responsibilities, tasks, meeting tags and source evidence supply their connections. A connection inspector shows its role, validity dates and meeting evidence. Display-name similarity alone does not create a connection.

The browser draws a bounded neighborhood from those records. No graph database or additional server process is required. The deployed backend remains one Go binary with PocketBase and authentication, serving the Vite and TypeScript frontend. The later temporal schema is a separate proposal.

1. Primary-source comparison of reference implementations

Repository metadata, license texts, and implementation files were inspected via the GitHub API (gh api) on 2026-11-09 without cloning repositories (bounded snapshot saved in ignored analysis/living-knowledge-review/primary_sources_2026-11-09.json). Rather than relying on ephemeral star counts, our architecture draws on four primary codebases and evaluates why external graph servers or Python sidecars are unnecessary for OpenScribe.

Core architectural references

Project and verified primary source filesCategory & license statusVerified mechanics to adopt vs constraints for OpenScribe
getzep/graphiti (edges.py, dedupe_edges.py, temporal_edge_utils.py)Python library. LICENSE reports Apache-2.0 (OSI open-source license).Adopt: Bi-temporal separation between transaction/observation time (created_at, expired_at) and validity time (valid_at, invalid_at), conservative contradiction rules in dedupe_edges.py (never invalidate a prior fact from omission or recency alone, and never treat task-level updates as contradicting role/employment facts), and future-deadline normalization in temporal_edge_utils.py. Constraint: Requires Python plus an external graph DB (Neo4j, FalkorDB, Neptune, or Kuzu) and multiple LLM calls per episode.
fastrepl/anarlog (redirected from fastrepl/hyprnote, canonical_data_model.sql, contact_ops.rs, session_participant_ops.rs)Native/desktop meeting notepad. Root LICENSE is MIT, while enterprise/ carries a separate commercial LICENSE.enterprise.Adopt: Relational SQLite schema (STRICT tables for organizations, humans, sessions, session_participants, action_items, tags, and polymorphic entity_mentions with source_type, source_id, target_type, target_id), non-empty case-insensitive email matching (primary_email + additional_emails), and explicit excluded participant tombstones in session_participant_ops.rs so manual removals are never re-added by auto-sync.
twentyhq/twenty (LICENSE, person.workspace-entity.ts)CRM web workspace. Root LICENSE combines AGPL-3.0 with /* @license Enterprise */ commercial exclusions and MIT client packages.Adopt: Entity detail page hierarchy (person.workspace-entity.ts linking calendarEventParticipants, noteTargets, taskTargets, attachments, and timelineActivities). Demonstrates that users need a clean current summary, structured attributes, linked files/links, and an activity timeline first, with a small neighborhood graph as an optional view.
topoteretes/cognee (DataPoint.py)Python knowledge-engine SDK. LICENSE reports Apache-2.0.Adopt: Explicit separation between deterministic identity fields (identity_fields hashed for idempotent upsert) and selective searchable fields (index_fields). Constraint: Ships a separate Python runtime across relational, vector, and graph stores.

Secondary references and storage systems evaluated

  • microsoft/graphrag (MIT): The repository README.md explicitly notes that the project is largely in maintenance mode (not accepting new features or PRs) and targets batch corpus indexing rather than incremental per-meeting state updates.
  • HKUDS/LightRAG (MIT): operate.py tracks chunk IDs on nodes and edges for document deletion, but merges entities by normalized display name alone (normalize_entity_name), which violates OpenScribe's speaker and person attribution rules.
  • mem0ai/mem0 (Apache-2.0 SDK): The repository README.md notes that published benchmark numbers reflect proprietary managed-platform optimizations not included in the open-source SDK, and mem0/memory/notices.py defines TEMPORAL_FEATURE_ERROR_MESSAGES (timestamp and reference_date parameters unsupported in the OSS SDK) and DECAY_FEATURE_ERROR_MESSAGE.
  • logseq/logseq (AGPL-3.0) and anyproto/anytype-ts (LICENSE.md uses the Any Source Available License 1.0, which is source-available rather than OSI open source): Useful UX references for typed objects and backlinks on local SQLite, though Anytype's source-available license restricts commercial redistribution.
  • Dedicated graph databases (neo4j/neo4j, FalkorDB/FalkorDB, kuzudb/kuzu):
    • kuzudb/kuzu (MIT) is archived on GitHub ("archived": true in gh api repos/kuzudb/kuzu) and requires CGO in Go.
    • FalkorDB/FalkorDB uses the Server Side Public License v1 (LICENSE, SSPL v1, source-available rather than OSI open source) and requires running a Redis/Valkey module server.
    • neo4j/neo4j Community Edition (GPL-3.0) requires a separate JVM daemon, separate credentials, and a separate backup volume from PocketBase, and Community Edition limits each server instance to a single user database (system plus one default database) without online backups.

2. Proposed storage recommendation and benchmark plan

Recommendation: retain embedded PocketBase + SQLite WAL (modernc.org/sqlite)

OpenScribe's backend is a single Go binary (github.com/pocketbase/pocketbase with pure-Go modernc.org/sqlite) deployed with one persistent directory (pb_data). For the living-knowledge system, we recommend keeping all entity, claim, evidence, and resource-link tables inside the existing PocketBase SQLite database rather than adding Neo4j, FalkorDB, Kuzu, or a Python sidecar:

  1. Deployment and backup reuse: Self-hosters and scribe.akyonlabs.com keep a single container and a single pb_data backup volume without managing a second database daemon or CGO build chain.
  2. Transactional tenant isolation and erasure: Every query filters by owner = userID inside the same SQLite database that stores meetings, transcript_segments, meeting_notes, and meeting_attachments. Deleting a meeting or note updates or removes dependent evidence rows in one ACID transaction.
  3. Graph as a deterministic projection over evidence-backed claims: OpenScribe only needs 1-to-2 hop ego-network queries around a selected person, company, project, or concept (knowledge_entities joined to knowledge_facts), plus SQLite FTS5 lexical search.

Proposed benchmark before making latency claims

No SQLite graph query latency numbers have been measured yet. Before claiming specific millisecond targets or adding any external index:

  • Add a repeatable Go benchmark (BenchmarkLivingKnowledgeQueries in server/intelligence) seeding an illustrative single-owner dataset (for example 1,000 meetings, 10,000 knowledge_facts, 25,000 knowledge_fact_evidence rows, and 100,000 transcript_segments) and measure p50/p95 execution time for:
    1. Current entity projection (status = "active" claims plus manual overrides and attached resources),
    2. 1-hop and 2-hop neighborhood expansion with composite (owner, subject_entity, status) and (owner, object_entity, status) indexes, and
    3. Cascade source deletion and entity timeline rebuild when a meeting is deleted.
  • Keep a narrow Go KnowledgeStore interface in server/intelligence so an embedded disk-backed index (such as LanceDB) or external adapter is considered only if measured benchmarks on real hardware justify it.
mermaid
flowchart LR
    subgraph Capture["Meeting & Artifact Ingestion"]
        M["Committed Meeting\n(Transcript, Notes, Screenshots, Links)"]
        MAC["Mac Companion Composer\n(Confirmed Active Meeting Only)"]
        WEB["Web Meeting Page\n(During & After Meeting)"]
    end

    subgraph Extraction["Proposed Idempotent Post-Commit Extraction"]
        RES["Scoped Entity Resolver\n(Verified Email / Proven Stable ID / Domain / Slug)"]
        EXT["Claim & Evidence Extractor\n(knowledge_facts + knowledge_fact_evidence)"]
        CON["Conservative Contradiction Check\n(Explicit Replacement Only, Never Omission)"]
    end

    subgraph Storage["Proposed PocketBase / SQLite Collections"]
        ENT["knowledge_entities\n(person, company, project, concept)"]
        FAC["knowledge_facts\n(Canonical Claim, valid_from, valid_to, status)"]
        EVI["knowledge_fact_evidence\n(Per-Meeting/Note/Attachment Support)"]
        LNK["entity_resource_links\n(github, google_drive, slack, webpage + ingested_status)"]
        LOG["knowledge_revisions\n(Undo / Merge / Split Audit Log)"]
    end

    subgraph Projection["Current View & Retrieval"]
        CUR["Current Entity Brief & Active Claims\n(Manual Overrides > Active Claims)"]
        HIST["Retained History & Superseded Claims\n(Re-evaluated from Remaining Evidence on Delete)"]
        MCP["Bounded Hybrid Search + 1-2 Hop Graph\n(Web UI & /api/openscribe/mcp)"]
    end

    MAC --> M
    WEB --> M
    M --> RES --> EXT --> CON
    CON --> ENT
    CON --> FAC
    CON --> EVI
    CON --> LNK
    CON --> LOG
    ENT --> CUR
    FAC --> CUR
    EVI --> FAC
    FAC --> HIST
    LNK --> CUR
    CUR --> MCP

3. Proposed data model (PocketBase / SQLite collections)

All proposed collections enforce @request.auth.id != '' && owner = @request.auth.id for ListRule and ViewRule, with direct client collection writes disabled (CreateRule = nil, UpdateRule = nil, DeleteRule = nil) so all mutations pass through validated /api/openscribe/* handlers.

3.1 knowledge_entities (living nodes)

Unifies living nodes across four canonical kinds while linking cleanly to existing people_profiles, meeting_series, and tags:

  • id (text PK)
  • owner (relation -> users, required, cascade delete)
  • kind (select, required): "person", "company", "project", "concept"
  • canonical_name (text, required, max 300): inferred or initial display name
  • manual_name (text, max 300): authoritative user override when non-empty
  • normalized_key (text, indexed): deterministic lookup key used only where allowed by the strict resolution rules in Section 4.3 (for example verified email for a person, verified domain for a company, or owner-scoped canonical slug for a project/concept)
  • person_profile (relation -> people_profiles, optional, max 1): 1:1 link when kind == "person"
  • tag (relation -> tags, optional, max 1): optional link when kind == "concept" corresponds to a user-defined tag
  • status (select, required): "active", "archived", "merged", "provisional"
  • merged_into (relation -> knowledge_entities, optional): points to target entity if merged
  • do_not_merge_with (json, []string): explicit negative constraints set when a user splits or rejects a merge
  • current_brief (text, max 20000): synthesized current state summary built strictly from active claims and manual overrides
  • manual_brief (text, max 20000): authoritative user override for the entity brief
  • current_attributes (json): projected key-value snapshot of active claims (role, company, country, status, owner_person_id, next_deadline)
  • manual_attributes (json): authoritative user-edited attributes that always take precedence over current_attributes
  • topic_weight (number, float): relevance score with exponential recency decay plus frequency and manual pin boost
  • pinned (bool): user pin so important entities/topics stay surfaced regardless of decay
  • last_observed_at (date): timestamp of the most recent remaining evidence or manual edit referencing this entity

3.2 knowledge_facts (canonical bi-temporal claims and relations)

Separates the canonical claim (subject_entity + predicate + object_entity/value_key) from the one or more evidence records (knowledge_fact_evidence) that support it across meetings:

  • id (text PK)
  • owner (relation -> users, required, cascade delete)
  • subject_entity (relation -> knowledge_entities, required, cascade delete)
  • predicate (select/text, required):
    • Person predicates: "works_at", "has_title", "located_in", "owns_project", "contributes_to", "reports_to", "focuses_on_concept"
    • Company predicates: "partners_with", "uses_project", "tracks_concept"
    • Project predicates: "has_status", "has_deadline", "owned_by_company", "led_by_person", "relates_to_concept", "blocked_by"
    • Concept/Topic predicates: "co_occurs_with", "applies_to_project"
  • object_entity (relation -> knowledge_entities, optional): populated when the claim connects two entities
  • value_text (text, max 5000): human-readable claim or scalar value (such as "Principal Researcher" or "Ship OpenShip self-host docs")
  • value_json (json): structured scalar attributes (such as {"due_at": "2026-11-16T17:00:00Z", "deadline_state": "upcoming"})
  • cardinality (select, required): "single_valued" (current job title, primary company, project status) vs "multi_valued" (project contributors, active concepts)
  • status (select, required):
    • "active": part of the current entity projection
    • "superseded": replaced by a newer contradicting claim or manual edit (excluded from the default current view, retained in history while backed by evidence)
    • "expired": time-bounded state or deadline whose window closed with explicit evidence
    • "provisional": candidate claim awaiting user confirmation (for example when conflicting claims occur in the same meeting or when an automated claim differs from a manual override)
    • "retracted": user explicitly rejected or hid this extracted claim
  • is_manual (bool): true when created or locked by the user (never overwritten by automated extraction)
  • Temporal validity dimensions:
    • first_observed_at (date, required): earliest supporting observed_at across remaining evidence rows (or manual creation time)
    • last_observed_at (date, required): latest supporting observed_at across remaining evidence rows
    • valid_from (date, required): when the claim became true (normalized so future promises/deadlines use first_observed_at as valid_from and store the target date in value_json.due_at)
    • valid_to (date, optional): when the claim stopped being true
    • superseded_at (date, optional): when a newer claim or manual edit superseded this row
    • superseded_by (relation -> knowledge_facts, optional): pointer to the replacing claim

3.3 knowledge_fact_evidence (one-to-many per-source provenance for each claim)

Because the same fact (for example "Alice leads Project Atlas") is often reaffirmed across multiple recurring meetings, notes, or screenshots, storing scalar source_meeting on knowledge_facts would either duplicate the fact row on every meeting or lose secondary support when one meeting is deleted. Instead, each supporting occurrence is stored as a row in knowledge_fact_evidence:

  • id (text PK)
  • owner (relation -> users, required, cascade delete)
  • fact (relation -> knowledge_facts, required, cascade delete)
  • source_meeting (relation -> meetings, optional, cascade delete)
  • source_series (relation -> meeting_series, optional)
  • source_note (relation -> meeting_notes, optional, cascade delete)
  • source_attachment (relation -> meeting_attachments, optional, cascade delete)
  • observed_at (date, required): exact timestamp when this evidence was recorded (meeting.started_at + evidence_start_ms or note/attachment creation time)
  • evidence_segment_ids (json, []string): exact transcript_segments IDs in source_meeting
  • evidence_start_ms / evidence_end_ms (number, int): meeting timeline offsets
  • evidence_speaker (text): speaker snapshot and attribution mode (one_mapped_source, local_mic, mixed_sources, unmapped)
  • evidence_quote (text, max 1000): verbatim transcript or note quote
  • source_url (text, max 2000)
  • confidence (number, float): extraction confidence ($0.0$ to $1.0$)
  • extraction_run_id (text): idempotency key (meeting_id + content_hash + model_id)

Connects external resources and meeting attachments to living people, companies, projects, and concepts while preserving OpenScribe's ingested_status truth boundary:

  • id (text PK)
  • owner (relation -> users, required, cascade delete)
  • entity (relation -> knowledge_entities, required, cascade delete)
  • source_meeting (relation -> meetings, optional, cascade delete)
  • source_note (relation -> meeting_notes, optional, cascade delete)
  • source_attachment (relation -> meeting_attachments, optional, cascade delete)
  • resource_type (select, required): "github_repo", "github_issue", "github_pr", "google_drive_file", "google_drive_folder", "slack_thread", "slack_channel", "webpage", "uploaded_file", "screenshot"
  • url (text, max 2000): normalized URL
  • external_key (text, max 500): canonical identifier (github:org/repo#42, drive:file_id, slack:team/channel/thread_ts)
  • title (text, max 500): user label, link text, or authorized fetched title
  • provenance (select, required): "manual", "calendar_description", "meeting_chat", "screenshot_ocr", "mac_companion_drop", "transcript_mention"
  • ingested_status (select, required): "url_only" vs "authorized_content" (never claim a private GitHub repo, Drive doc, or Slack thread was read when only its URL was pasted or seen in chat/OCR)
  • status (select, required): "active", "archived"
  • first_seen_at / last_seen_at (date)

3.5 knowledge_revisions (reversible audit log for merge, split, override, and erasure)

  • id (text PK)
  • owner (relation -> users, required, cascade delete)
  • action (select, required): "extraction_commit", "manual_override", "fact_supersede", "fact_retract", "entity_merge", "entity_split", "source_cascade_delete"
  • target_entity_id (text)
  • secondary_entity_id (text, optional)
  • before_snapshot (json): serialized state of affected entity rows, claims, evidence rows, and participant mappings prior to the operation
  • after_snapshot (json): resulting state
  • created (autodate)

4. Lifecycle rules: temporal updates, contradictions, identity safety, and erasure

4.1 Current view vs retained history (why omission never invalidates a fact)

  1. Current projection: An entity's current view displays manual_attributes overlaid on knowledge_facts where status == "active". When a claim is superseded, its status changes to "superseded", valid_to and superseded_at are set, and superseded_by points to the replacing claim. Outdated details immediately leave the current entity brief and active claim list.
  2. History preservation: Superseded claims remain in knowledge_facts with their supporting knowledge_fact_evidence rows and are inspectable under the entity's History & timeline tab with their original meeting links, timestamps, and speaker quotes.
  3. Omission is not contradiction: If Meeting 1 establishes that "Alice leads Project Atlas" and Meeting 2 with Alice discusses only "Q4 hiring" without mentioning Project Atlas, Alice leads Project Atlas stays active. A claim is marked superseded only when:
    • The predicate is single_valued (such as has_title, primary works_at, or has_status) and a newer verified claim asserts a different value for the same (subject_entity, predicate) with observed_at >= prior.last_observed_at, or
    • A multi_valued claim has explicit termination or replacement evidence in the transcript or notes (such as "Alice handed off Project Atlas to Bob" or "We canceled the Redis migration"), or
    • The user manually edits or retracts the claim.
  4. Authoritative manual overrides and conflicting claims:
    • Manual user claims (is_manual = true) and manual_attributes have highest authority. If a later meeting extracts a claim that contradicts a manual fact, the extractor saves the extracted claim with status = "provisional" for user review rather than overwriting the manual value.
    • If two speakers make incompatible claims within the same meeting without resolution, or if an unmapped / mixed_sources stream contradicts a one_mapped_source speaker, the new claim is recorded as status = "provisional".

4.2 Deadlines and time-bounded claims

  • When a meeting on 2026-11-09 states "We will deploy scribe.akyonlabs.com by 2026-11-12", the claim's valid_from is 2026-11-09 (when the commitment was made), valid_to remains empty while open, and value_json records {"due_at": "2026-11-12", "deadline_state": "upcoming"}.
  • Passing the due_at wall-clock timestamp does not silently delete the deadline or mark it completed. If now > due_at and no subsequent meeting or manual action provides completion, rescheduling, or cancellation evidence, the UI renders the deadline as past_due (unconfirmed) and moves it out of active commitments only when superseded by actual evidence (completed, rescheduled, or canceled) or user action.

4.3 Strict scoped entity resolution (preventing false merges)

  1. People (kind = "person"):
    • Never merge by display name alone and never infer speaker identity from a calendar invite. Two participants named "Alex" remain separate people_profiles and separate knowledge_entities.
    • Ephemeral per-meeting device IDs never auto-link across meetings: In Google Meet, Zoom, and Teams, transport source IDs (SSRC/CSRC) and per-call participant/device IDs (participant_device_id, such as spaces/.../devices/136) are ephemeral and scoped to a single meeting_id (idx_meeting_participants_unique on (meeting, platform, participant_device_id)). Do not auto-merge a person across meetings using calendar_account + platform + participant_device_id.
    • Allowed automatic cross-meeting resolution for a person:
      • An exact, non-empty verified email match (owner + lower(email)), or
      • A platform account identifier that has been separately proven stable across meetings (stable_id in people_profiles.platform_ids), or
      • Explicit user merge confirmation.
    • Participants with only a display name and a meeting-scoped participant_device_id create a meeting-scoped or provisional person entity and surface a non-intrusive merge suggestion in the UI.
  2. Companies (kind = "company"):
    • Never auto-merge companies by bare display name alone, because short or common company names (such as "Delta", "Linear", or " Mercury") can refer to distinct organizations across different contexts.
    • Automatic resolution for a company requires a verified normalized domain (akyonlabs.com from a verified participant email domain or explicit company URL), a canonical external key (github:org), or an existing company linked to the same recurring meeting_series. When only a bare spoken company name is available without a domain or series link, create or propose a provisional company link unless the user confirms it.
  3. Projects and Concepts (kind = "project", "concept"):
    • Match by (owner, kind, normalized_key) where normalized_key is a canonical project slug or linked tags / meeting_series.projects entry, always respecting do_not_merge_with tombstones from any prior user split.
  4. Evolving keyword and topic weights:
    • User-defined tags in tags are never deleted automatically.
    • Concept nodes (kind = "concept", such as health, openship, webapp, ai, research) compute topic_weight over supporting evidence rows: $$W(c, t) = \sum_{e \in \text{Evidence}(c)} w_e \cdot 2^{-(t - t_e)/\tau}$$ with half-life $\tau = 30\text{ days}$ and manual tag weight $w_e = 2.0$ vs transcript/summary mention weight $1.0$.

4.4 Reversible merge, split, and undo

  • Merge (POST /api/openscribe/entities/{id}/merge): Records a knowledge_revisions snapshot of both entities and their claims/evidence/links, repoints knowledge_facts, entity_resource_links, and meeting_participants to the surviving entity, sets merged_into = target_id on the source entity, and recomputes the target entity's temporal timeline from the combined evidence.
  • Split / Undo (POST /api/openscribe/entities/revisions/{revId}/undo or /split): Restores the exact claim, evidence, and participant assignments from before_snapshot (or moves user-selected meeting evidence to a split entity), records both entity IDs in do_not_merge_with so future extraction runs never re-merge them, and rebuilds both entities' projections.

4.5 Source deletion, revocation, and evidence-based timeline rebuild

When a user deletes a meeting, a note, or an attachment, or revokes a connected source:

  1. Delete per-source evidence rows: All knowledge_fact_evidence and entity_resource_links rows referencing the deleted source_meeting, source_note, or source_attachment cascade-delete immediately.
  2. Prune claims with zero remaining support: For every affected knowledge_facts row:
    • If is_manual == false and the claim now has zero remaining knowledge_fact_evidence rows, the knowledge_facts row itself is deleted.
    • If the claim still has one or more remaining knowledge_fact_evidence rows (for example, it was stated in both Meeting 1 and Meeting 2, and only Meeting 2 was deleted), the knowledge_facts row is retained, and its first_observed_at and last_observed_at timestamps are recomputed from the remaining evidence rows.
  3. Rebuild the entity timeline without unconditional resurrection:
    • Deleting a newer claim that had superseded an older claim (old_fact.superseded_by == deleted_fact.id) must not unconditionally flip old_fact.status back to "active".
    • Instead, OpenScribe re-evaluates all remaining non-retracted claims (is_manual == true or having $\ge 1$ remaining knowledge_fact_evidence row) for that (subject_entity, predicate) in chronological order of their remaining observed_at evidence:
      • If a manual override exists for that predicate, the manual claim remains authoritative (status = "active").
      • Otherwise, if another remaining verified claim with later evidence (last_observed_at) still supersedes old_fact, old_fact remains "superseded" and its superseded_by pointer updates to that remaining newer claim.
      • If old_fact itself has an explicit valid_to in the past or explicit termination evidence in a remaining meeting, it transitions to "expired" rather than being asserted as current truth.
      • Only if old_fact still has valid remaining evidence, no remaining newer superseding claim, and no expired validity bound does it return to status = "active" with superseded_by = nil and superseded_at = nil.
  4. Recompute entity projection: Rebuild current_attributes and current_brief strictly from the resulting status == "active" claims and manual_attributes in the same SQLite transaction.

5. Privacy, security, and untrusted-content guardrails

  1. Strict owner isolation: Every entity, claim, evidence row, resource link, and revision carries owner (users.id). No entity resolution or graph traversal ever crosses owner boundaries.
  2. Prohibited financial and sensitive data: Extraction prompts and backend validators (CheckNoFinancialData) forbid extracting, inferring, or storing income, salary, pay stubs, tax returns, bank statements, paid employment-contract rates, or medical diagnoses.
  3. URL-only vs authorized content ingestion: When a GitHub issue/PR, Google Drive file, Slack thread, or webpage URL appears in a calendar invite, meeting chat, screenshot OCR, or Mac companion paste, entity_resource_links.ingested_status is set to "url_only". OpenScribe never claims private document contents were read unless fetched through an explicitly authorized connector (ingested_status = "authorized_content").
  4. Prompt-injection isolation: Transcript segments, meeting chat links, calendar descriptions, and screenshot OCR text are treated as untrusted data blocks during extraction. They can produce candidate claims with citations, but can never invoke tools, alter admin model settings, or overwrite manual user fields.

6. Mac active-meeting composer vs web living-knowledge workspace

6.1 Mac companion composer: confirmed ongoing meeting only

The native macOS companion (native/Sources/OpenScribeCore/RecorderCoordinator.swift and native/Sources/OpenScribeApp/CompanionView.swift) captures context during a confirmed ongoing meeting, while all post-meeting additions and edits happen on the web:

  1. Confirmed ongoing meeting gating (audio recording not required):
    • RecorderCoordinator.canAddMeetingContext is true whenever a meeting is confirmed active (meetingState == .joined, meetingState == .recording, isRecording == true, or a monitored native meeting app is in state == "confirmed_ui"), even when audio recording or live ASR is not running.
    • CompanionView.swift binds its composer enabled state to coordinator.canAddMeetingContext (replacing the older activeBackendMeetingId + currentUser gate).
  2. Local-first persistence in ~/Library/Application Support/OpenScribe/recordings/<sessionId>/:
    • During an active meeting, the user can type comments, paste or type website URLs, and drag-and-drop or pick images and arbitrary files.
    • Every accepted item is written atomically to LocalRecordingStore (manifest.json, notes.jsonl, attachments.jsonl, and attachments/<id>-<filename>) with its originating (sessionId, backendMeetingId, capturedAtMs) before the UI reports it as saved.
    • When signed in and reachable, RecorderCoordinator uploads queued notes (POST /api/openscribe/meetings/{id}/notes) and files (POST /api/openscribe/meetings/{id}/attachments with kind = "file" or "screenshot") asynchronously.
  3. Strict post-meeting lock and late-upload safety:
    • As soon as the meeting transitions to .ended or .idle, canAddMeetingContext becomes false, disabling new comment, link, and file submissions in the Mac companion and showing Open dashboard. Each Past meeting opens its exact web page at <PUBLIC_URL>/?meeting=<meetingId>.
    • Any upload that was already accepted while Meeting A was active captures originatingSessionId by value so it finishes syncing to Meeting A even after Meeting A ends, and never attaches to a subsequent Meeting B.
    • Core original-session binding and post-meeting rejection are verified in --self-test (SelfTest.swift). The actual picker saved the supplied test file locally, and text sheets and Quick Look were verified. Backend attachment receipt and drag-and-drop acceptance remain pending. The picker bypass was removed.

6.2 Web workspace and /api/openscribe/mcp

  • Meeting detail page (/?meeting=<id>): Supports adding and editing notes, occurrence or series links, and file/screenshot attachments both during and after a meeting.
  • Proposed living entity views: Extend EntityWorkspaceView.tsx to show an entity's Current Snapshot (manual_attributes + status == "active" claims with clickable [Meeting · mm:ss] evidence chips and attached GitHub/Drive/Slack/web resources), a History & Superseded tab, merge/split/undo controls, and an optional bounded 1-hop neighborhood graph.
  • MCP integration (/api/openscribe/mcp): Expose current entity state and temporal claim history with verbatim evidence citations through the existing authenticated OAuth 2.1 MCP server in server/mcpbridge.