Files
goclaw/docs/24-knowledge-vault.md

557 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 24 - Knowledge Vault
The Knowledge Vault is a query layer above existing memory systems (episodic memory, knowledge graph, memory files). It provides document registration, bidirectional wikilinks, filesystem sync, and unified search across all knowledge sources.
**Not a replacement** — extends capability. The vault sits between agents and episodic/KG stores, enabling agents to curate workspace documents with explicit relationships.
---
## 1. Architecture Overview
### Components
| Component | Role |
|-----------|------|
| **VaultStore** | Interface: document CRUD, link management, hybrid search (FTS+vector) |
| **VaultService** | Search coordinator: fan-out across vault, episodic, KG; parallel queries with weighted ranking |
| **VaultSyncWorker** | Filesystem watcher: detects file changes (create/write/delete), syncs content hashes back to registry |
| **VaultRetriever** | Retrieval adapter: bridges vault search into agent L0 memory system |
| **HTTP Handlers** | REST endpoints: list, get, search, links |
### Data Flow
```
Agent writes document → Workspace FS
VaultSyncWorker detects change
Update vault_documents (hash, metadata)
On agent query: vault_search tool
VaultSearchService (parallel fan-out)
↙ ↓ ↘
Vault Episodic Knowledge Graph
↘ ↓ ↙
Normalize & Weight Scores
Return Top Results
```
### Tenant & Scope Isolation
Documents are scoped by **tenant** (isolation), **agent** (per-agent namespace), and **document scope** (personal/team/shared):
- **personal**: Agent-specific documents (agent_context_files, per-user work)
- **team**: Team workspace documents (team_context_files)
- **shared**: Cross-tenant shared knowledge (future)
### Document Scope & Ownership
| scope | agent_id | team_id | Visibility |
|-------|----------|---------|------------|
| personal | set | NULL | Owning agent only (within tenant) |
| team | NULL | set | Members of the team (within tenant) |
| shared | NULL | NULL | All agents within the tenant |
| custom | any | any | User-defined via `custom_scope` |
#### DB Invariant (migration 000055)
A CHECK constraint enforces the above table: `vault_documents_scope_consistency`. Rejects inserts/updates that violate the scope × ownership relationship. `scope='custom'` is unconstrained (user-defined scopes).
#### Agent Read Semantics
`vault_search`, `ListDocuments`, `CountDocuments` return:
- Docs owned by the agent (`agent_id = <agent>`)
- PLUS shared docs (`agent_id IS NULL`)
Within a team context (RunContext with TeamID set), results also include team-scoped docs for that team. Tenant isolation (`tenant_id = <tenant>`) is always enforced.
---
## 2. Data Model
### vault_documents
Document registry: metadata pointers. Content lives on filesystem; registry holds path, hash, embeddings, and links.
| Column | Type | Notes |
|--------|------|-------|
| `id` | UUID | Primary key |
| `tenant_id` | UUID | Multi-tenant isolation |
| `agent_id` | UUID | Per-agent namespace |
| `scope` | TEXT | personal \| team \| shared |
| `path` | TEXT | Workspace-relative path (workspace/notes/foo.md) |
| `title` | TEXT | Display name |
| `doc_type` | TEXT | context, memory, note, skill, episodic |
| `content_hash` | TEXT | SHA-256 of file content (detects changes) |
| `embedding` | vector(1536) | pgvector: semantic similarity |
| `tsv` | tsvector | Generated: FTS index on title+path |
| `metadata` | JSONB | Optional custom fields |
| `created_at`, `updated_at` | TIMESTAMPTZ | Timestamps |
| **Unique constraint** | (agent_id, scope, path) | One doc per path per scope |
**Indices:**
- `idx_vault_docs_tenant` — tenant_id (multi-tenant queries)
- `idx_vault_docs_agent_scope` — agent_id, scope (agent-scoped filters)
- `idx_vault_docs_type` — agent_id, doc_type (type filters)
- `idx_vault_docs_hash` — content_hash (change detection)
- `idx_vault_docs_embedding` — HNSW vector (semantic search)
- `idx_vault_docs_tsv` — GIN FTS index (keyword search)
### vault_links
Bidirectional links between documents (wikilinks, explicit references).
| Column | Type | Notes |
|--------|------|-------|
| `id` | UUID | Primary key |
| `from_doc_id` | UUID | Source document |
| `to_doc_id` | UUID | Target document |
| `link_type` | TEXT | wikilink, reference, etc. |
| `context` | TEXT | ~50 chars: surrounding text snippet |
| `created_at` | TIMESTAMPTZ | Creation timestamp |
| **Unique constraint** | (from_doc_id, to_doc_id, link_type) | No duplicate links |
**Indices:**
- `idx_vault_links_from` — from_doc_id (outgoing links)
- `idx_vault_links_to` — to_doc_id (backlinks)
### vault_versions
Version history (v3.1+ preparation; empty in v3.0).
| Column | Type | Notes |
|--------|------|-------|
| `id` | UUID | Primary key |
| `doc_id` | UUID | Document reference |
| `version` | INT | Version number |
| `content` | TEXT | Snapshot of document content |
| `changed_by` | TEXT | User/agent identifier |
| `created_at` | TIMESTAMPTZ | Snapshot timestamp |
---
## 3. Wikilinks
Bidirectional markdown links in the `[[target]]` format.
### Parsing & Extraction
`ExtractWikilinks(content string)` parses all `[[...]]` patterns:
- **Format:** `[[path/to/file.md]]` or `[[name|display text]]` (display text ignored)
- **Returns:** `[]WikilinkMatch` with target, context (~50 chars), and byte offset
Examples:
```markdown
See [[architecture/components]] for details.
Reference [[SOUL.md|agent persona]] here.
Link [[../parent-project]] up.
```
**Edge cases:**
- Empty target `[[]]` → skipped
- Whitespace-only targets → trimmed and skipped
- Paths with spaces: `[[foo bar]]` → preserved
- File extensions: `.md` auto-appended if missing
### Resolution Strategy
`ResolveWikilinkTarget()` finds the document matching a wikilink target:
1. **Exact path match**`GetDocument(tenantID, agentID, "path/to/file.md")`
2. **With .md suffix** — If target lacks `.md`, retry with suffix
3. **Basename search** — Linear search through all agent docs; match by basename (case-insensitive)
4. **Unresolved** — Return nil (not an error; backlinks can be incomplete)
Example: `[[SOUL.md]]` → lookup SOUL.md → fallback to lookup SOUL → scan for any doc with basename SOUL
### Link Sync
`SyncDocLinks(ctx, vaultStore, doc, content, tenantID, agentID)` keeps wikilinks in vault_links in sync with document content:
1. Extract `[[...]]` patterns from content
2. Delete all outgoing links for the document (replace strategy)
3. For each match:
- Resolve target via strategy above
- Create `vault_link` row if resolved
4. Return (errors logged, not thrown)
Called during:
- Document upsert (agent writing to workspace)
- VaultSyncWorker processing file changes
---
## 4. Search
Hybrid search integrates vault FTS, vector embeddings, episodic memory, and knowledge graph.
### Vault Search (Store-Level)
`VaultStore.Search(ctx, opts VaultSearchOptions)` on single vault:
- **FTS**: PostgreSQL `plainto_tsquery()` on tsv (title+path keywords)
- **Vector**: pgvector cosine similarity on embedding (semantic)
- **Combined scoring**: Normalize each method's scores (01), then apply query-time weights
- **Results:** Top N documents with score
### Unified Search (Cross-Store)
`VaultSearchService.Search(ctx, opts UnifiedSearchOptions)`:
**Parallel fan-out:**
```
Query → ├─ VaultStore.Search() [0.4 weight]
├─ EpisodicStore.Search() [0.3 weight]
└─ KGStore.SearchEntities() [0.3 weight]
Normalize each source
(max score = 1.0, then * weight)
Merge & deduplicate by ID
Sort by final score DESC
Return top N
```
**Score normalization:** Each source's scores scaled to 01 (max_score / weight), then weighted:
- Vault: 0.4 (keyword + semantic)
- Episodic: 0.3 (session summaries)
- KG: 0.3 (entity relationships)
Default max results per source: `maxResults * 2` (then deduplicate + cap to maxResults).
### Parameters
| Param | Type | Default | Notes |
|-------|------|---------|-------|
| `Query` | string | — | Required: natural language |
| `AgentID` | string | — | Scope to agent |
| `TenantID` | string | — | Scope to tenant |
| `Scope` | string | all | personal, team, shared |
| `DocTypes` | []string | all | context, memory, note, skill, episodic |
| `MaxResults` | int | 10 | Results per final result set |
| `MinScore` | float64 | 0.0 | Filter by minimum score |
---
## 5. Filesystem Sync
`VaultSyncWorker` watches workspace directories for changes and keeps vault_documents hashes in sync.
### Watcher Loop
Uses `fsnotify` to detect Write, Create, Remove events:
1. Debounce: 500ms (multiple rapid changes → one batch)
2. For each file change:
- Compute SHA-256 hash of file content
- Compare to `vault_documents.content_hash`
- If different: update hash in DB
- If file deleted: mark `metadata["deleted"] = true`
### Constraints
- Only syncs **registered** documents (files already in vault_documents)
- New files must be registered by agent (via agent write) first
- Unreadable files → marked as deleted
### Setup
```go
syncer := vault.NewVaultSyncWorker(vaultStore)
go syncer.Watch(ctx, workspaceDir, tenantID, agentID)
```
---
## 6. HTTP API
RESTful endpoints for vault operations.
### List Documents
**Endpoint:** `GET /v1/agents/{agentID}/vault/documents`
**Query params:**
- `scope` — personal, team, or shared (optional)
- `doc_type` — comma-separated types (optional)
- `limit` — default 20, max 500
- `offset` — pagination
**Response:**
```json
[
{
"id": "uuid",
"agent_id": "uuid",
"path": "workspace/notes/foo.md",
"title": "Foo Notes",
"doc_type": "note",
"content_hash": "sha256hex",
"created_at": "2026-04-07T00:00:00Z"
}
]
```
### Get Document
**Endpoint:** `GET /v1/agents/{agentID}/vault/documents/{docID}`
**Response:** Single VaultDocument object
### Search
**Endpoint:** `POST /v1/agents/{agentID}/vault/search`
**Request body:**
```json
{
"query": "authentication flow",
"scope": "team",
"doc_types": ["context", "note"],
"max_results": 10
}
```
**Response:**
```json
[
{
"document": { /* VaultDocument */ },
"score": 0.87,
"source": "vault"
},
{
"document": { /* episodic record */ },
"score": 0.65,
"source": "episodic"
}
]
```
### Get Links
**Endpoint:** `GET /v1/agents/{agentID}/vault/documents/{docID}/links`
**Response:**
```json
{
"outlinks": [
{
"id": "uuid",
"to_doc_id": "uuid",
"link_type": "wikilink",
"context": "See [[target]] for details."
}
],
"backlinks": [
{
"id": "uuid",
"from_doc_id": "uuid",
"link_type": "wikilink",
"context": "Reference [[SOUL.md]] here."
}
]
}
```
### List (Cross-Agent)
**Endpoint:** `GET /v1/vault/documents`
**Query params:**
- `agent_id` — optional filter to specific agent (otherwise all tenant agents)
- `scope`, `doc_type`, `limit`, `offset` — same as per-agent endpoint
---
## 7. Tools
Agents access vault via two tools.
### vault_search
**Primary discovery tool:** Search across all knowledge sources (vault, episodic, KG) with unified ranking.
**Parameters:**
```json
{
"query": "string (required)",
"scope": "string (optional: personal|team|shared)",
"types": "string (optional: comma-separated doc types)",
"maxResults": "number (optional: default 10)"
}
```
**Example:**
```
Agent: "Find documents about authentication"
Tool call: vault_search(query="authentication", types="context,note")
Result: Top 10 results from vault + episodic + KG
```
### vault_link
**Create explicit link:** Connect two documents (similar to [[wikilink]]).
**Parameters:**
```json
{
"from": "string (source document path, required)",
"to": "string (target document path, required)",
"context": "string (optional: relationship description)"
}
```
**Example:**
```
Agent: "Link the authentication guide to the SOUL file"
Tool call: vault_link(from="docs/auth.md", to="SOUL.md", context="Persona reference")
Result: Explicit link created in vault_links
```
---
## 8. Retriever Integration
`VaultRetriever` bridges vault search into agent L0 memory system.
### Usage
```go
retriever := vault.NewVaultRetriever(searchService)
summaries, err := retriever.RetrieveL0(ctx, agentID, userID, query, config)
// Returns []memory.L0Summary with highest-ranked results
```
### Configuration
```go
type RetrieverConfig struct {
RelevanceThreshold float64 // default 0.3
MaxL0Items int // default 5
TenantID string
}
```
Used by agent to retrieve relevant documents during think phase (before plan/act).
---
## 9. Web UI (v3)
Vault page in dashboard displays:
- **Document list** — workspace documents filtered by scope/type
- **Graph visualization** — nodes = documents, edges = wikilinks, interactive pan/zoom
- **Search interface** — unified cross-store search with source badges
- **Link editor** — create/remove links between documents
---
## 10. Feature Flags & Configuration
Knowledge Vault is **v3-only** feature.
- **Edition:** Standard and Lite (full support)
- **Prerequisite:** PostgreSQL with pgvector extension
- **Storage:** vault_* tables created by migration 000038
- **Workspace:** Documents organized in agent-level workspace directory (e.g., `~/.goclaw/workspace/agent_name/`)
No explicit feature flag; vault is enabled if:
1. Migration 000038 ran successfully
2. VaultStore initialized during gateway startup
3. VaultSyncWorker started
---
## 11. Examples
### Adding a Document
Agent writes to workspace:
```
~/.goclaw/workspace/myagent/notes/architecture.md
```
On next sync (or immediate write):
1. VaultSyncWorker detects file creation
2. Computes SHA-256 hash
3. Vault document auto-registered with metadata
### Creating a Wikilink
Markdown content:
```markdown
See [[architecture.md]] for system design.
See [[SOUL.md|persona]] for agent personality.
```
Agent calls `vault_search("system design")`:
1. ExtractWikilinks finds `[[architecture.md]]` and `[[SOUL.md]]`
2. ResolveWikilinkTarget matches to registered documents
3. SyncDocLinks creates vault_link rows
4. Backlinks available via `/links` endpoint
### Search Example
Agent: "Find notes about authentication"
Request:
```json
POST /v1/agents/agent-123/vault/search
{
"query": "authentication flow",
"scope": "personal",
"max_results": 5
}
```
Response (parallel results from vault + episodic + KG):
```json
[
{
"document": {
"id": "doc-456",
"path": "notes/auth.md",
"title": "Authentication Flow",
"doc_type": "note"
},
"score": 0.92,
"source": "vault"
},
{
"id": "episodic-789",
"title": "Session-2026-04-06",
"source": "episodic",
"score": 0.68
}
]
```
---
## 12. Limitations
- **Vault docs do not auto-embed in system prompt** — Must be retrieved via agent tools
- **No full-text indexing on document content** — Only title+path FTS; content requires embeddings
- **Sync is one-way** — Filesystem changes sync to vault; vault does not write back to FS
- **No conflict resolution** — Concurrent edits not detected; last write wins
- **Version history empty** — vault_versions table prepared for v3.1
---
## File Reference
| Module | Path | Purpose |
|---|---|---|
| Vault service & sync | `internal/vault/` | VaultStore, VaultService, VaultSyncWorker, VaultRetriever, wikilink parsing |
| Store & HTTP | `internal/store/vault_store.go`, `internal/http/vault_handlers.go` | Store interface, REST endpoints (list, get, search, links) |
| Tools & migration | `internal/tools/vault_*.go`, `migrations/000038_vault_tables.up.sql` | vault_search and vault_link tools, schema migration |
Use `grep` or your editor's symbol search for specific files.