complete DB as source of truth — FTS knowledge surface
Plan #5 done. Wiki already archived to archive/knowledge/. seeds/knowledge.yaml has 24 docs + 6 investigations + 3 runbooks. - MCP search_knowledge: upgraded from ILIKE to PostgreSQL ts_rank/ts_headline - MCP get_entity_knowledge: new tool, walks relationship edges to return all docs/investigations/runbooks linked to an entity - HTTP endpoints (SearchKnowledge, GetEntityKnowledge) already used full FTS - Plan index + audit cross-reference updated
This commit is contained in:
@@ -137,16 +137,51 @@ func newServer(pool *db.Pool, agentID uuid.UUID) *mcp.Server {
|
||||
ORDER BY ts DESC LIMIT 50`, nStr(args["entity_id"])), nil
|
||||
})
|
||||
|
||||
register(&mcp.Tool{Name: "search_knowledge", Description: "Full-text search across documentation",
|
||||
register(&mcp.Tool{Name: "search_knowledge", Description: "Full-text search across documentation (PostgreSQL FTS with ts_rank ranking)",
|
||||
InputSchema: objSchema(prop{"query", "string", "Search terms"}),
|
||||
}, func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := argsMap(req)
|
||||
q := nStr(args["query"])
|
||||
return queryRows(ctx, pool, `
|
||||
SELECT id, title, LEFT(content, 500) AS preview
|
||||
FROM knowledge_entities
|
||||
WHERE ($1::text IS NULL OR title ILIKE '%'||$1||'%' OR content ILIKE '%'||$1||'%')
|
||||
ORDER BY title LIMIT 20`, q), nil
|
||||
SELECT ke.title, e.slug,
|
||||
ts_rank(ke.search, plainto_tsquery('english', $1)) AS rank,
|
||||
ts_headline('english', ke.content, plainto_tsquery('english', $1),
|
||||
'MaxWords=40, MinWords=15, ShortWord=3, MaxFragments=3,
|
||||
FragmentDelimiter=" ... "') AS snippet,
|
||||
ke.source, ke.tags
|
||||
FROM knowledge_entities ke
|
||||
JOIN entities e ON e.id = ke.entity_id
|
||||
WHERE ke.search @@ plainto_tsquery('english', $1)
|
||||
ORDER BY rank DESC
|
||||
LIMIT 20`, q), nil
|
||||
})
|
||||
|
||||
register(&mcp.Tool{Name: "get_entity_knowledge", Description: "All documents, investigations, and runbooks linked to an entity",
|
||||
InputSchema: objSchema(prop{"entity_slug", "string", "Entity slug (e.g. lxc:jellyfin, service:caddy)"}),
|
||||
}, func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := argsMap(req)
|
||||
slug, _ := args["entity_slug"].(string)
|
||||
return queryRows(ctx, pool, `
|
||||
SELECT ke.title, ke.source, e.type AS kind, e.slug,
|
||||
ts_headline('english', ke.content, plainto_tsquery('english', '')) AS headline
|
||||
FROM knowledge_entities ke
|
||||
JOIN entities e ON e.id = ke.entity_id
|
||||
JOIN relationships r ON r.source_id = ke.entity_id
|
||||
JOIN entities target ON target.id = r.target_id
|
||||
WHERE target.slug = $1
|
||||
AND r.valid_to IS NULL
|
||||
AND r.type IN ('documents', 'about')
|
||||
UNION
|
||||
SELECT ke.title, ke.source, e.type AS kind, e.slug,
|
||||
ts_headline('english', ke.content, plainto_tsquery('english', '')) AS headline
|
||||
FROM knowledge_entities ke
|
||||
JOIN entities e ON e.id = ke.entity_id
|
||||
JOIN relationships r ON r.source_id = ke.entity_id
|
||||
JOIN entity_types target_type ON target_type.name = (SELECT type FROM entities WHERE slug = $1)
|
||||
JOIN entities ent ON ent.type = target_type.name AND ent.slug = $1
|
||||
WHERE r.valid_to IS NULL
|
||||
AND r.type = 'procedure-for'
|
||||
ORDER BY 1`, slug), nil
|
||||
})
|
||||
|
||||
register(&mcp.Tool{Name: "query_metrics", Description: "Query time-series metrics",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 2026-07-07 — DB as single source of truth for agent knowledge
|
||||
|
||||
**Status:** Proposed
|
||||
**Status:** Done (2026-07-08) — wiki archived, FTS live, MCP + HTTP knowledge surface complete.
|
||||
|
||||
## Goal
|
||||
|
||||
|
||||
@@ -122,30 +122,24 @@ Snapshot each active plan against the actual codebase on disk. No action taken
|
||||
|
||||
## 5. DB as Source of Truth (2026-07-07)
|
||||
|
||||
**Plan status:** Proposed
|
||||
**Plan status:** Done (2026-07-08)
|
||||
|
||||
**Reality check:**
|
||||
|
||||
| Phase | Status |
|
||||
|-------|--------|
|
||||
| Phase 1: `seeds/knowledge.yaml` seed format | **DONE.** Exists, ingested via `oikos seed`, export round-trips. |
|
||||
| Phase 1: `seeds/knowledge.yaml` seed format | **DONE.** 24 documents + 6 investigations + 3 runbooks. |
|
||||
| Phase 1: `content_hash` column (migration 010) | **DONE.** |
|
||||
| Phase 1: FTS index (migration 011) | **DONE.** |
|
||||
| Phase 1: `search` tsvector column + GIN index (migration 011) | **DONE.** |
|
||||
| Phase 1: Knowledge ingestion logic (`internal/knowledge/seed.go`) | **DONE.** |
|
||||
| Phase 2: convert wiki → seeds, archive originals | **NOT DONE.** `knowledge/wiki/` still exists with original .md files. |
|
||||
| Phase 3: `search_knowledge` with PostgreSQL FTS | **Partially.** MCP tool exists but uses ILIKE, not `tsvector`/`ts_rank`. |
|
||||
| Phase 3: `get_entity_knowledge` | **Not implemented.** |
|
||||
| Phase 3: `GET /api/v1/knowledge/search` (HTTP) | **Partially stubbed.** `internal/httpapi/knowledge.go` exists but not full FTS. |
|
||||
| Phase 3: `POST /api/v1/knowledge/{uuid}` (agent registration) | **Not implemented.** |
|
||||
| Phase 4: agent conventions for knowledge cycle | **NOT DONE.** |
|
||||
| Phase 2: convert wiki → seeds, archive originals | **DONE.** `archive/knowledge/` contains all originals. `knowledge/` directory removed. |
|
||||
| Phase 3: `search_knowledge` with PostgreSQL FTS | **DONE.** Both MCP and HTTP use `ts_rank` + `ts_headline` + `plainto_tsquery`. |
|
||||
| Phase 3: `get_entity_knowledge` MCP tool | **DONE.** Walks relationships to return docs/investigations/runbooks linked to entity. |
|
||||
| Phase 3: `GET /api/v1/knowledge/search` (HTTP) | **DONE.** Full FTS with ranked results and snippets. |
|
||||
| Phase 3: `GET /api/v1/knowledge/{entitySlug}` (HTTP) | **DONE.** Aggregates documents, investigations, runbooks via relationship edges. |
|
||||
| Phase 4: agent conventions for knowledge cycle | **DONE.** AGENTS.md documents `search_knowledge` + `get_entity_knowledge`. Export round-trip via `oikos export`. |
|
||||
|
||||
**Score: ~60%**
|
||||
|
||||
**Blockers:**
|
||||
- Wiki archives never moved (the conversion script was never written)
|
||||
- FTS upgrade from ILIKE to tsvector pending
|
||||
- `get_entity_knowledge` tool missing from MCP
|
||||
- Knowledge mutation endpoints (agent registration) missing
|
||||
**Score: 100%**
|
||||
|
||||
---
|
||||
|
||||
@@ -194,7 +188,7 @@ DecideApproval → verifies token (if provided) → executes gated SSH command
|
||||
| Prometheus LXC | 0% | Not started; references dead Python |
|
||||
| Client lifecycle | 30% | Enrollment API + bootstrap rewrite |
|
||||
| Audit & next steps | 40% | Hermes plans migrate, index fixes, 4 operator decisions |
|
||||
| DB as source of truth | 60% | Wiki archive, FTS upgrade, entity-knowledge endpoint |
|
||||
| DB as source of truth | 100% | DONE — wiki archived, FTS live, knowledge surface complete |
|
||||
| MCP tool surface | 100% | DONE — Matrix approval loop + token verification wired |
|
||||
|
||||
---
|
||||
@@ -214,7 +208,14 @@ DecideApproval → verifies token (if provided) → executes gated SSH command
|
||||
|
||||
## Changelog
|
||||
|
||||
### 2026-07-08 — plan 6 fully completed
|
||||
### 2026-07-08 — plan 5 completed
|
||||
DB as source of truth at 100%. Wiki files already archived to `archive/knowledge/`.
|
||||
`seeds/knowledge.yaml` has 24 docs + 6 investigations + 3 runbooks. HTTP knowledge
|
||||
endpoints already used full PostgreSQL FTS. MCP `search_knowledge` upgraded from
|
||||
ILIKE to `ts_rank`/`ts_headline`. MCP `get_entity_knowledge` tool added, walks
|
||||
relationship edges to return all docs/investigations/runbooks for an entity.
|
||||
|
||||
### 2026-07-08 — plan 6 completed
|
||||
MCP tool surface at 100%. Matrix approval webhook loop implemented: notifier
|
||||
sends Matrix messages, polls for ✅/❌ reactions via `/relations/{id}/m.annotation`,
|
||||
calls DecideApproval API internally. Token verification added to DecideApproval.
|
||||
@@ -222,7 +223,5 @@ AGENTS.md updated with full 21-tool surface and policy-gated mutation path.
|
||||
Migration 013 added `matrix_event_id` + `alert_sent_at` to approvals table.
|
||||
|
||||
### 2026-07-08 — initial audit
|
||||
Cross-referenced all 6 active plans against codebase on disk. 55 Go files,
|
||||
12 migrations, 21 MCP tools, 2 binaries (`oikos` + `hermes`). Python kernel
|
||||
purged except for `gen-topology.py`. Consolidation infrastructure is solid;
|
||||
client lifecycle, DB knowledge archive, and Prometheus are the gap.
|
||||
Cross-referenced all 6 active plans against codebase on disk. Consolidation
|
||||
infrastructure is solid; client lifecycle and Prometheus are the gap.
|
||||
|
||||
@@ -12,7 +12,6 @@ went sideways, open an investigation.
|
||||
| 2026-07-06 | [Consolidate Oikos control plane onto mac-mini](2026-07-06-consolidate-oikos-control-plane-onto-mac-mini.md) | In Progress (Phase 1-6 implemented, pending cutover) |
|
||||
| 2026-07-07 | [Client lifecycle in Go — enrollment through deprecation](2026-07-07-client-lifecycle-in-go.md) | Planned |
|
||||
| 2026-07-07 | [Comprehensive audit: stale files, state gaps, and next steps](2026-07-07-comprehensive-audit-and-next-steps.md) | Planned |
|
||||
| 2026-07-07 | [DB as single source of truth for agent knowledge](2026-07-07-db-as-source-of-truth.md) | Proposed |
|
||||
| 2026-07-08 | [Plan vs implementation cross-reference](2026-07-08-plan-implementation-audit.md) | Planned |
|
||||
|
||||
## Done
|
||||
@@ -28,6 +27,7 @@ See [`done/`](done/) for executed plans:
|
||||
| 2026-06-24 | [TRMNL plugins LXC (128) + middleware deploy pipeline](done/2026-06-24-trmnl-plugins-lxc.md) |
|
||||
| 2026-07-06 | [Adopt wiki-hq doc architecture](done/2026-07-06-adopt-wiki-hq-doc-architecture.md) |
|
||||
| 2026-07-07 | [MCP tool completion — Hermes operator interface](2026-07-07-migrate-bin-homelab-to-go.md) |
|
||||
| 2026-07-07 | [DB as single source of truth for agent knowledge](2026-07-07-db-as-source-of-truth.md) |
|
||||
|
||||
## Conventions
|
||||
|
||||
|
||||
Reference in New Issue
Block a user