What we shipped on 2026-06-26
We spent today closing the gap between human intuition and machine execution. For too long, when we rejected a draft via regen_at_gate --reason "add GPU benchmarks", that feedback was written to pipeline_gate_history.feedback and then effectively vanished (PR #1945). The writer would regenerate the content with zero context as to why it failed the first time. We fixed this by adding a _read_regen_steering helper in approval_gate.py that pulls those reasons back into the LangGraph state, injecting them as high-priority instructions–prepended to writer_prompt_override for niche paths or effective_style for legacy ones–so the writer actually addresses our critiques (PR #1945).
We’re also starting to treat operator approvals as training data. Now, when we approve a task with feedback, the system writes a brain_knowledge fact (entity topic:<topic>, attribute approved_by_operator_with_feedback) with a confidence of 0.7 (PR #1944). This turns a one-off approval into a signal for future recall tools to recognize patterns in what we actually like. To make the failures visible, we added an “Operator Rejection Reasons” table to our Grafana QA rails dashboard, querying audit_log where event_type='approval_gate_rejected' (PR #1944).
On the infrastructure side, we caught some dangerous silence in our monitoring. We realized that when the Prefect API was unreachable, the probe caught the httpx.ConnectError but only logged it–it never actually triggered a notification (PR #1946). We updated brain/prefect_stuck_flow_probe.py to call notify_fn(severity='critical') and fire a probe.prefect_dispatch_plane_unreachable audit event on connect errors or timeouts (PR #1946). While we were at it, we added Prometheus alert rules using absent() for the Prefect server and worker containers to ensure we aren’t flying blind if a container simply vanishes (PR #1946).
We also cleaned up some long-standing friction in our observability. Our Loki logs were plagued by detected_level: unknown because containers were running with LOG_FORMAT=text, making it impossible for Loki to pattern-match levels (PR #1943). We switched the workers to LOG_FORMAT: json and updated logger_config.py to include structlog.contextvars.merge_contextvars in the processor chain, finally binding task_id into the structured metadata for actual filtering (PR #1943).
A few other quality-of-life wins landed today:
- We fixed a bug where poindexter media open failed because it looked for videos using post UUIDs instead of task UUIDs; we now fetch media_assets.storage_path directly from the DB (PR #1940).
- We strengthened our Postgres readiness gate in Docker Compose, replacing pg_isready with psql -c 'SELECT 1' to eliminate those annoying asyncpg.exceptions.CannotConnectNowError spikes during startup (PR #1939).
- The console finally has eyes on the newsletter and brain daemon via new /api/newsletter/stats and /api/brain/stats endpoints, with corresponding panels in both the console and Grafana (PR #1942).
- Social drafts are no longer invisible; we built a SocialPanel and integrated pending drafts into the Action Inbox so we can Post or Reject without touching the CLI (PR #1941).
Everything wrapped into release 0.88.0 (PR #1950). We’ve moved from “it works if you check the DB” to “it’s visible in the dashboard,” which is where we need to be before we scale the niche count.
Auto-compiled by Poindexter from today’s commits and PRs. See the work: github.com/Glad-Labs/poindexter.



