fs

package
v0.4.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 16, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package fs implements knowledge.DocumentRepo, knowledge.ChunkRepo and knowledge.LayerRepo on top of any workspace.Workspace.

Layout under <prefix>/<dataset>/:

<doc>                  raw source content (unchanged)
<doc>.meta.json        SourceDocument.Version + Metadata sidecar
.chunks.json           DerivedChunk[] for the dataset (atomic write)
<doc>.abstract         L0 layer text   (Q7=A: human-readable)
<doc>.abstract.vec     L0 layer embedding (binary, see vec.go)
<doc>.overview         L1 layer text
<doc>.overview.vec     L1 layer embedding
<doc>.layers.json      per-doc layer DerivedSig sidecar
.abstract.md           dataset-level L0 text
.abstract.md.vec       dataset-level L0 embedding
.overview.md           dataset-level L1 text
.overview.md.vec       dataset-level L1 embedding
.dataset_layers.json   dataset-level layer DerivedSig sidecar

Atomic writes go through workspace.Rename(tmp, final); the workspace contract requires Rename to be POSIX-atomic when the medium supports it, so partial writes are never observable.

Deprecated: use github.com/GizClaw/flowcraft/memory/knowledge/backend/fs instead. This package will be removed in v0.5.0.

Index

Constants

View Source
const DefaultPrefix = "knowledge"

DefaultPrefix is the workspace sub-tree owned by the knowledge backend.

Variables

This section is empty.

Functions

This section is empty.

Types

type FSChunkRepo deprecated

type FSChunkRepo struct {
	// contains filtered or unexported fields
}

FSChunkRepo persists DerivedChunks per dataset. Each dataset has one .chunks.json file and an in-memory inverted index built lazily on first access (or on Load).

Concurrency: every public method is safe for concurrent use; the repo's RWMutex protects the state map and each datasetState.

Deprecated: FSChunkRepo is a unit-test / demo-grade backend. Replace rewrites the entire dataset .chunks.json on every per-doc ingest and rebuilds both chunk-level and doc-level inverted indices from scratch, all under a single write lock — making per-doc ingest O(N) and total ingest O(N^2) in the dataset (see #134). For any non-trivial dataset use factory.NewRetrieval with an in-process sdk/retrieval/memory.Index or a production retrieval.Index from sdkx/retrieval/{sqlite,postgres,workspace}. Slated for removal in v0.5.0; see docs/migrations/v0.5.0.md.

func NewChunkRepo deprecated

func NewChunkRepo(ws workspace.Workspace, prefix string, tok textsearch.Tokenizer) *FSChunkRepo

NewChunkRepo constructs an FSChunkRepo. Tokenizer is auto-detected from the first content seen when nil; explicit override wins.

Deprecated: see FSChunkRepo. Use factory.NewRetrieval with a sdk/retrieval/memory.Index (or any production retrieval.Index) instead. Slated for removal in v0.5.0.

func (*FSChunkRepo) DeleteByDataset

func (r *FSChunkRepo) DeleteByDataset(ctx context.Context, datasetID string) error

DeleteByDataset removes every chunk for the dataset and the chunks.json file.

func (*FSChunkRepo) DeleteByDoc

func (r *FSChunkRepo) DeleteByDoc(ctx context.Context, datasetID, docName string) error

DeleteByDoc removes every chunk for (datasetID, docName). Missing target documents are idempotent success and do not return NotFound.

Same locking discipline as Replace: read-mutate-persist-install runs under the write lock so concurrent Delete + Replace cannot lose docs by racing on a stale state snapshot.

func (*FSChunkRepo) GetDocSig added in v0.4.0

func (r *FSChunkRepo) GetDocSig(ctx context.Context, datasetID, docName string) (knowledge.DerivedSig, bool, error)

GetDocSig satisfies knowledge.ChunkSigReader for the filesystem backend. Missing datasets or documents return (DerivedSig{}, false, nil).

func (*FSChunkRepo) Load

func (r *FSChunkRepo) Load(ctx context.Context) error

Load rehydrates state for every dataset under the prefix. Idempotent; safe to call on startup. Failures for individual datasets are collected and returned as a joined error.

func (*FSChunkRepo) Replace

func (r *FSChunkRepo) Replace(ctx context.Context, datasetID, docName string, chunks []knowledge.DerivedChunk) error

Replace atomically swaps every chunk for (datasetID, docName). The in-memory index is rebuilt from the merged chunk set and the dataset chunks.json is written atomically.

The full read-merge-persist-install sequence runs under the write lock. The pre-fix layout dropped the lock between read-merge and install so persist (slow IO) could overlap other Replace calls; that races against any concurrent Replace because both goroutines see the SAME pre-mutation state, build conflicting merged slices, and whichever installs last silently drops the other's chunks. Observed on BEIR scifact: ingest_concurrency=8 lost ~60% of docs vs ingest=1, dragging chunk-level BM25 nDCG@10 from 0.180 to 0.071 (see eval/leaderboard.md follow-up). Correctness wins over the persist-out-of-lock micro-optimisation; callers that need higher throughput should batch upstream.

func (*FSChunkRepo) Search

Search runs the requested mode against the in-memory index. Vector recall returns nothing when q.Vector is empty; callers are expected to feed an embedded query vector for ModeVector / ModeHybrid.

The function snapshots the per-dataset state under RLock, releases the lock, then scores outside the lock so long-running CPU work does not stall writers.

func (*FSChunkRepo) SearchDocs added in v0.3.10

SearchDocs runs a doc-level BM25 query against the dataset.

This is the doc-granularity counterpart of Search: instead of returning one Candidate per matching chunk (which forces callers like eval/beir to collapse chunks→docID themselves), SearchDocs scores every doc as a whole and returns at most one Candidate per matching docName. ChunkIndex is set to -1 and Layer is left empty in the resulting Hit, signalling "doc-level, no specific chunk".

Vector / Hybrid modes are not supported at the doc level yet (vectors are chunk-embedded by design); ModeVector/ModeHybrid currently fall back to BM25-only doc scoring with a soft error log. Tracked in #126.

type FSDocumentRepo

type FSDocumentRepo struct {
	// contains filtered or unexported fields
}

FSDocumentRepo persists SourceDocuments under <prefix>/<dataset>/.

Concurrency: Put against the same (datasetID, name) is serialised by a per-document mutex so the read-modify-write Version increment is deterministic. Reads do not acquire the per-document mutex.

func NewDocumentRepo

func NewDocumentRepo(ws workspace.Workspace, prefix string) *FSDocumentRepo

NewDocumentRepo wires an FSDocumentRepo to ws under the given prefix ("knowledge" if empty).

func (*FSDocumentRepo) Delete

func (r *FSDocumentRepo) Delete(ctx context.Context, datasetID, name string) error

Delete removes the raw document, its meta sidecar and its layer sidecars. Idempotent: missing files are ignored.

func (*FSDocumentRepo) Get

func (r *FSDocumentRepo) Get(ctx context.Context, datasetID, name string) (*knowledge.SourceDocument, error)

Get returns the SourceDocument with Content losslessly preserved.

Returns errdefs.NotFound when the document does not exist.

func (*FSDocumentRepo) List

func (r *FSDocumentRepo) List(ctx context.Context, datasetID string) ([]knowledge.SourceDocument, error)

List returns SourceDocuments for the dataset, sorted by Name. Content is loaded for each document; callers that only need names should use the lighter ListDatasets+manual List pattern in higher tiers.

func (*FSDocumentRepo) ListDatasets

func (r *FSDocumentRepo) ListDatasets(ctx context.Context) ([]string, error)

ListDatasets enumerates dataset IDs by listing the prefix directory.

Workspace.List in some implementations (notably MemWorkspace) emits the queried directory itself as an entry; we filter that case out so the dataset list never contains the prefix name itself.

func (*FSDocumentRepo) Put

Put atomically increments SourceDocument.Version, writes the raw content, updates the .meta.json sidecar, and returns the authoritative stored SourceDocument. Doc.Version / UpdatedAt supplied by the caller are ignored; the repo is the source of truth for both fields.

func (*FSDocumentRepo) WithNow

func (r *FSDocumentRepo) WithNow(now func() time.Time) *FSDocumentRepo

WithNow overrides the time source (test injection point).

type FSLayerRepo deprecated

type FSLayerRepo struct {
	// contains filtered or unexported fields
}

FSLayerRepo persists DerivedLayers as human-readable sidecars (Q7=A).

Layout per document:

<doc>.abstract        L0 layer text
<doc>.abstract.vec    L0 layer embedding (binary, see vec.go)
<doc>.overview        L1 layer text
<doc>.overview.vec    L1 layer embedding
<doc>.layers.json     DerivedSig metadata for both layers

Layout per dataset:

.abstract.md           dataset L0
.abstract.md.vec       dataset L0 embedding
.overview.md           dataset L1
.overview.md.vec       dataset L1 embedding
.dataset_layers.json   DerivedSig metadata for both

Vectors are stored as separate sidecars so the human-readable text stays diffable in source control while embeddings remain binary. A missing or malformed .vec is a recoverable miss (vector lane skips the entry); the BM25 lane keeps working unchanged.

Detail (L2) layers are not persisted by this repo: detail content already lives in chunks via FSChunkRepo. Put for LayerDetail returns a validation error.

Deprecated: FSLayerRepo is a unit-test / demo-grade backend. Like FSChunkRepo it scales poorly on multi-doc datasets and is paired exclusively with FSChunkRepo through factory.NewLocal — once that pairing is retired in v0.5.0 there is no remaining caller. For production wiring use factory.NewRetrieval, which stores layers inside the retrieval.Index alongside chunks (see #134 and docs/migrations/v0.5.0.md).

func NewLayerRepo deprecated

func NewLayerRepo(ws workspace.Workspace, prefix string, tok textsearch.Tokenizer) *FSLayerRepo

NewLayerRepo wires an FSLayerRepo to ws under prefix.

Deprecated: see FSLayerRepo. Use factory.NewRetrieval instead; slated for removal in v0.5.0.

func (*FSLayerRepo) DeleteByDataset

func (r *FSLayerRepo) DeleteByDataset(ctx context.Context, datasetID string) error

DeleteByDataset removes the dataset-level layer files, their vec sidecars and the sig sidecar.

Per-document layers must be cleaned via DeleteByDoc; this method does NOT enumerate documents to keep its cost predictable.

func (*FSLayerRepo) DeleteByDoc

func (r *FSLayerRepo) DeleteByDoc(ctx context.Context, datasetID, docName string) error

DeleteByDoc removes per-document layer files, their vec sidecars and the sig sidecar. Missing files are ignored so the call is idempotent.

func (*FSLayerRepo) Get

func (r *FSLayerRepo) Get(ctx context.Context, datasetID, docName string, layer knowledge.Layer) (*knowledge.DerivedLayer, error)

Get returns the layer for (datasetID, docName, layer); docName == "" reads the dataset-level layer. Returns (nil, nil) when missing.

func (*FSLayerRepo) Put

Put writes a layer text, refreshes its DerivedSig sidecar and persists the embedding vector when present.

  • layer.DocName == "" denotes a dataset-level layer.
  • layer.Layer must be LayerAbstract or LayerOverview.
  • layer.Vector, when non-empty, is encoded via encodeVec and stored in a co-located ".vec" sidecar; when empty, any pre-existing sidecar is removed so the vector and text never drift apart.

func (*FSLayerRepo) Search

Search performs a layer-tier scan with separate BM25 and vector lanes. The selected mode picks which lane runs:

  • ModeBM25: keyword scan only (vectors ignored).
  • ModeVector: cosine scan only (text ignored); requires q.Vector and a layer that has a stored vector sidecar.
  • ModeHybrid (and the legacy default): both lanes run, results are merged keyed by (dataset, docName), and the higher score wins so downstream rankers see one Candidate per layer entity.

Hits carry Layer == q.Layer (contract guarantee #3: queries never cross layers). When neither lane has anything to score, Search returns (nil, nil) so SearchEngine can keep fanning out without a hard failure.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL