跳到正文
GitHub·· 6 天前AI 评分60

Glad Labs 开源本地内容生成引擎 Poindexter,17 道 QA 环节过滤草稿

Glad-Labs/poindexter

AI 导读

Glad Labs 在 GitHub 开源 Poindexter,一个在本地 GPU 上运行的内容生成引擎,从选题、研究、写作到多模型对抗式 QA 与发布全流程无需 API 费用,已支撑 gladlabs.io 上 220+ 篇线上文章。

正文

Poindexter — the open-source AI content factory that runs on your PC

One engine that discovers topics, researches them, writes long-form posts, tears them apart in review, and publishes the survivors — on your GPU, with zero API costs.

License: Apache 2.0 Unit tests Release Tests Status Built by Glad Labs LLC

Quick start · What it does · The QA gauntlet · Architecture · Docs · Pro


This is not a demo repo. Poindexter™ is the production system behind gladlabs.io — 220+ live posts and counting, every one generated, reviewed, and published by this pipeline on a single PC. Here is the operator's actual view of it running:

Operator console, live: a post mid-pipeline in the system pulse, KPI row, approval inbox with QA verdicts, then the TRACE view and the animated system map

The operator console, recorded live: a post mid-generation, the approval inbox with per-task QA verdicts, pipeline traces, and the system map. The console is the one Pro-only surface — the free engine exposes the same data through the Grafana boards, the CLI, and the REST API below.

Grafana Pipeline dashboard — approval queue with average quality 93, 8 approved, 4 auto-rejected, live pipeline activity

The Pipeline dashboard (ships with the repo): drafts arrive scored, weak ones are auto-rejected, survivors wait for your one-click approval.

Quick start

From a clean machine to your first post in about an hour — most of it one-time downloads: the stack's images build once and the models pull once.

Prereqs: Docker with Compose v2.24+ · Ollama · Python 3.13 (3.14 is not supported yet) · about 50 GB of free disk (31 GB of models plus ~13 GB of images) · an NVIDIA GPU is strongly recommended: the default writer and critic are 27B and 14B models, so on a smaller card Ollama spills layers to the CPU and a post takes much longer (CPU-only works, slowly). Windows: run from Git Bash or WSL2. Linux: Ollama has to listen beyond loopback — see the note under the commands.

# 1. Clone
git clone https://github.com/Glad-Labs/poindexter.git && cd poindexter

# 2. Install the CLI into a Python 3.13 virtualenv (Windows Git Bash: py -3.13 -m venv .venv && source .venv/Scripts/activate)
python3.13 -m venv .venv && source .venv/bin/activate
pip install -e src/cofounder_agent

# 3. Bootstrap — generates secrets, starts the stack's Postgres, runs migrations, mints your OAuth client
poindexter setup --auto

# 4. Pull the models the default pipeline calls (one-time, about 31 GB)
ollama pull gemma3:27b && ollama pull phi4:14b && ollama pull llama3:latest && ollama pull nomic-embed-text

# 5. Start everything (the first run builds the images — allow several minutes)
bash scripts/start-stack.sh up -d

# 6. Queue your first post (waits for the worker to finish booting)
poindexter tasks create "Why Docker changed everything" --niche starter-blog

Linux: let the containers reach Ollama. The stack calls Ollama at host.docker.internal:11434, which on Linux is the Docker bridge gateway, and a default Ollama install listens on loopback only, so every model call is refused. Once, before step 5:

sudo mkdir -p /etc/systemd/system/ollama.service.d
printf '[Service]\nEnvironment="OLLAMA_HOST=0.0.0.0"\n' | sudo tee /etc/systemd/system/ollama.service.d/override.conf
sudo systemctl daemon-reload && sudo systemctl restart ollama

That makes Ollama listen on every interface, so firewall port 11434 if the machine sits on a network you don't trust. Docker Desktop on Windows and macOS needs none of this.

Then watch it work:

  • Prefect — localhost:4200, the content-generation deployment picks the task up within two minutes
  • Grafana — localhost:3000, the Pipeline dashboard fills in as stages complete
  • Terminal — poindexter tasks list for the queue, docker logs -f poindexter-prefect-worker for the run itself

The draft lands in your approval queue with its QA scores attached: poindexter tasks list --status awaiting_approval. Approve it with poindexter tasks approve <id>, publish it with poindexter tasks publish <id>, and read it at localhost:8002/site/: a fresh install publishes to a local folder the worker serves there, so no bucket, account or frontend is needed. That's the loop.

If the task ends rejected instead: a finished draft that scores under min_curation_score (seeded at 75) is turned away before it reaches the queue, and a writer smaller than the default can land there. poindexter tasks list --status rejected shows its Q: score, and the troubleshooting guide shows how to read the verdict and lower the bar (poindexter settings set min_curation_score 50; 0 turns the auto-curator off).

The whole sequence above runs on a clean GitHub runner as a CI job (quickstart-e2e) that has to end with the post readable at /site/. The runner has no GPU, so the job stands one tiny model in for the three large ones and zeroes that bar; the rest is your commands, verbatim.

Which model does what — and what to upgrade first

The four models step 4 pulls are the ones the default pipeline calls. Nothing pulls a model on demand: a model Ollama does not have fails the call that names it, so this list is derived from the seeded settings and a CI job fails when it drifts (poindexter setup --auto also prints the command for your database's settings).

Model Size Role
gemma3:27b 17 GB Writer, and everything seeded with it: self-review, structured extraction, image prompts, the fallback writer, the video director
phi4:14b 9 GB Adversarial QA critic — the hard quality gate — and the Ragas and DeepEval judges
llama3:latest 4.7 GB Podcast and video script drafting
nomic-embed-text 274 MB Embeddings for semantic search, originality checks and memory retrieval

The critic is a different model family from the writer on purpose — cross-model QA means their biases don't cancel. On a smaller card, Ollama runs part of the 27B writer on the CPU, which is slower but works.

Optional — pull one when you switch its feature on:

Model Size Used for
qwen3-vl:30b-a3b-instruct ~20 GB Image QA and captions. Only called when a post has images (--profile image-gen or a Pexels key).
qwen2.5:32b ~20 GB Fallback critic when phi4:14b is unavailable.
granite4.2:3b small The brain's alert triage.
qwen2.5:7b 4.7 GB Console chat and the voice agent.

The writer is the one model worth upgrading. Set pipeline_writer_model to any Ollama model you have:

ollama pull qwen3:30b          # 18 GB — best speed/quality balance publicly available
ollama pull qwen3.5:35b        # 23 GB — stronger prose, slower
ollama pull llama3.3:70b       # 42 GB — highest quality, needs 48 GB+ VRAM or CPU offload
ollama pull glm-4.7:9b         # 6 GB — lighter fallback for <16 GB VRAM

Every model routing decision (writer / critic / research / summarizer / embedder) lives in app_settings and can be swapped at runtime — no restart, no redeploy. Cloud models (Anthropic, OpenAI, Groq, OpenRouter) are an opt-in plugin gated by a spend guard.

What it does

One engine, eight jobs:

  1. Discovers trending topics from HackerNews, Dev.to, and your niche feeds
  2. Researches each topic with deep web search and source verification
  3. Writes long-form posts using local LLMs — or cloud models via the optional LiteLLM plugin
  4. Reviews every draft with multi-model adversarial QA (more on that below)
  5. Validates against hallucinations — catches fake people, stats, quotes, impossible claims
  6. Publishes to any frontend via static JSON export (push-only headless CMS), or with no cloud account at all to a local folder you read at localhost:8002/site/
  7. Generates podcast episodes, AI images, and short text-to-video clips (alpha, opt-in)
  8. Monitors itself with Grafana dashboards, self-heals via a watchdog daemon, alerts on Telegram/Discord

Run it on your machine. Own your data. No cloud lock-in.

Built to reject its own work

Most AI content tools optimize for output volume. Poindexter optimizes for curation: it generates candidates, then makes each one survive 17 QA rails — cross-model LLM critics, DeepEval and Ragas evaluations, deterministic anti-hallucination validators, citation verification against the research corpus, and vision QA on every generated image. Roughly half of all drafts don't make it.

Grafana QA Rails panel — average score per reviewer across 17 signals, from internal_consistency at 100 down to content_originality at 19.1

Real 30-day per-reviewer averages from the QA Rails dashboard — more reviewers than rails, because the DeepEval rail scores three and the citation rail two. The strict rails at the bottom are why the output doesn't read like AI slop — speed comes from generating more candidates and filtering hard, not from lowering the bar.

Every rail is DB-configurable: advisory or blocking per rail, thresholds tunable at runtime, and a bounded rescue cycle gives near-miss drafts one revision pass before the hard reject.

It monitors itself, too

The stack treats itself like production infrastructure. A standalone watchdog daemon (the "brainstem") checks every service on a 5-minute cycle, restarts what it can, and pages you on Telegram/Discord for what it can't. The doctor command aggregates every health probe into one score:

poindexter doctor output — health score 80/100, one FAIL flagged (pipeline throughput drop), 26 probes OK

Real output. Note the FAIL: the system caught its own throughput drop and said so — you find out from the tool, not from silence.

And the output side is a real website, not a JSON blob in a bucket somewhere:

gladlabs.io — the production site published by Poindexter

gladlabs.io — built on the static JSON export. Bring any frontend: Next.js, Hugo, Astro, or a single HTML file.

Who this is for

  • Solo operators who want to run a content business from one machine, with their own GPU, without paying per-token API fees
  • Indie publishers who need automation but refuse to ship hallucinated text
  • AI/ML engineers who want a working content stack to fork, extend, and learn from — every layer is OSS, every layer is swappable

It is not for marketing teams who want a one-click web app (use Jasper / Copy.ai), or anyone unwilling to run Docker.

Architecture

Poindexter is a kernel + modules + capabilities stack. The kernel is the substrate everything rents — plugin registry, DI container, pipeline engine, settings. Business modules (content, finance, yours) compose capability plugins across 18 entry-point groups. Components never import each other — everything communicates through PostgreSQL (the "spinal cord"), and a standalone watchdog daemon (the "brainstem") can crash and restart without taking down the API.

flowchart LR
    subgraph pc["Your PC — the whole factory"]
        direction TB
        brain["Brainstem — watchdog daemon<br>(standalone, self-healing)"]
        modules["Modules<br>content · finance · yours"]
        kernel["Kernel<br>plugin registry · DI · pipeline engine · settings"]
        caps["Capabilities — 18 plugin groups<br>llm · image · video · audio · tts · taps · …"]
        pg[("PostgreSQL + pgvector<br>the shared bus — no cross-imports")]
        ollama["Ollama<br>local inference"]
    end
    store["Any S3-compatible storage<br>static JSON + RSS"]
    fe["Any frontend<br>Next.js · Hugo · Astro · plain HTML"]

    modules --> kernel
    modules --> caps
    caps --> ollama
    kernel <--> pg
    brain -.->|monitors + restarts| pg
    kernel --> store --> fe

Loading

The content pipeline itself is a declarative LangGraph DAG stored in the database — 49 nodes covering research, writing, image generation, the 17 QA rails, SEO, and publish. Swap any piece as long as it writes the same tables. Full diagrams and design rationale in docs/architecture/.

Key features

Feature Description
Local AI by default Ollama for inference. Your GPU, your data, zero API costs.
Cloud opt-in LiteLLM provider plugin routes to Anthropic, OpenAI, Groq, OpenRouter — gated by cost guard
Anti-hallucination 3 independent layers: prompts, multi-model QA, deterministic validator
DB-as-config 2,100+ settings in PostgreSQL. Change with SQL, REST, or CLI. No deploys, no .env sprawl.
LangGraph pipelines Declarative DAGs with Postgres checkpointing — resumable mid-run
Multi-modal output Markdown posts, AI images, podcast audio, text-to-video (alpha)
Push-only output Static JSON + RSS + JSON Feed 1.1 to S3-compatible storage or a local folder (/site/)
Multi-site One daemon manages N sites. Each site = config row + storage bucket.
Self-healing Watchdog daemon monitors all services, restarts failures, alerts via Telegram/Discord
Production observability Grafana, Prometheus, Loki, Pyroscope (CPU profiling), Sentry-compatible (GlitchTip)
OAuth 2.1 throughout Every consumer (CLI, MCP, scripts) mints scoped JWTs. No static API keys.
22,000+ tests Unit coverage across all services, smoke tests on migrations, link-rot CI

Stack

  • Backend: Python 3.13 / FastAPI / asyncpg
  • LLM (default): Ollama — local inference, your GPU
  • LLM (optional): LiteLLM provider plugin — Anthropic, OpenAI, Groq, OpenRouter, Bedrock, Vertex (gated by cost_guard)
  • Orchestration: LangGraph declarative pipelines + Prefect dispatch
  • Prompt management: versioned SKILL.md packs in-repo, mirrored to Langfuse for trace-side review
  • Embeddings: nomic-embed-text via Ollama → pgvector
  • Database: PostgreSQL 16 + pgvector
  • Auth: OAuth 2.1 Client Credentials Grant (per-consumer JWTs)
  • Observability: Grafana + Prometheus + Loki + Pyroscope + Sentry-compatible (GlitchTip)
  • Voice (optional): LiveKit + Whisper (STT) + Kokoro (TTS)
  • Storage: any S3-compatible (Cloudflare R2, AWS S3, Backblaze B2, MinIO)
  • Infrastructure: Docker Compose — 17 containers in the default stack (docker-compose.consumer.yml, sized for 8-16 GB VRAM and 32 GB RAM) plus 6 opt-in (local image generation, TTS, the Postiz social hub); the operator's full stack adds the rest of the observability tier (Langfuse, Loki, Tempo, GlitchTip)

Configuration

Everything tunable lives in the app_settings database table — not environment variables. The only file on disk is ~/.poindexter/bootstrap.toml, created by poindexter setup: the database URL plus the few pre-DB secrets (Postgres password, OAuth signing key, optional Telegram/Discord alert channels).

poindexter settings list                                  # view all settings
poindexter settings set auto_publish_threshold 80         # change at runtime — no restart

Every knob is also reachable over REST with an OAuth-minted JWT. See environment-variables.

Plugins

Eighteen plugin entry-point groups let you extend the system without touching core code: Taps (pull data in — RSS, Slack, social), TopicSources (discover candidate topics), LLMProviders / ImageProviders / TTS / video / audio / captions, PublishAdapters (where finished posts go), Probes, Jobs, Stages, Reviewers, and full Modules that bundle plugins + migrations + routes into a versioned business function. The canonical list lives in plugins/registry.py::ENTRY_POINT_GROUPS.

Each plugin is a pip package registering a setuptools entry point:

pip install poindexter-tap-slack
poindexter settings set plugin.tap.slack '{"enabled": true, "config": {"workspace": "myteam"}}'

Authoring a plugin — a complete Tap in ~20 lines

# my_package/slack_tap.py
from poindexter.plugins import Tap, Document

class SlackTap:
    name = "slack"
    interval_seconds = 3600

    async def extract(self, pool, config):
        async for msg in fetch_slack_messages(config):
            yield Document(
                source_id=f"slack/{msg.ts}",
                source_table="slack",
                text=msg.text,
                metadata={"channel": msg.channel, "user": msg.user},
                writer="poindexter-tap-slack",
            )
# pyproject.toml
[project.entry-points."poindexter.taps"]
slack = "my_package.slack_tap:SlackTap"

Shipping samples (HelloTap, DatabaseProbe, NoopJob) live in src/cofounder_agent/poindexter/plugins/samples/. The first production plugin is the LiteLLMProvider — Glad Labs eats its own dog food. Full design in plugin-architecture.

Project status

Poindexter is in alpha. Honest snapshot:

Built by one person in evening hours, directing AI coding agents (Claude Code) that wrote almost all of the code. The test suite and the CI ratchets are what keep it coherent.

What works today

  • Full content pipeline end-to-end on the author's daily-driver setup (RTX 5090, 64 GB RAM, Pop!_OS). Single-operator content business publishing daily.
  • 220+ live posts on gladlabs.io (390+ posts total, 2,000+ pipeline runs).
  • 22,000+ unit tests passing in CI on every push, plus migrations smoke test and link-rot CI.
  • poindexter setup --auto takes a fresh clone to a healthy local stack — no .env file, no manual secret wrangling — and the quick start above runs on a clean GitHub runner every week (with a tiny stand-in model, since a runner has no GPU), failing unless the queued post reaches the approval queue.
  • Live in-place upgrades — schema changes applied to a running instance with zero data loss.
  • Multi-model QA with deterministic validators, an LLM critic chain, and a programmatic anti-hallucination layer.
  • Push-only static export to any S3-compatible storage; frontend fully decoupled.
  • OAuth 2.1 throughout (per-consumer scoped JWTs, no static API keys).

Known rough edges

  • No managed/hosted offering yet. Self-host only.
  • No multi-tenant deployment recipe. One operator, one machine.
  • Native Windows cmd / PowerShell not supported. Use Git Bash or WSL.
  • Database schema is not yet stable across releases. Read the CHANGELOG before upgrading.
  • The plugin framework is real (LiteLLMProvider runs in production), but the ecosystem is nascent — you may be writing the second-ever third-party plugin.
  • Text-to-video is alpha. The T2V provider plugin passes smoke tests but is opt-in and needs ~28 GB VRAM headroom. Track Glad-Labs/poindexter#124.

If any of those would block your use case, that's worth knowing before you start. PRs welcome — see CONTRIBUTING.md.

The engine is free and open-source under Apache 2.0. Pro is the operator layer on top: the console at the top of this page, plus the configuration and the book behind the live site.

Tier Price What you get
Free $0 The full pipeline: every QA rail, every prompt pack, every Grafana dashboard, the CLI and REST API. GitHub issues support.
Pro $19/mo or $180/yr — 7-day free trial The operator console (the one Pro-only surface), the live config seed (1,200+ production settings, re-exported every week, adopted with a dry-run-first poindexter pro apply), the Poindexter book, and the founding-member Discord

Pro gates exactly one surface: the console. Everything else it sells is curation and freshness. You've installed the engine, you've seen output that's almost there, and you want to start from the settings that actually ship gladlabs.io, rebuilt from the live system every week, instead of spending months finding them yourself. Every prompt pack and dashboard in this repo stays free. Pro doesn't hold them back.

Pro starts with a 7-day free trial, and the current price is a Founding Member rate, locked for life. The free engine never needs a subscription either way.

See Poindexter Pro → gladlabs.ai

Documentation

Full technical docs at gladlabs.mintlify.app. Recommended path:

Contributing

Issues and PRs welcome — see CONTRIBUTING.md. The screenshots above re-bake with node scripts/capture-readme-screenshots.mjs against a running stack.

Security & SBOM

  • Report vulnerabilities to security@gladlabs.io (SECURITY.md)
  • Every push to main runs gitleaks (secrets), Trivy (CVEs), and syft+grype (SBOM + CVE scan)
  • A CycloneDX-JSON SBOM is published as a workflow artifact on every release; enterprise buyers can request one directly

License

Apache License 2.0 — Copyright 2025-2026 Glad Labs LLC. Relicensed from AGPL-3.0 on 2026-04-29 — see CHANGELOG.


Built in the open by Glad Labs. If Poindexter is interesting, a ⭐ helps other operators find it.


Poindexter™ is a trademark of Glad Labs LLC. The code is Apache-2.0; use of the name is covered by the trademark policy.

来源:GitHub · github.com