test
  • Python 99.8%
  • Mako 0.1%
Find a file
forgejo_admin 545df120bb
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
fix: add retry loop to CI wget for update-kustomize-tag (#242)
2026-03-28 20:02:24 +00:00
alembic feat: data migration to retype doc notes to new NoteType values (#230) 2026-03-28 04:02:06 +00:00
k8s build: automatic update of pal-e-docs 2026-03-21 16:41:57 +00:00
scripts feat: standardize ruff config to line-length=88, select=E,F,I,W (#240) 2026-03-28 19:08:57 +00:00
src/pal_e_docs feat: standardize ruff config to line-length=88, select=E,F,I,W (#240) 2026-03-28 19:08:57 +00:00
tests feat: standardize ruff config to line-length=88, select=E,F,I,W (#240) 2026-03-28 19:08:57 +00:00
.gitignore Remove .argocd-source-* from .gitignore (#163) 2026-03-14 18:19:05 +00:00
.pre-commit-config.yaml feat: responsive nav, mobile breakpoints, CSS cleanup, pre-commit config (#48) 2026-02-27 16:24:40 +00:00
.woodpecker.yaml fix: add retry loop to CI wget for update-kustomize-tag (#242) 2026-03-28 20:02:24 +00:00
alembic.ini Scaffold pal-e-docs FastAPI service (#4) 2026-02-24 14:03:24 +00:00
CLAUDE.md docs: update repo name references after rename (#219) 2026-03-27 05:35:59 +00:00
Dockerfile Scaffold pal-e-docs FastAPI service (#4) 2026-02-24 14:03:24 +00:00
pyproject.toml feat: standardize ruff config to line-length=88, select=E,F,I,W (#240) 2026-03-28 19:08:57 +00:00
README.md docs: update repo name references after rename (#219) 2026-03-27 05:35:59 +00:00

pal-e-api

The platform's knowledge service (repo formerly pal-e-docs — renamed to match its role as the FastAPI backend). Every SOP, architecture decision, project plan, and onboarding guide lives here — queryable by AI agents, readable by humans, and versioned in a database that never bloats the repo.

Why This Exists

The pal-e platform is a self-hosted system where the tools you use to build are the tools you ship. But tools without knowledge are just programs. pal-e-docs is what turns the platform into a system that remembers:

  • AI agents query it for context at the start of every session — SOPs, project state, architecture decisions — so they never start cold
  • Humans browse the same data through a web frontend — same source of truth, different view
  • Services query it for platform conventions — auth patterns, deployment steps, naming conventions
  • Plans are stored as notes with tags and cross-links, forming a chain where each plan inherits context from the one before it

Without pal-e-docs, knowledge lives in scattered markdown files, stale Notion pages, and context windows that evaporate between sessions. With it, knowledge accumulates, stays current, and is always one API call away.

Architecture

Storage: SQLite on PVC

The database is a single SQLite file on a Kubernetes PersistentVolumeClaim. The repo contains only schema migrations and application code — it never grows as content is added.

Why SQLite:

  • Single writer (admin-only), single replica, read-heavy — a perfect fit
  • No separate database Deployment, Service, PVC, or secrets to manage
  • WAL mode enables concurrent reads during writes
  • Backup is a file copy (Litestream for continuous replication later)

API: FastAPI

A REST API serves notes, projects, and tags. Tag intersection queries (?tags=auth,basketball-api) return exactly the context needed — no scanning file trees, no token-heavy bulk fetches.

Auth: pal-e-auth (shared platform package)

Public notes are readable without authentication. Write operations require the admin role via JWT from the shared pal-e-auth package. Currently there is one admin (Lucas); the "single writer" design reflects this — SQLite's single-writer lock is a feature, not a limitation.

Data Model

Project        Note             Tag
───────        ────             ───
id             id               id
name       ◄── project_id       name (unique)
slug           title
platform       slug             NoteTag
repo_url       html_content     ───────
               is_public        note_id
               created_at       tag_id
               updated_at
                                NoteLink
NoteRevision                    ────────
────────────                    source_id
id                              target_id
note_id
html_content
revised_by      ← PR URL or commit SHA
revised_at
revision_number

Projects organize notes by codebase. A project maps to a repo (GitHub, Forgejo, or local).

Notes store HTML content (rendered from Markdown on write, stored as HTML for fast retrieval). They can belong to a project or stand alone as general knowledge. Public notes are readable by anyone; private notes require auth.

Tags enable cross-cutting queries. A note about auth in basketball-api has tags auth and basketball-api. Querying ?tags=auth returns all auth-related notes across all projects.

NoteLinks connect notes bidirectionally — zettelkasten-style cross-references.

NoteRevisions track every edit with a revised_by field linking back to the PR or commit that prompted the change. This is documentation versioning without git tracking content.

Three Roles

1. Platform Knowledge Base

SOPs, conventions, and architecture decisions live as tagged notes:

  • GET /notes?tags=sop,active — all current SOPs
  • GET /notes?tags=onboarding — how to deploy a new service
  • GET /notes?tags=architecture,auth — auth design decisions

These are the notes that claude-custom hooks reference on session startup. When an SOP changes, the note is edited in pal-e-docs and the next session picks it up — no commit to claude-custom needed.

2. Planning Continuity

Plans are not isolated documents — they form a chain. Each plan reads the previous plan's Vision and Next Plan Seeds, carries forward the decision log, and produces the next iteration. No session starts cold.

Plans are notes with structured content:

  • Tagged with plan + the project slug
  • Cross-linked to the previous plan (via NoteLink)
  • Decisions accumulate as tagged notes (decision + project)
  • Next Plan Seeds from one plan become candidate phases in the next

The /plan skill reads the latest plan note, carries forward Vision and Seeds, and writes the new plan back. The SOP enforces the template, the template enforces continuity. Context flows session to session without loss.

3. Project Documentation

Every project (basketball-api, pal-e-auth, claude-custom) has a project entry with notes:

  • GET /projects/basketball-api/notes — all basketball-api docs
  • GET /notes?tags=basketball-api,stripe — Stripe integration specifics
  • GET /notes/{slug}/revisions — when was this doc last updated, and why

PR templates across all repos remind: "Did this PR change behavior documented in pal-e-docs? Update the relevant note."

How claude-custom Connects

Session Start
     │
     ▼
~/.claude/CLAUDE.md (bootstrap: identity, platform URL)
     │
     ▼
Session-start hook queries pal-e-docs:
  GET /notes?tags=sop,active → current SOPs
     │
     ▼
Agent has: identity + SOPs + available tools (hooks, skills, MCP)
     │
     ▼
Agent assesses: what repos exist, what's deployed, what's in progress
     │
     ▼
Work begins with full platform context

claude-custom controls behavior — hooks enforce the SOP, skills provide workflows, commands provide shortcuts.

pal-e-docs controls knowledge — what the SOPs say, what decisions have been made, what the current state of each project is.

This separation means SOPs can evolve through pal-e-docs edits without touching claude-custom. The hooks enforce that SOPs are followed; pal-e-docs defines what the SOPs are.

API Endpoints

Method Path Auth Description
GET /projects none List all projects
GET /projects/{slug}/notes none/admin Notes in a project
GET /notes none/admin Search by tags: ?tags=auth,sop
GET /notes/{slug} none/admin Note with HTML content
POST /notes admin Create note
PUT /notes/{slug} admin Update note (creates revision)
DELETE /notes/{slug} admin Delete note
GET /notes/{slug}/links none All linked notes (both incoming and outgoing)
PUT /notes/{slug}/links admin Set outgoing links (reverse links created automatically)
GET /notes/{slug}/revisions none Revision history
POST /projects admin Create project
PUT /projects/{slug} admin Update project
GET /tags none List all tags
GET /healthz none Health check
GET /metrics none Prometheus metrics

Public notes (is_public = true) are readable without auth. Private notes and all write operations require admin JWT.

Deployment

Deployed via the standard pal-e-services GitOps pipeline:

# In pal-e-services k3s.tfvars
pal-e-docs = {
  forgejo_repo = "forgejo_admin/pal-e-api"
  image_repo   = "pal-e-docs/api"
  port         = 8000
  funnel       = true
}

tofu apply creates the namespace, Harbor project, ArgoCD app, and Tailscale funnel. Woodpecker CI builds and pushes on every merge to main. ArgoCD syncs.

Live at: https://pal-e-docs.tail5b443a.ts.net

Tech Stack

  • Runtime: Python 3.12 + FastAPI + Uvicorn
  • Database: SQLite with WAL mode, on Kubernetes PVC
  • ORM: SQLAlchemy + Alembic migrations
  • Auth: pal-e-auth (shared JWT + Google OAuth package)
  • CI: Woodpecker CI → Harbor → ArgoCD
  • Monitoring: Prometheus metrics + Loki logs via platform stack

Roadmap

  • Scaffold: FastAPI + SQLAlchemy + SQLite + Alembic + k8s manifests
  • Auth integration: pal-e-auth for admin write access
  • Seed initial content: platform SOPs, architecture decisions, project docs
  • MCP tool: Claude reads/writes notes directly via MCP server
  • Frontend: human-readable UI browsing the same SQLite data
  • claude-custom hook: session-start SOP injection from pal-e-docs API
  • PR templates: "update pal-e-docs" reminder across all repos
  • Litestream: continuous SQLite backup/replication
  • Notion migration: move PROJ-17 product docs to pal-e-docs