Skip to main content
Paul Welty, PhD AI, WORK, AND STAYING HUMAN

Synthesis: March 26, 2026

Overview

A wide-ranging day across four projects with a surprising amount of thematic coherence: silent failures getting audited and fixed, feedback loops getting closed, and institutional knowledge being codified so it can propagate forward. The mix of infrastructure work (paulos), security hardening (eclectis), feature density (authexis), and test coverage (dinly) reflects the current state of the fleet — different projects at different maturity levels, all moving in the same general direction.


Project summaries

Paulos — eidos model repo + governance docs

The most structurally significant work of the day was in paulos, though it produces no user-facing output: the pwelty/eidos repo was transformed from a pure-spec collection of markdown files into a runnable 38-file GitHub template. This matters because every new project in the fleet starts from eidos, and until today the “template” was aspirational rather than actual.

The scaffold covers the full three-tier stack — Next.js 16 (web, Vercel), Supabase (Postgres + Auth + RLS), Python FastAPI (engine, Railway) — with all the gotchas from the authexis and eclectis pilots baked in as code comments: the asyncpg statement_cache_size=0 requirement for Supabase’s transaction mode pooler, the claim-then-process poller pattern that prevents Railway pooler timeouts, the .order().limit(1).single() chain that prevents multi-membership redirect loops, the three-client-type Supabase pattern (server/client/admin), and the RLS co-member visibility policy that prevents PostgREST joins from silently returning null. Next.js 16.2.1 conventions are correct throughout: proxy.ts instead of middleware.ts, async request APIs (await cookies(), await params), turbopack-as-default (no --turbopack flag), React.cache() for per-request deduplication. The repo is marked as a GitHub template — the “Use this template” button is live.

Beyond the eidos scaffold, the day also landed the Reddit API client (paulos/commands/reddit.py) with automatic OAuth token exchange and a reddit-search marketing command. The health command now checks Reddit credentials as part of its sweep. Status report polish: project logo in the email header. And the /reflect podcast skill was updated to a more disciplined standard: read the technical work as raw material for insight, then discard it entirely — never name what was built, speak only to the organizational or AI implication it reveals.

Issues closed: #586, #606, #630, #631, #632, #633, #634, #635, #636, #132 (plus eidos scaffold commit)

Carry-over: Reddit credentials need manual app registration at reddit.com/prefs/apps before reddit-search is usable. Eidos PRODUCT.md entity/user-flow sections remain placeholder text (intentional — each project fills them in).


Authexis — hashtag pipeline, content refresh, image work (reverted)

Authexis had the highest feature throughput of the day, closing five issues across three distinct product areas.

The hashtag pipeline is the most complete story. Before today, posts often had hashtags baked inline into the LLM-generated body text — buried in the visible prose, not a first-class field. The new addContentToQueue logic extracts these inline #hashtags, strips them from the text, and writes them to social_posts.hashtags. If no inline tags exist, it falls back to the source content’s tag array. On top of that seeding mechanism, two new surfaces: an inline HashtagEditor component on queue post cards (chips with × delete and + add) and a background social_post.generate_hashtags engine command that prompts Claude for 3–5 hashtags when none were seeded. The three issues (#1742, #1743, #1744) form a complete loop — seed, edit, generate — and dispatch wires them together automatically.

The content refresh flow (#1736) addresses a real product gap: LLM-written content becomes wrong when the world changes. A “Refresh content →” link now appears on content in review or final status, expanding an inline panel (not a modal) where the user describes what changed. On submit: note persisted to contents.refresh_notes, history event logged, and three content.redo_field commands queued for draft, intro, and title. No engine changes were needed — the existing redo_field handler already supported revision_notes as a prepend prefix.

The image upload work (#1745, #1746) was shipped and then reverted. The code was correct — a Vercel Blob upload route and ImageUploader component — but Vercel Blob hasn’t been provisioned for this project. Shipping a /api/upload/image endpoint that 500s in production was the wrong call. Both issues are reopened and parked behind a Blob provisioning step.

Issues closed: #1722 (prior session carry-over), #1736, #1742, #1743, #1744 (note: #1745/#1746 reverted)

Carry-over: Three issues stack behind Vercel Blob provisioning (#1745, #1746, #1738). The provisioning is a 2-minute dashboard action — it just hasn’t happened yet. Hashtags are stored as TEXT (space-separated), not TEXT[]; this is worth normalizing in a future pass. #1747 (image_url on social posts, LinkedIn Assets API) needs prep.


Eclectis — security hardening sprint

Eclectis ran a focused security and reliability pass — four issues in the same thematic cluster, all shipped. The thread: silent failures and secret exposure paths that had been in the codebase since early development.

Settings and export fail-loudly hardening (#488): getFeedbackLoopHealth() now treats any query failure as a hard error captured to Sentry. The export route no longer returns a partial ZIP with an inline warning — it 500s cleanly. New test file covers five error scenarios including the PGRST116 “not found” / genuine database error distinction (the former should never reach Sentry; the latter always should).

Passkey ceremony hardening (#489): All four passkey route handlers — options and verify for register and login — now fail loudly. The critical fix: challenge cleanup failure now blocks session issuance instead of being swallowed. Eight test scenarios including the same PGRST116/real-error distinction applied to challenge lookups.

Secret exposure closure (#490): The user_profiles_decrypted view was returning raw api_key plaintext and the full integrations_encrypted blob over the wire to every authenticated read. A migration drops and recreates the view with has_api_key (boolean) and integrations (decrypted JSON only). The RSS feed URL’s loose LIKE pattern for matching newsletter addresses was tightened to exact match with a stricter 12-char hex regex. The view rebuild required DROP + CREATE (PostgreSQL won’t remove columns via CREATE OR REPLACE VIEW) with grants re-added after CASCADE.

Admin impersonation guard (#491): The _realAdminId sentinel (set by getUser() during impersonation) is now wired into deleteAccount(). The settings page DangerZone hides the delete button entirely during impersonation and shows an explanatory notice instead.

Issues closed: #488, #489, #490, #491

Carry-over: supabase db push was not run for the #490 migration — it’s committed but not applied to the remote database. Must run before next deploy or auth-related testing. The test coverage queue (#498–#502) remains in ready-for-dev. Any code reading profile.api_key against user_profiles_decrypted will break post-migration — needs a scan.


Dinly — feedback loop closed, test coverage

Dinly had two distinct wins: a one-line product gap closed and meaningful test coverage added.

The feedback loop fix (#319) is the kind of bug that’s embarrassing in retrospect — the ranking engine had been designed from the start to incorporate post-meal ratings (bookmark_tried_positive: +2, bookmark_tried_negative: -2), but plan/page.tsx was never loading recipe_bookmarks and never passing them to rankCandidates. The fix was a single new entry in a Promise.all batch and one field in the call. Now the “learn from what you actually eat” promise actually works.

The test coverage work (#320, #321, #323 plus cadence-nudge #315) added 27+ tests across the most consequential server actions in the app: feedback/actions.ts (multi-step bookmark upserts, history updates, household ownership verification), vote/actions.ts (status-gated voting windows, delete-then-insert atomicity), and plan/actions.ts (one-way finalization, guard conditions). A design insight worth preserving: finalizePlan calls redirect() at the end; Next.js uses redirect as a throw internally, so the right mock is vi.fn() (no-op) rather than an implementation mock. Also notable: the feedback nudge test must return a non-empty active-cycles result for the first query to prevent the early-return guard from firing before the code reaches the finalized-cycle loop.

Scout filed two new issues: #322 (past weeks list has no feedback summary — no average rating, no “ate N/7 meals”) and #318 (AI suggestions have no loading/error state in the candidate selector).

Issues closed: #315, #319, #320, #321, #323 (plus #309–#317 from prior session context)

Carry-over: #318 (AI suggestions error/loading state, needs-prep), #322 (past weeks feedback summary, needs-prep), #302 (vote requests not surfaced to the cook, ready-for-prep), #308 (no cooking history view, ready-for-prep).


Cross-cutting themes

Silent failures getting audited

Both eclectis and authexis spent significant effort on “fail loudly” patterns today. Eclectis did it systematically (four dedicated issues), authexis did it as a byproduct of other work (the image upload revert being the cleanest example — better to 500 than to silently degrade). Dinly’s error handling fixes (optimistic rollback in feedback section, error display in shopping list) fit the same pattern. The fleet is maturing: the early-stage tolerance for swallowed errors is giving way to a higher standard.

Feedback loops closing

Dinly’s ranking fix is the most literal version of this — user ratings literally weren’t feeding back into recommendations. But authexis’s hashtag pipeline also closes a loop (LLM-generated hashtags → proper first-class field → user can edit → AI can regenerate). And eclectis’s admin impersonation guard closes an integrity loop — destructive actions now require confirmation that you’re acting as yourself.

Institutional knowledge encoding

The eidos scaffold is the most explicit version: 38 files that exist specifically to preserve lessons from previous pilots so the next project doesn’t have to rediscover them. The PIPELINE.md, ROADMAP.md, RISKS.md, and /reflect skill updates in paulos are all the same idea at a different scale — turning tacit knowledge into persistent structure that survives context resets and session boundaries.

Revert discipline

Authexis reverted a shipped feature because the infrastructure it depended on wasn’t in place. This is the right call. The pattern — ship, discover the dependency gap, revert and park — is healthier than leaving broken endpoints in production. Worth noting as a team norm.


Carry-over (cross-project)

ProjectItemBlocker
PaulosReddit credentials registrationPaul needs to register app at reddit.com/prefs/apps
AuthexisVercel Blob provisioningDashboard action needed before image features can land
Authexis#1747 image_url + LinkedIn Assets APINeeds prep
Eclectissupabase db push for #490 migrationMust run before next deploy
Eclectisprofile.api_key reference scanMay break engine/admin tooling
Dinly#318, #322Need prep before grind

Risks

  • Eclectis #490 migration not applied. supabase db push was not run. Any deploy that touches profile reads will fail until this runs.
  • Authexis image upload code in git history. The reverted endpoint still exists in git history — if someone cherry-picks or references an older commit, they could expose a broken route. Not urgent, but worth awareness.
  • Paulos test suite has ~10 pre-existing stale failures. Today’s session fixed 20 failures, but the baseline of pre-existing failures masks new regressions. The count should not grow.

By the numbers

ProjectIssues closedNotes
Paulos10#132, #586, #606, #630–#636; eidos scaffold separate commit
Authexis5#1722, #1736, #1742–#1744 (2 reverted)
Eclectis4#488–#491
Dinly5#315, #319–#321, #323
Total24
ProjectMilestone status
PaulosMarch 2026: 24/24 closed; April 2026: 2/2 closed
Authexisv1.5: 49/50 closed (1 backlog item)
EclectisAll milestones closed; backlog queue active
DinlyNo milestone assignments yet

Why customer tools are organized wrong

This article reveals a fundamental flaw in how customer support tools are designed—organizing by interaction type instead of by customer—and explains why this fragmentation wastes time and obscures the full picture you need to help users effectively.

Infrastructure shapes thought

The tools you build determine what kinds of thinking become possible. On infrastructure, friction, and building deliberately for thought rather than just throughput.

Server-side dashboard architecture: Why moving data fetching off the browser changes everything

How choosing server-side rendering solved security, CORS, and credential management problems I didn't know I had.

The work of being available now

A book on AI, judgment, and staying human at work.

The practice of work in progress

Practical essays on how work actually gets done.

Designed to learn, built to ignore

The most dangerous organizational failures don't throw errors. They look fine, return results, and quietly stay frozen at the moment of their creation.

The variable that was never wired in

The gap between having a solution and using a solution is one of the most persistent failure modes in organizations. You see the escaped variable. You see the risk register. You assume the work is done.

Your empty queue isn't a problem

Dropping a column from a production database is the organizational equivalent of admitting you were wrong. Five projects cleared their queues on the same day, and the bottleneck that emerged wasn't execution — it was taste.