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
| Area | Current status in repository | Verification 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 projections | Implemented 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 inspector | Implemented 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 benchmarks | Proposed | The 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 files | Category & license status | Verified 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.0SDK): 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 definesTEMPORAL_FEATURE_ERROR_MESSAGES(timestampandreference_dateparameters unsupported in the OSS SDK) andDECAY_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": trueingh api repos/kuzudb/kuzu) and requires CGO in Go.FalkorDB/FalkorDBuses 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/neo4jCommunity 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 (systemplus 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:
- Deployment and backup reuse: Self-hosters and
scribe.akyonlabs.comkeep a single container and a singlepb_databackup volume without managing a second database daemon or CGO build chain. - Transactional tenant isolation and erasure: Every query filters by
owner = userIDinside the same SQLite database that storesmeetings,transcript_segments,meeting_notes, andmeeting_attachments. Deleting a meeting or note updates or removes dependent evidence rows in one ACID transaction. - 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_entitiesjoined toknowledge_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 (
BenchmarkLivingKnowledgeQueriesinserver/intelligence) seeding an illustrative single-owner dataset (for example 1,000 meetings, 10,000knowledge_facts, 25,000knowledge_fact_evidencerows, and 100,000transcript_segments) and measure p50/p95 execution time for:- Current entity projection (
status = "active"claims plus manual overrides and attached resources), - 1-hop and 2-hop neighborhood expansion with composite
(owner, subject_entity, status)and(owner, object_entity, status)indexes, and - Cascade source deletion and entity timeline rebuild when a meeting is deleted.
- Current entity projection (
- Keep a narrow Go
KnowledgeStoreinterface inserver/intelligenceso an embedded disk-backed index (such as LanceDB) or external adapter is considered only if measured benchmarks on real hardware justify it.
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 --> MCP3. 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 namemanual_name(text, max 300): authoritative user override when non-emptynormalized_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 whenkind == "person"tag(relation ->tags, optional, max 1): optional link whenkind == "concept"corresponds to a user-defined tagstatus(select, required):"active","archived","merged","provisional"merged_into(relation ->knowledge_entities, optional): points to target entity if mergeddo_not_merge_with(json,[]string): explicit negative constraints set when a user splits or rejects a mergecurrent_brief(text, max 20000): synthesized current state summary built strictly from active claims and manual overridesmanual_brief(text, max 20000): authoritative user override for the entity briefcurrent_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 overcurrent_attributestopic_weight(number, float): relevance score with exponential recency decay plus frequency and manual pin boostpinned(bool): user pin so important entities/topics stay surfaced regardless of decaylast_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"
- Person predicates:
object_entity(relation ->knowledge_entities, optional): populated when the claim connects two entitiesvalue_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):truewhen created or locked by the user (never overwritten by automated extraction)- Temporal validity dimensions:
first_observed_at(date, required): earliest supportingobserved_atacross remaining evidence rows (or manual creation time)last_observed_at(date, required): latest supportingobserved_atacross remaining evidence rowsvalid_from(date, required): when the claim became true (normalized so future promises/deadlines usefirst_observed_atasvalid_fromand store the target date invalue_json.due_at)valid_to(date, optional): when the claim stopped being truesuperseded_at(date, optional): when a newer claim or manual edit superseded this rowsuperseded_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_msor note/attachment creation time)evidence_segment_ids(json,[]string): exacttranscript_segmentsIDs insource_meetingevidence_start_ms/evidence_end_ms(number, int): meeting timeline offsetsevidence_speaker(text): speaker snapshot and attribution mode (one_mapped_source,local_mic,mixed_sources,unmapped)evidence_quote(text, max 1000): verbatim transcript or note quotesource_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)
3.4 entity_resource_links (GitHub, Google Drive, Slack, and web attachments on entities)
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 URLexternal_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 titleprovenance(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 operationafter_snapshot(json): resulting statecreated(autodate)
4. Lifecycle rules: temporal updates, contradictions, identity safety, and erasure
4.1 Current view vs retained history (why omission never invalidates a fact)
- Current projection: An entity's current view displays
manual_attributesoverlaid onknowledge_factswherestatus == "active". When a claim is superseded, itsstatuschanges to"superseded",valid_toandsuperseded_atare set, andsuperseded_bypoints to the replacing claim. Outdated details immediately leave the current entity brief and active claim list. - History preservation: Superseded claims remain in
knowledge_factswith their supportingknowledge_fact_evidencerows and are inspectable under the entity's History & timeline tab with their original meeting links, timestamps, and speaker quotes. - 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 Atlasstaysactive. A claim is markedsupersededonly when:- The predicate is
single_valued(such ashas_title, primaryworks_at, orhas_status) and a newer verified claim asserts a different value for the same(subject_entity, predicate)withobserved_at >= prior.last_observed_at, or - A
multi_valuedclaim 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.
- The predicate is
- Authoritative manual overrides and conflicting claims:
- Manual user claims (
is_manual = true) andmanual_attributeshave highest authority. If a later meeting extracts a claim that contradicts a manual fact, the extractor saves the extracted claim withstatus = "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_sourcesstream contradicts aone_mapped_sourcespeaker, the new claim is recorded asstatus = "provisional".
- Manual user claims (
4.2 Deadlines and time-bounded claims
- When a meeting on
2026-11-09states "We will deployscribe.akyonlabs.comby2026-11-12", the claim'svalid_fromis2026-11-09(when the commitment was made),valid_toremains empty while open, andvalue_jsonrecords{"due_at": "2026-11-12", "deadline_state": "upcoming"}. - Passing the
due_atwall-clock timestamp does not silently delete the deadline or mark it completed. Ifnow > due_atand no subsequent meeting or manual action provides completion, rescheduling, or cancellation evidence, the UI renders the deadline aspast_due (unconfirmed)and moves it out of active commitments only when superseded by actual evidence (completed,rescheduled, orcanceled) or user action.
4.3 Strict scoped entity resolution (preventing false merges)
- People (
kind = "person"):- Never merge by display name alone and never infer speaker identity from a calendar invite. Two participants named
"Alex"remain separatepeople_profilesand separateknowledge_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 asspaces/.../devices/136) are ephemeral and scoped to a singlemeeting_id(idx_meeting_participants_uniqueon(meeting, platform, participant_device_id)). Do not auto-merge a person across meetings usingcalendar_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_idinpeople_profiles.platform_ids), or - Explicit user merge confirmation.
- An exact, non-empty verified email match (
- Participants with only a display name and a meeting-scoped
participant_device_idcreate a meeting-scoped orprovisionalperson entity and surface a non-intrusive merge suggestion in the UI.
- Never merge by display name alone and never infer speaker identity from a calendar invite. Two participants named
- 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.comfrom a verified participant email domain or explicit company URL), a canonical external key (github:org), or an existing company linked to the same recurringmeeting_series. When only a bare spoken company name is available without a domain or series link, create or propose aprovisionalcompany link unless the user confirms it.
- Never auto-merge companies by bare display name alone, because short or common company names (such as
- Projects and Concepts (
kind = "project","concept"):- Match by
(owner, kind, normalized_key)wherenormalized_keyis a canonical project slug or linkedtags/meeting_series.projectsentry, always respectingdo_not_merge_withtombstones from any prior user split.
- Match by
- Evolving keyword and topic weights:
- User-defined tags in
tagsare never deleted automatically. - Concept nodes (
kind = "concept", such ashealth,openship,webapp,ai,research) computetopic_weightover 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$.
- User-defined tags in
4.4 Reversible merge, split, and undo
- Merge (
POST /api/openscribe/entities/{id}/merge): Records aknowledge_revisionssnapshot of both entities and their claims/evidence/links, repointsknowledge_facts,entity_resource_links, andmeeting_participantsto the surviving entity, setsmerged_into = target_idon the source entity, and recomputes the target entity's temporal timeline from the combined evidence. - Split / Undo (
POST /api/openscribe/entities/revisions/{revId}/undoor/split): Restores the exact claim, evidence, and participant assignments frombefore_snapshot(or moves user-selected meeting evidence to a split entity), records both entity IDs indo_not_merge_withso 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:
- Delete per-source evidence rows: All
knowledge_fact_evidenceandentity_resource_linksrows referencing the deletedsource_meeting,source_note, orsource_attachmentcascade-delete immediately. - Prune claims with zero remaining support: For every affected
knowledge_factsrow:- If
is_manual == falseand the claim now has zero remainingknowledge_fact_evidencerows, theknowledge_factsrow itself is deleted. - If the claim still has one or more remaining
knowledge_fact_evidencerows (for example, it was stated in both Meeting 1 and Meeting 2, and only Meeting 2 was deleted), theknowledge_factsrow is retained, and itsfirst_observed_atandlast_observed_attimestamps are recomputed from the remaining evidence rows.
- If
- 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 flipold_fact.statusback to"active". - Instead, OpenScribe re-evaluates all remaining non-retracted claims (
is_manual == trueor having $\ge 1$ remainingknowledge_fact_evidencerow) for that(subject_entity, predicate)in chronological order of their remainingobserved_atevidence:- 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 supersedesold_fact,old_factremains"superseded"and itssuperseded_bypointer updates to that remaining newer claim. - If
old_factitself has an explicitvalid_toin the past or explicit termination evidence in a remaining meeting, it transitions to"expired"rather than being asserted as current truth. - Only if
old_factstill has valid remaining evidence, no remaining newer superseding claim, and no expired validity bound does it return tostatus = "active"withsuperseded_by = nilandsuperseded_at = nil.
- If a manual override exists for that predicate, the manual claim remains authoritative (
- Deleting a newer claim that had superseded an older claim (
- Recompute entity projection: Rebuild
current_attributesandcurrent_briefstrictly from the resultingstatus == "active"claims andmanual_attributesin the same SQLite transaction.
5. Privacy, security, and untrusted-content guardrails
- Strict owner isolation: Every entity, claim, evidence row, resource link, and revision carries
owner(users.id). No entity resolution or graph traversal ever crossesownerboundaries. - 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. - 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_statusis set to"url_only". OpenScribe never claims private document contents were read unless fetched through an explicitly authorized connector (ingested_status = "authorized_content"). - 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:
- Confirmed ongoing meeting gating (audio recording not required):
RecorderCoordinator.canAddMeetingContextistruewhenever a meeting is confirmed active (meetingState == .joined,meetingState == .recording,isRecording == true, or a monitored native meeting app is instate == "confirmed_ui"), even when audio recording or live ASR is not running.CompanionView.swiftbinds its composer enabled state tocoordinator.canAddMeetingContext(replacing the olderactiveBackendMeetingId+currentUsergate).
- 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, andattachments/<id>-<filename>) with its originating(sessionId, backendMeetingId, capturedAtMs)before the UI reports it as saved. - When signed in and reachable,
RecorderCoordinatoruploads queued notes (POST /api/openscribe/meetings/{id}/notes) and files (POST /api/openscribe/meetings/{id}/attachmentswithkind = "file"or"screenshot") asynchronously.
- Strict post-meeting lock and late-upload safety:
- As soon as the meeting transitions to
.endedor.idle,canAddMeetingContextbecomesfalse, 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
originatingSessionIdby 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.
- As soon as the meeting transitions to
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.tsxto 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 inserver/mcpbridge.