wiki-vault/Sources/Homelab/herbylab-project-management.md

8.0 KiB
Raw Permalink Blame History

created path project status tags type updated
2026-05-11 Sources/Homelab herbylab-project-management active
mcp
automation
n8n
obsidian
project-plan 2026-05-11

Goal

Extend herbylab MCP to manage projects and notes as first-class artifacts backed by Postgres, with Trello as the visual projection. Vault remains the immutable content store in Phase 1; Postgres becomes sole source in a later phase.

Solves the pain of status hygiene and project visibility — 44+ vault entries, almost all flagged active, no aggregate view, no real query surface, frontmatter status edits feel wrong.

Locked Decisions

Three-layer model. Mirrors Trellis:

  • Vault md files — content, narrative, immutable. Feeds wiki-vault.
  • Postgres (herbylab schema) — state, metadata, queryable index.
  • Trello — visual projection of project state. Read-mostly.

Single MCP, not split. Project tools live alongside vault tools in herbylab. One connector, one tool surface.

Polymorphic tool shape. create_artifact(kind, ...) and update_artifact(kind, slug, ...) dispatch on kind. Adds future artifact types without growing the tool surface.

Postgres co-located with Trellis. Same instance, separate schema (herbylab.*). Trellis's schema-namespace pattern leaves room for this by design.

Phase 1: dual-write, immutable entries.

  • create_artifact writes Postgres row + vault file atomically.
  • No content updates via MCP — entries immutable once created.
  • update_artifact only mutates metadata (status, tags, title, summary, domain).
  • Vault stays the read surface for Obsidian and wiki-vault.
  • New phases/addenda get new files, matching existing pattern (ob1-deployment + ob1-main-deployment).

Phase N: vault retires, Postgres becomes sole source.

  • Viewer TBD — wiki-vault adaptation or new web UI served from Postgres.
  • Out of scope for this plan.

Source of truth for status: Postgres. Status changes mid-conversation via update_artifact — never a file edit.

Status propagates via NOTIFY → n8n → Trello. Postgres trigger fires pg_notify('project_changed', ...) on insert/update. n8n LISTENs, projects to Trello.

Frontmatter scope simplifies. Vault frontmatter holds identity (project, type, path, tags, created). Status and updated leave frontmatter — Postgres concerns.

Schema (Phase 1)

CREATE SCHEMA herbylab;

CREATE TYPE herbylab.artifact_kind AS ENUM ('note', 'project');
CREATE TYPE herbylab.project_status AS ENUM ('active', 'paused', 'blocked', 'completed', 'archived');
CREATE TYPE herbylab.project_domain AS ENUM ('homelab', 'dev', 'venture', 'reference');

CREATE TABLE herbylab.artifacts (
    slug         TEXT NOT NULL,
    kind         herbylab.artifact_kind NOT NULL,
    title        TEXT NOT NULL,
    domain       herbylab.project_domain,
    tags         TEXT[] DEFAULT '{}',
    vault_path   TEXT NOT NULL,
    body         TEXT NOT NULL,
    created_at   TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at   TIMESTAMPTZ NOT NULL DEFAULT now(),
    PRIMARY KEY (kind, slug)
);

CREATE TABLE herbylab.projects (
    slug            TEXT PRIMARY KEY,
    status          herbylab.project_status NOT NULL DEFAULT 'active',
    summary         TEXT,
    trello_card_id  TEXT,
    FOREIGN KEY (kind, slug) REFERENCES herbylab.artifacts (kind, slug)
        DEFERRABLE INITIALLY DEFERRED
);

CREATE INDEX idx_artifacts_kind ON herbylab.artifacts(kind);
CREATE INDEX idx_artifacts_tags ON herbylab.artifacts USING GIN(tags);
CREATE INDEX idx_projects_status ON herbylab.projects(status);

body stores the markdown blob. Vault file holds the same content. Dual-write keeps them in lockstep; immutability eliminates drift risk.

NOTIFY trigger

CREATE OR REPLACE FUNCTION herbylab.notify_project_change() RETURNS TRIGGER AS $$
DECLARE payload JSON;
BEGIN
    payload := json_build_object(
        'op',     TG_OP,
        'slug',   COALESCE(NEW.slug, OLD.slug),
        'status', COALESCE(NEW.status, OLD.status)
    );
    PERFORM pg_notify('project_changed', payload::text);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER projects_notify
AFTER INSERT OR UPDATE OR DELETE ON herbylab.projects
FOR EACH ROW EXECUTE FUNCTION herbylab.notify_project_change();

Tool surface

Vault tools (existing, unchanged):

  • save_vault_note
  • get_vault_project
  • check_vault_file
  • list_vault_projects
  • get_vault_schema

Artifact tools (new):

  • create_artifact(kind, slug, title, vault_path, content, domain?, tags?, summary?) — writes Postgres row + vault file. For kind="project", also inserts projects row with default status='active'.
  • update_artifact(kind, slug, **fields) — mutates metadata only (status, title, tags, summary, domain). Never touches body or vault file.
  • get_artifact(kind, slug) — returns row + linked file path.
  • list_artifacts(kind?, status?, domain?, tag?) — filtered query.

list_vault_projects deprecates in favor of list_artifacts once cutover completes.

Open Items

  • Postgres deployment shape — depends on Trellis decision (LXC vs container)
  • Migration tooling — match whatever Trellis lands on
  • Trello board layout — single board with status lists vs board per domain
  • Duplicate cleanup before backfill: ob1-deployment + ob1-main-deployment, editor-stack-upgrade (Homelab + Venture), content-hub-phase5-architecture + -planning
  • Naming pin-down: memory has both herbylab and herby-dev — settle on one

Phases

Phase 1 — Infrastructure

  • Postgres instance available with herbylab schema on the Trellis-shared instance
  • Migration tooling chosen (match Trellis decision)
  • Apply schema, types, indexes, NOTIFY trigger

Phase 2 — MCP tools

  • create_artifact (dual-write, transactional)
  • update_artifact (metadata only)
  • get_artifact, list_artifacts (read paths)
  • Tag normalization mirroring Trellis pattern

Phase 3 — Trello projection

  • Trello board structure: lists for active, paused, blocked, completed. Labels for domain.
  • n8n workflow: Postgres LISTEN node on project_changed
  • Branch on op: INSERT → create card, UPDATE → move/edit card, DELETE → archive
  • Store trello_card_id back to projects row on creation

Phase 4 — Backfill and cutover

  • Script walks existing vault project-plan files, populates artifacts + projects rows
  • Status defaults to current frontmatter value (42 of 44 are active)
  • Manual triage pass in Trello — drag to honest columns
  • Strip status / updated from vault frontmatter going forward
  • Backfill of existing frontmatter optional

Phase N — Vault retirement

  • Deferred. Revisit once Phase 14 are operational and read patterns are clearer.

Notes

Why dual-write in Phase 1. Keeps two readers (Obsidian, wiki-vault) functional during transition. Immutability of entries kills the main drift risk — entries land once, neither side updates content. Update tool only touches metadata, which lives only in Postgres.

Why immutable entries. Matches existing behavior — new phases get new files (-phase2, -addendum) rather than edits. Codifying this as a constraint removes a class of bugs and makes Phase N (Postgres-only) trivially achievable later.

Why Postgres co-located with Trellis. Trellis's plan explicitly chose schema-namespacing to leave room for consolidation. Same instance, separate schemas, no new infrastructure. Backup/recovery story is shared.

Why not Trellis itself for project tracking. Trellis is for conversation threads — coarse session-level checkpoints, append-only events, paused/resumed work. Projects are different: durable, named, status-bearing, visualized externally. Different lifecycle, different reader, different surface.

Why the polymorphic tool shape. Avoids tool sprawl. Adding a future artifact type (task, decision log, etc.) is a kind value, not a new MCP tool. Tradeoff is a fatter schema in the tool description.