docs/references/knowledge/workflow-architecture.md
This document records the intended v2 Knowledge workflow architecture. It is the canonical reference for the runtime workflow; temporary RFC copies under v2-refactor-temp/ may be removed before release.
Knowledge operations are modelled as a lightweight workflow rather than a single indexing pipeline:
API / user action
-> KnowledgeIngestionService
-> JobManager
-> Knowledge job handlers
-> KeyedMutex.runExclusive
-> SQLite / index store / knowledge-owned files (raw/)
The design keeps three owners:
KnowledgeIngestionService decides the next workflow step.KeyedMutex.runExclusive) serializes same-base mutations and cleanup.Helpers may own source planning, lifecycle writes, knowledge-owned raw files, and FileProcessing adaptation. They should stay as modules until they need lifecycle-managed resources, IPC, timers, or long-lived state.
addItems, deleteItems, and reindexItems are async workflow entry points. API resolution means the durable workflow has been accepted, not that every physical side effect has finished.
addItems resolves after root rows are created and first Knowledge jobs are queued.deleteItems resolves after top-level target subtrees are marked deleting and knowledge.delete-subtree is queued.reindexItems resolves after each top-level target subtree is confirmed terminal (completed or failed) and knowledge.reindex-subtree is queued.Default item list, search, and RAG hydration exclude deleting items. deleting is a durable cleanup marker, not a tombstone or terminal success state.
The workflow service owns all branching:
scheduleItem(baseId, itemId)
directory -> enqueue knowledge.prepare-root
file / note / url -> source planning
direct -> enqueue knowledge.index-documents
invalid -> mark item failed
needs processing -> Round 2 FileProcessing path
Job handlers do not decide whether an item is a root, nested container, direct leaf, or FileProcessing candidate. They perform their current stage and re-enter the workflow service.
knowledge.prepare-root expands a directory item and creates or replaces its child rows. Expansion results must not assume every child is a leaf:
prepare-root(container)
-> create/replace child rows
-> for each child:
workflowService.scheduleItem(baseId, childId)
If a child is another directory, scheduleItem queues another knowledge.prepare-root. If a child is file, note, or url, scheduleItem routes it to source planning and indexing. Recursive processing therefore lives in the workflow service loop, not inside a reader-specific branch.
Round 1 job types:
knowledge.prepare-root: expand a container and schedule each child.
knowledge.index-documents: read/chunk/embed/write vectors for a concrete document source. Empty reader results or zero chunks still write an empty vector set and complete the item.
knowledge.delete-subtree: cancel active subtree jobs, delete vectors, delete base-directory files, then delete resolved item ids with deleteItemsByIds. The create/index path does not register FileManager refs, so there is no separate file-ref detach step; any historical FileEntry rows are left to the file module's no-reference policy.
knowledge.reindex-subtree: for terminal subtrees only, delete vectors, remove stale container descendants, reset selected root state, then call scheduleItem. Selected leaf roots keep their source files on disk and are repaired by index-documents from knowledge_item.data.
knowledge.check-file-processing-result: poll or inspect the FileProcessing job, record the converted markdown's location on the item (via updateIndexedRelativePath) on success, then schedule indexing.
knowledge_base.fileProcessorId controls source planning for supported file items. When a source needs conversion, the workflow starts FileProcessing, schedules knowledge.check-file-processing-result, records the converted markdown's location on the item via updateIndexedRelativePath (the indexedRelativePath leaf field, not a separate file-ref artifact row), then indexes that markdown.
Status (2026-06-08): baseline behavior.
check-file-processing-resultis a polling job:scheduleFileProcessingCheckreschedules it atFILE_PROCESSING_CHECK_DELAY_MS = 5_000(5s) intervals until the FileProcessing job reaches a terminal state; if it is still unfinished afterFILE_PROCESSING_MAX_WAIT_MS = 30 * 60 * 1000(30 minutes), the remote job is cancelled and the item is markedfailed. On success the output's relative path is recorded viaupdateIndexedRelativePath, thenindex-documentsis scheduled.
Same-base Knowledge mutations must go through the per-base mutation lock (KeyedMutex.runExclusive). Main SQLite writes still use a synchronous write transaction (DbService.withWriteTx, or an equivalent db.transaction()); the mutation lock is not a replacement for that write transaction.
Crash safety comes from durable jobs, durable item states, JobManager recovery, and idempotent cleanup. The in-memory mutation lock only serializes concurrent work in the current process.
Delete and reindex span two stores: the main SQLite database and the per-base vector store. They cannot be one cross-store transaction. Consistency relies on durable re-entry and idempotent vector/artifact/row cleanup.
User-triggered reindex is not a cancellation primitive. The service admits reindex only when the entire selected subtree is already completed or failed. Active states (idle, preparing, processing, reading, embedding) and deleting are rejected; delete remains the operation that can be requested at any time.