cd /news/artificial-intelligence/show-hn-amanuensis-a-local-first-ai-… · home topics artificial-intelligence article
[ARTICLE · art-22693] src=github.com pub= topic=artificial-intelligence verified=true sentiment=· neutral

Show HN: Amanuensis – a local-first AI persona that won't fabricate facts

A developer released Amanuensis, a local-first AI persona system that runs on a personal GPU machine without cloud LLM calls, designed to prevent fabricated information by requiring human approval before any post is published. The pilot persona, AlexaPavlova, posted as a sarcastic senior Berlin developer on Mastodon and Bluesky, with every post reviewed via Telegram before publication. The project is now an archived experiment, with its MIT-licensed code available for others to fork and run their own AI personas.

read7 min publishedJun 5, 2026

A local-first AI persona that writes under a human's veto. It drafts, you approve, and nothing it can't ground gets published.

A local-first pipeline for running an AI persona on Mastodon and Bluesky. The pilot persona, AlexaPavlova, posts as a sarcastic senior Berlin dev with dry takes on tech news and open source.

Everything runs on a local GPU machine. No cloud LLM calls.

See it live: Mastodon · Bluesky — now disclosed as AI, no longer posting.

Every post is reviewed on a phone before it publishes — approve, regenerate text or image, or cancel.

Status:this was an experiment, not an active product. The code is MIT-licensed and works end-to-end — fork it, learn from it, run your own persona. Issues and PRs may not get a response.

The hard part wasn't generating text, it was stopping the model from fabricating technical detail. The short version: factual-only source summaries, deterministic cleanup before any LLM judgment, a regex pre-screen in front of an LLM grounding check, titles-only memory, and a human approving every post over Telegram.

Full write-up of the design and what broke along the way: write-up.

pip install -e ".[dev]"
cp .env.example .env

LMStudio

  • Download LMStudioand load any instruction-tuned model (tested with Mistral-7B-Instruct and similar) - Go to Local Server→ start the server on port1234

  • Set LMSTUDIO_BASE_URL=http://localhost:1234

in.env

SwarmUI

  • Install SwarmUIand load the image model +41ex4_p4v10v4

LoRA - Set SWARMUI_BASE_URL=http://localhost:7801

in.env

→ copy the token intoTELEGRAM_BOT_TOKEN

  • Message @userinfobot→ copy your numeric ID intoTELEGRAM_CHAT_ID

  • Send any message to your new bot so it can message you back

python main_batch.py --dry-run

This fetches real stories and generates posts + images using your local services. Nothing is written to any database and nothing is sent to Telegram. If this prints 8 posts, your local stack is working.

Add your social credentials to .env

(Mastodon and/or Bluesky — both optional, see table below), then open three terminals:

python main_batch.py

python main_telegram_listener.py

python main_dispatcher.py

Approve posts in Telegram. The dispatcher picks them up and publishes. Done.

For long-running setups, run the three persistent processes under

systemd

orsupervisord

so they survive reboots.main_batch.py

is a one-shot script — run it via cron or manually each day.

The persona's images come from a custom LoRA trained on top of Juggernaut XL "Ragnarok" (SDXL), generated through SwarmUI. The LoRA is not included in this repo — it only contains trained deltas, not the base model.

LoRA: download from Hugging Face —msalsas/alexa-lora. Trigger word41ex4_p4v10v4

, weight0.3

, generated at 768×1024.Base model:Juggernaut XL "Ragnarok" by RunDiffusion— get it separately, not distributed here.- The LoRA was trained on a fully synthetic dataset(images generated with Juggernaut XL); the character is not based on any real person.

To run the alexa profile with images you need both: load Juggernaut XL in SwarmUI and apply this LoRA. See the model card on Hugging Face for the exact prompt format.

Adapters (HN, Lobsters, BearBlog, AskHN)
    └── Curator (dedup by URL + title + subreddit, banned-topic filter)
        └── BatchFactory
            ├── Brain (LMStudio → post text + image prompt)
            └── ImageService (SwarmUI → PNG via LoRA)
                └── Scheduler (UTC time windows with jitter)
                    └── QueueService (SQLite)
                        └── TelegramNotifier (photo + approval keyboard)
                            ├── MastodonPublisher (on APPROVE)
                            └── BlueskyPublisher  (on APPROVE)

Reply pipeline (runs in parallel):
    ReplyListener (random poll, 30 min – 2 h per post)
        ├── MastodonCommentFetcher / BlueskyCommentFetcher
        ├── Brain.evaluate_relevance() → skip or draft reply
        ├── Brain.generate_reply()
        └── TelegramNotifier (REPLY_APPROVE / REPLY_CANCEL)
            ├── MastodonPublisher.publish_reply() (on APPROVE)
            └── BlueskyPublisher.publish_reply()  (on APPROVE)
  • Python 3.10+ LMStudiorunning locally (OpenAI-compatible API)SwarmUIrunning locally with the41ex4_p4v10v4

LoRA loaded- A Telegram bot token + chat ID (for the approval workflow)

  • Mastodon and/or Bluesky credentials (for publishing)
  • An OpenWeatherMapAPI key (free tier, for ambient context in prompts)
pip install -e ".[dev]"
cp .env.example .env
Variable Description
LMSTUDIO_BASE_URL
LMStudio API base, e.g. http://localhost:1234
SWARMUI_BASE_URL
SwarmUI base, e.g. http://localhost:7801
TELEGRAM_BOT_TOKEN
Bot token from @BotFather
TELEGRAM_CHAT_ID
Your personal chat ID (use @userinfobot to find it)
ACTIVE_PROFILE
Profile slug, default alexa
WEATHER_API_KEY
OpenWeatherMap key (free)
ALEXA_MASTODON_ACCESS_TOKEN
Mastodon token for the alexa profile
ALEXA_BLUESKY_APP_PASSWORD
Bluesky app password for the alexa profile
MASTODON_ACCESS_TOKEN
Global fallback Mastodon token (used if no prefixed var found)
MASTODON_INSTANCE_URL
Fallback — prefer setting mastodon_instance_url in identity.yaml
BLUESKY_HANDLE
Fallback — prefer setting bluesky_handle in identity.yaml
BLUESKY_APP_PASSWORD
Global fallback Bluesky app password

main_batch.py

generates 8 posts + images and sends each to Telegram for approval (run via cron).- You APPROVE / REGEN / CANCEL on your phone. main_dispatcher.py

publishes approved posts at their scheduled time (persistent loop).main_reply_listener.py

polls published posts, drafts replies to incoming comments, and sends them back through Telegram for approval (persistent loop).

The three persistent loops (dispatcher

, telegram_listener

, reply_listener

) belong under systemd or supervisord.

python main_batch.py --dry-run

Fetches real stories, generates text and images via local services, prints everything to stdout. No DB writes, no Telegram, no side effects.

python main_dispatcher.py --dry-run

Logs what would be published for each approved post without making any social API calls.

Script Purpose
main_batch.py
Run once daily — fetches stories, generates posts, saves to memory + queue, notifies Telegram
main_dispatcher.py
Persistent loop — publishes APPROVED posts at their scheduled UTC time
main_telegram_listener.py
Persistent loop — handles APPROVE / REGEN / CANCEL / REPLY_APPROVE / REPLY_CANCEL callbacks
main_reply_listener.py
Persistent loop — polls published posts for new comments, drafts replies, sends for Telegram approval

Each daily batch generates 8 posts by default (profiles/alexa/identity.yaml

):

Category Count Sources
TECH 5 Hacker News, Lobste.rs, BearBlog
PERSONAL 2 Ask HN discussion threads
RAW 1 Internally generated (no source story)
mkdir -p profiles/marco/prompts profiles/marco/generated
cp profiles/alexa/identity.yaml profiles/marco/
cp profiles/alexa/prompts/*.j2 profiles/marco/prompts/

ACTIVE_PROFILE=marco python main_batch.py --dry-run

The directory name (slug) must match ACTIVE_PROFILE

. It is separate from the name

field in identity.yaml

("alexa"

vs "AlexaPavlova"

).

config/
  schemas.py              # RawStory, Post, ProfileConfig (Pydantic v2)
  settings.py             # Pydantic-settings from .env
core/
  brain.py                # LMStudio calls, text cleaning, truncation
  curator.py              # Dedup by URL + title + subreddit; banned-topic filter
  factory.py              # Orchestrates adapters → curator → brain → image
  scheduler.py            # UTC-aware time windows with random jitter
  profile_.py       # Loads profiles/{slug}/identity.yaml
adapters/
  hn_adapter.py           # Hacker News top stories (TECH)
  lobsters_adapter.py     # Lobste.rs hottest (TECH)
  bearblog_adapter.py     # BearBlog Discover RSS (TECH)
  ask_hn_adapter.py       # Ask HN discussion posts via Algolia (PERSONAL)
  reddit_adapter.py       # Reddit (requires OAuth2 credentials; not used by default)
services/
  memory_service.py       # Per-profile SQLite post history + platform IDs
  queue_service.py        # Approval queue (SQLite)
  image_gen.py            # SwarmUI REST client
  notification.py         # Telegram sendPhoto/sendMessage, approval keyboards, long-poll
  comment_service.py      # comments.sqlite: comments, pending_replies, poll_state
social/
  mastodon_publisher.py         # Mastodon REST — publish + publish_reply
  bluesky_publisher.py          # AT Protocol XRPC — publish + publish_reply
  mastodon_comment_fetcher.py   # Fetches replies via /api/v1/statuses/{id}/context
  bluesky_comment_fetcher.py    # Fetches replies via app.bsky.feed.getPostThread
profiles/
  alexa/
    identity.yaml         # Persona config: slots, sources, banned topics, image model
    prompts/*.j2          # Jinja2 templates: system prompt + per-mood + per-platform
    generated/            # Output images (slot_NNN.png)
    memory.sqlite         # Post history injected as context into each prompt
    queue.sqlite          # Approval queue
    comments.sqlite       # Comments, pending replies, poll state
pytest tests/ -v      # 312 tests, all mocked — no live services required

All HTTP calls (LMStudio, SwarmUI, Telegram, HN, Algolia, etc.) are mocked with respx

.

── more in #artificial-intelligence 4 stories · sorted by recency
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/show-hn-amanuensis-a…] indexed:0 read:7min 2026-06-05 ·