Wikantik System Architecture

Wikantik began as a fork of Apache JSPWiki and has been re-architected into something its ancestor never was: an agent-grade knowledge platform where every page is simultaneously a human-readable document, a retrieval target, a node in two distinct graphs, and a set of machine-callable tools. This page is the deep, current (2.4.x) reference for how the system is built — the modules, the data model, the retrieval and knowledge layers, the agent surface, the rendering and security pipelines — followed by an honest assessment of where it is strong, where it is weak, and where it should go next.

The guiding principle, repeated throughout the design, is human–machine parity: a human editing in the browser and an AI agent calling an MCP tool go through the same save pipeline, the same validation, the same permission checks, and read from the same retrieval index. There is no separate "API content." That single decision shapes almost everything below.

1. System at a Glance

                         ┌──────────────────────────────────────────┐
   Humans  ──browser──▶  │  React SPA (Vite/TS)  +  SSR head/meta   │
                         └───────────────┬──────────────────────────┘
                                         │  HTTP
   Agents  ──MCP/HTTP──▶ ┌──────────────▼──────────────────────────┐
   Crawlers ─REST/RDF──▶ │      Servlet filter pipeline            │
                         │  CSRF · CORS · CSP · auth · SPA routing │
                         └──────────────┬──────────────────────────┘
            ┌───────────────────────────┼───────────────────────────────┐
            ▼               ▼            ▼             ▼                  ▼
      /api/* REST     /wikantik-     /knowledge-   /scim/v2/*     /sparql · /id/*
      /admin/*        admin-mcp        mcp                         /export/*  (RDF)
            │          (27 tools)     (21 tools)     │                  │
            └───────────────────────────┬────────────┴──────────────────┘
                                         ▼
                         ┌───────────────────────────────────────────┐
                         │            WikiEngine (orchestrator)      │
                         │  PageManager · RenderingManager · Search  │
                         │  FilterManager · PluginManager · Attach…  │
                         └───────────────┬───────────────────────────┘
                  ┌──────────────────────┼───────────────────────────┐
                  ▼                      ▼                            ▼
        ┌──────────────────┐  ┌────────────────────┐    ┌──────────────────────┐
        │  Page corpus     │  │   PostgreSQL       │    │  Ontology (Jena TDB2) │
        │  Markdown + YAML │  │  users · policy ·  │    │  RDF/OWL T-Box +      │
        │  (file provider, │  │  KG (kg_*) ·       │    │  projected A-Box      │
        │   versioned)     │  │  pgvector embeds · │    │  (SPARQL/SHACL)       │
        │                  │  │  citations         │    │                       │
        └──────────────────┘  └────────────────────┘    └──────────────────────┘

The wiki deploys as a single WAR into Tomcat 11. It is a modular monolith: 22 Maven modules with strict dependency boundaries (enforced by an ArchUnit decomposition test), not a microservice fleet. Heavy AI work (embedding, entity extraction, LLM judging) is delegated to external services (Ollama / an OpenAI-compatible endpoint) and to a companion CLI, so the engine itself stays a normal JEE application.

2. The Module Reactor

Modules are layered: wikantik-api defines the contracts (ports); everything else depends inward toward it. The ArchUnit DecompositionArchTest freezes the allowed getManager() call sites and the dependency direction so the decoupling cannot silently rot.

ModuleLayerResponsibility
wikantik-bombuildBill-of-materials pinning shared dependency versions.
wikantik-apiports/domainManager interfaces, frontmatter + schema model, Page Graph + Knowledge Graph + bundle contracts, ClusterPath. No implementations.
wikantik-mainengineWikiEngine, rendering, providers, auth, search, references, entity extraction, math parser, derived-page reflow.
wikantik-eventcoreDecoupled WikiEvent bus.
wikantik-utilcoreHelpers, crypto utilities.
wikantik-cache / -cache-memcachedcoreEhCache render/object caches; Memcached adapter for distributed deploys.
wikantik-httpedgeServlet filters: CSRF, CORS, CSP, security headers, SPA routing, backpressure, rate limiting, the /wiki/{slug}?format=md\|json content filter.
wikantik-restedgeREST /api/* (33 servlets, incl. POST /api/ingest, GET /api/bundle, GET /api/briefing) and admin /admin/* (26 servlets: audit, drift, ontology, derived, kg-policy, connectors, cluster rename). Public RDF servlets.
wikantik-mcp-coreagentShared MCP substrate — McpTool, endpoint bootstrap, access filter, and the two tools both MCP servers register. Extracted to break the wikantik-knowledgewikantik-admin-mcp module cycle.
wikantik-admin-mcpagentMCP server at /wikantik-admin-mcp — 27 write/analytics/KG-curation tools.
wikantik-knowledgeagent + brainMCP server at /knowledge-mcp (21 read tools when fully wired) and the KG service: pgvector embeddings, co-mention graph, hybrid retriever, the context-bundle assembler.
wikantik-toolsagentOpenAPI 3.1 tool server /tools/* (2 tools) for non-MCP clients.
wikantik-scimagentSCIM 2.0 provisioning /scim/v2/* — IdP-driven Users + Groups.
wikantik-ontologyknowledgeApache Jena: the wikantik: T-Box (wikantik.ttl), SHACL shapes, Postgres→RDF projectors, TDB2 store.
wikantik-ingestingestionPure Tika/flexmark document extraction for derived pages (isolates PDFBox/POI from the engine).
wikantik-connectorsingestionExternal-source connector runtime — seven connector types syncing into derived pages via a shared SyncOrchestrator, with DB-backed configs and an encrypted credential store.
wikantik-extract-clitoolingOffline entity-extractor, the derived-page batch ingester, and CorpusDivergenceCli.
wikantik-observabilityopsIn-app health checks, Prometheus metrics, request correlation (the deployment monitoring stack lives in a separate jakemon repo).
wikantik-frontendUIReact SPA (Vite/TS): reader, editor, admin panel, KG + Page Graph viewers. Not a Maven module — wikantik-war drives the npm build and bundles the output.
wikantik-warbundlePackages the React build + wires every servlet/filter into one deployable.
wikantik-wikipagescontentDefault pages shipped with a fresh install.
wikantik-it-teststestCargo-launched Selenide + REST + custom-provider integration suites.
   wikantik-api  ◀───────────  (everyone depends inward on the ports)
        ▲
        │  implements
   wikantik-main  ◀──  http · rest · mcp-core · admin-mcp · knowledge · tools · scim · ontology
        ▲
        └──  war  ──packages──▶  frontend + all servlets/filters

3. Three Graphs Over One Corpus

A recurring source of confusion — deliberately disambiguated in the codebase — is that Wikantik maintains three distinct edge types. Conflating them is treated as a code smell.

The bare word "graph" is avoided in identifiers; code always says Page Graph, Knowledge Graph, or kg_*/pagegraph.

3a. Cluster Taxonomy

Cluster membership is a projection of frontmatter, not a filesystem hierarchy (ADR-0009). A cluster exists if and only if exactly one page carries type: hub plus a cluster: value — the hub page is the authoritative declaration. Non-hub pages may hold several memberships (cluster: is scalar-or-list; the first entry is primary and drives breadcrumbs, JSON-LD articleSection, sidebar placement and the embedding prefix). Sub-clusters are parent/child, one level deep, permanently.

Comparison is segment-aware through a single ClusterPath class — a bare startsWith would report machine-learning-ops as a descendant of machine-learning and silently merge two unrelated clusters. Membership is transitive and resolved at query time, so re-parenting needs no reindex. Structural conflicts (duplicate declaration, headless cluster, undeclared cluster, clusterless hub, multi-cluster hub) surface on the /admin/drift burn-down rather than blocking saves; rename_cluster rewrites a cluster across every member plan-first.

The rejected alternative is recorded deliberately: a path in frontmatter is data — validatable, re-projectable, multi-valued, revertable per page — while a path in the filesystem would bind page identity, slug history, OLD/, attachments, URLs and wikilinks to one axis.

4. The Retrieval Stack

Retrieval is hybrid: lexical BM25 (Apache Lucene, full-page index) fused with dense vector search (pgvector / Lucene-HNSW) via reciprocal rank fusion, with a fail-closed fallback to BM25 if the dense side is unavailable.

 query
   │
   ├─▶ BM25 (Lucene)        ┐
   │                         ├─ reciprocal rank fusion ─▶ candidates
   └─▶ dense ANN (pgvector / ┘                                │
         lucene-hnsw)                                          ▼
                                              de-dup · version-pin · cite
                                                                │
                                                                ▼
                                          context bundle  (GET /api/bundle,
                                          assemble_bundle MCP) — ranked,
                                          cited sections, NO answer synthesis

Two measured levers, not guesses, moved global section recall@12 from ~0.60 to ~0.74:

  1. Chunker heading-fidelity fixContentChunker force-emits its merge-forward buffer at every heading boundary so early/first-H2 sections keep their own heading_path (they were previously mis-attributed and mis-cited); plus a sub-floor fragment merge and small overlap.
  2. Contextual document embeddingsEmbeddingTextBuilder.forDocument prepends Page: {title} | Cluster: {cluster} | Section: {heading} + the frontmatter summary before embedding. This is the reason title/cluster/summary are first-class retrieval levers and not just SEO metadata.

The context bundle is RAG-as-a-Service done deliberately: it returns a ranked, de-duplicated, version-pinned, citation-bearing set of sections — and never synthesizes an answer (ADR-0001). Its default source is a global dense+BM25 chunk hybrid, not a page-gated retrieve, because page-gating drops relevant sections whose page ranks outside the top-N.

Levers that were measured and rejected: the LLM listwise reranker, HyDE, doc2query (all left off by default), and the KG graph rerank — which was deleted outright in 2026-07. None moved recall; some actively hurt it.

5. The Knowledge & Ontology Layer

The Knowledge Graph is a property graph over wiki content stored in PostgreSQL: entities and edges extracted by an LLM (a reasoning model run with thinking disabled for clean structured JSON), embedded with pgvector, with a human-in-the-loop proposal workflow before anything is written back. Cluster-primary inclusion policy keeps it default-exclude with a kg_include: frontmatter override; policy lookup walks cluster ancestors (most specific wins) and resolves fail-closed across multiple memberships — an explicit EXCLUDE on any membership wins outright.

Above the KG sits a formal RDF/OWL ontology (wikantik-ontology, Apache Jena):

The ontology is, in effect, a projection of the same knowledge the KG holds — the SEO JSON-LD @type on each page is even re-sourced from the ontology's inferred schema.org type, with a test asserting the two faces can't silently drift.

6. The Agent Surface

Agents are first-class clients, not an afterthought. Every surface enforces the same ACLs as the human UI.

EndpointProtocolWhat
/wikantik-admin-mcpMCP (Streamable HTTP)27 write/analytics/KG-curation tools (incl. admin-bypass reads, orphan listing, real-traffic query log, bulk cluster rename).
/knowledge-mcpMCP21 read tools when fully wired: hybrid retrieval, KG traversal, schema discovery, structural-spine nav, agent-grade page projection, batched reads, get_ontology, sparql_query, list_stale_citations, get_briefing, and assemble_bundle. Several register conditionally on what a deployment has wired.
/tools/*OpenAPI 3.12 tools (search_wiki, get_page) for OpenWebUI-style non-MCP clients.
/scim/v2/*SCIM 2.0IdP-driven Users + Groups provisioning (SCIM Groups never grant Admin).
/api/*, /admin/*REST/JSON33 + 26 servlets; GET /api/bundle, GET /api/briefing, POST /api/ingest, /api/changes?since= feed.
/sparql, /id/{type}/{id}, /export/*RDFPublic read-only ontology: SPARQL, per-resource JSON-LD/Turtle dereferencing, full dumps.

7. Storage Model — The Hybrid

Wikantik deliberately splits its state across three stores so each does what it is good at:

  1. Page corpus — CommonMark Markdown with mandatory YAML frontmatter, behind a PageProvider (file-system + versioning provider). This is the source of truth for content and gives free version history. Production keeps its corpus independent of deploys (content edits go through MCP/REST, not a redeploy).
  2. PostgreSQL — the structured backbone: users, database-backed policy grants and groups, the kg_* Knowledge Graph, pgvector embeddings, and the citations table. Schema is versioned as numbered idempotent migrations (currently V001–V049).
  3. Jena TDB2 — the materialized RDF A-Box for the ontology endpoints, rebuilt incrementally from Postgres.

The frontmatter is the contract that ties these together: a server-authoritative FrontmatterSchema validates every save (malformed YAML 422s; field-value issues are advisory warnings so the existing corpus still saves), and those same fields drive retrieval embeddings, JSON-LD, feeds, and the News Sitemap.

8. Rendering Pipeline

 Markdown source
   │  MarkdownParser → Flexmark AST
   ├─ pre/post filters (FilterManager): structural spine, schema validation,
   │   math validation, citation parsing, frontmatter → KG projection
   ├─ plugins  [{Plugin}]()  (auto-normalized to [{Plugin}]() for Flexmark)
   ▼
 MarkdownRenderer → HTML  ──▶  SSR (title/meta/JSON-LD head) + React SPA hydrate

The same content is served three ways: rendered HTML for browsers (with an SSR head carrying <title>, meta, and JSON-LD so crawlers and the React app agree), raw ?format=md|json for RAG ingestion, and projected for-agent views via MCP. SSR + the SPA must agree on the head, which is why a soft-404 class of bug (SPA refetch wiping the SSR body) is guarded explicitly.

9. Security Model

10. Strengths

11. Critique — Where It Is Weak

An honest architecture page names its own debts.

12. Areas for Future Growth

See Also