Skip to content
← Back to writing

AI · SEP 2026 · 11 MIN READ

The LLM is not the autopilot

How I turned evidence-grounded content generation into a durable Facebook and Threads publishing system with bounded autonomy.

The LLM is not the autopilot

Generating a social post takes seconds. Building a system that can decide what is worth promoting, publish it at the right time, and recover safely when the network fails took much longer.

I learned this while building an internal growth workflow for Lemtika. The product already turns a Google Maps listing into a researched business website. The new workflow promotes selected locations on Lemtika's own Facebook and Threads accounts.

The obvious prototype fit in one sentence: collect some business details, ask a model for a caption, and call the Meta API on a schedule.

That prototype leaves the important questions unanswered. What if the website changes after caption generation? What if Auto-pilot turns off while older work waits in the queue? What if Threads publishes the first two items in a chain, then rejects the third? What if Meta accepts a request, but the process crashes before recording the response?

Those questions do not belong in the prompt. They belong to the product around it.

The LLM is one bounded stage inside a larger social publishing system that owns evidence, policy, time, external effects, and measurement.

Keep selection outside the model

The workflow starts with a daily planner, but it does not ask an LLM which businesses look interesting.

A location is eligible only when its website is published, its research contains a sufficiently supported positive insight, it has not already been promoted on the target platform, it is not blocked, and a valid platform connection exists. The initial daily policy admits no more than two distinct locations.

Eligible candidates are ranked by deterministic code. The score combines the strength of their supported insights, business-information completeness, usable media, and publish freshness. A persisted hash breaks ties reproducibly. The strongest one to three insights become the evidence supplied to generation.

This separation controls both cost and authority. Candidate discovery may inspect many locations. Calling a model for every one would spend money on a decision that arithmetic and database constraints can make more reliably. It would also make the daily limit difficult to explain. With code-owned ranking, I can reconstruct why one location entered the queue and another did not.

The model enters the process only after the product has decided there is something supported and useful to say.

Freeze the world before generating

An approved post may wait for hours or months. Its inputs cannot remain mutable pointers to whatever data happens to be current when publication begins.

Before generation, Lemtika creates an immutable source snapshot. It includes the business facts used by the prompt, the selected insight statements and their evidence metadata, the exact website and document version, the canonical public URL, ordered media, effective social links, and the versions of the workflow, model policy, reusable writing skills, and Meta API contract.

The snapshot solves a subtle product problem. A business owner can edit a website after a social draft is created. Without version binding, a person could approve one message and the system could later publish it beside a changed image or destination. With an immutable snapshot, the approval refers to one exact content package. A material source change makes the post require a new revision instead of silently changing what was approved.

The same constraint applies to media. Selected images are normalized, stored as immutable content-addressed assets, read back, and bound to the revision by their hashes. Immediately before transmission, the publisher fetches the exact public bytes and verifies their content type, size, and hash. The approved image is therefore the image Meta receives, even if the website gallery changes in the meantime.

Use one model call for one editorial decision

The current generation policy pins Gemini 3.7 Flash through the existing OpenRouter rail. One structured call returns exactly the platform set that is still eligible: Facebook, Threads, or both.

I chose one logical call rather than an unrelated call per platform because the variants should share an editorial angle. Facebook can receive a substantial place introduction with several images. Threads needs an ordered chain, with each item respecting its own rendered length limit. The formats differ, but the underlying reason for featuring the location should remain coherent.

The system prompt is assembled from immutable, versioned writing skills:

  1. Vietnamese copywriting.
  2. Lemtika's brand voice.
  3. A Facebook place-introduction contract when Facebook is requested.
  4. A Threads place-introduction contract when Threads is requested.

Gemini receives the source snapshot without image bytes, the full review corpus, or reviewer identities. It returns the editorial body and the IDs of the insights it selected. A strict schema rejects missing platform variants, extra variants, unknown insight IDs, malformed Threads chains, and other contract failures. One bounded schema-repair call may follow a confirmed invalid response.

The model can write. It cannot redefine the task, choose a different destination, invent the source set, or expand its own retry budget.

Put deterministic code around probabilistic writing

Structured output is necessary, but a valid JSON object is not yet a publishable post.

Application code checks that Facebook copy reaches the configured minimum substance. It verifies that a Threads chain has enough items for its selected media. After mentions and links are rendered, every Threads item must remain within both the Unicode and UTF-16 interpretations of the platform's 500-character boundary.

The model writes only the editorial body. Code appends the location-insight link, attribution parameters, community disclosure, owner-claim invitation, and Lemtika product message. Mention placeholders are resolved against a verified social identity. If the identity cannot be proven, the application renders the plain business name. If an approved mention later becomes invalid, the post returns for revision instead of quietly publishing altered text.

This division keeps operational truth outside probabilistic output:

Application code ownsGemini owns
Candidate eligibility and rankingEditorial angle
Source and configuration snapshotVietnamese prose
Destination and posting windowPlatform-appropriate voice
Links, disclosure, and attributionNarrative flow
Mentions and media bindingHow supported insights are introduced
Approval, retries, and publication stateStructured Facebook and Threads drafts

Giving the model less authority gave it a clearer job. It could focus on language while the product enforced everything that had one correct answer.

Treat Auto-pilot as a versioned decision

The workflow supports two operating modes. With Auto-pilot off, a staff member reviews the exact Facebook or Threads revision and approves, edits, rejects, regenerates, or cancels it. Each platform has an independent decision. Approving Facebook never approves Threads by implication.

With Auto-pilot on, valid initial content receives a system approval bound to the same immutable revision, content hash, destination, and source binding a person would approve. It then follows the existing scheduling, preflight, publication, and recovery path. Auto-pilot does not introduce a shortcut around the safety model.

The important implementation detail is when the mode is read. Each location snapshots the Auto-pilot setting and its version when it claims a queue slot. Turning the switch on does not bulk-approve older drafts. Turning it off does not reinterpret work that was already admitted under the previous policy. Edits, regeneration, source changes, and provider ambiguity retain their existing controls.

This turns the toggle into an auditable product decision rather than a mutable global boolean consulted at arbitrary points. The system can answer which policy admitted a post, even when the post publishes much later.

Own the clock

Scheduling is application state too. Lemtika does not hand a draft to a native Meta scheduler and hope the provider becomes the source of truth.

The daily planner creates a calendar in the Vietnam timezone with a bounded capacity and two non-overlapping posting windows. Each selected business claims one window ordinal. Facebook and Threads variants for the same business may share that business slot, but each receives an independently randomized publishing time inside the window.

That timestamp is persisted once. A replay never rolls the dice again.

The workflow uses a durable sleep until the assigned time. If the process restarts or a deployment replaces it, execution resumes from its recorded checkpoint. If approval arrives after the original window, the post moves through the same capacity rules to a future valid slot. It does not create an off-hours burst. Automatic backfill is disabled, so a restart does not publish every missed historical day.

The planner stays short. It reserves work and starts independent preparation jobs. Approval waits and scheduled sleeps do not occupy the small queues reserved for actual provider calls. That distinction prevents one post waiting for a decision from blocking every post behind it.

A durable workflow does not make an API call exactly once

DBOS gives the workflow durable checkpoints and replay. It cannot make a PostgreSQL transaction commit atomically with an HTTP request to Meta.

Consider the dangerous boundary:

send publish request
Meta accepts the post
process crashes
store provider response

After recovery, the database may not know whether the request reached Meta. Blindly retrying may create a duplicate. Assuming success may lose a post that was never published.

The publication layer records an immutable attempt identity and payload fingerprint before a credential can be loaded. Capacity admission and executor start are separate persisted transitions. Only the winner of the executor-start transition receives the one-use in-memory capability that can call the provider.

If a process dies after transmission could have begun, the attempt becomes ambiguous. It is not treated as a normal retryable exception. The reconciler queries provider evidence and recent account activity using the recorded identity. A visible effect is adopted. A proven absence still follows the operation's recovery policy, including explicit staff authorization where a retry could create a duplicate publication. An inconclusive result waits for operational attention.

A Meta publication attempt moves through prepared, admitted, transmitted, and confirmed states. Ambiguous transmission branches into evidence-based reconciliation instead of a blind retry.

Exactly-once workflow execution does not create exactly-once behavior at an external API boundary. The product has to model uncertainty explicitly.

Threads is a workflow, not a string

A Facebook publication is one logical post, even when it attaches several uploaded images. A Threads publication is an ordered set of external effects.

For every chain position, the workflow creates a media container when needed, waits for it to become publishable, publishes it, records its external ID and permalink, and uses that confirmed item as the parent of the next reply.

If a chain stops after two items, both already exist in public. The correct recovery is not to restart the chain. Lemtika enters a partial-publication state, preserves the confirmed prefix, reconciles its evidence, and resumes only from the first missing suffix when continuation is safe. An operator may also cancel the remaining items without pretending the visible prefix was rolled back.

This is ordinary saga design applied to a social format. Treating the chain as one caption would erase the state needed to recover it correctly.

Close the loop without overstating the result

Publishing is not the final state of the product. Each platform post includes an opaque attribution key in a tightly allowlisted campaign URL. The public website reduces that URL to the platform and anonymous post key before recording aggregate signals. It does not send raw URLs, visitor identities, sessions, contact values, or original referrers into the measurement path.

At seven and thirty days, the system can compare attributed page views, call intent, and directions intent at post and location-package level. The thirty-day view also records owner claims and paid subscriptions. Each post remains distinguishable as generated, edited, or regenerated content.

These numbers answer whether a post was followed by measurable activity. They do not prove the post caused it. Organic visits can arrive without the campaign link, and the current workflow does not include a randomized no-post holdout. I would require that experiment before claiming incremental growth.

That caveat is part of the design, not an apology after the dashboard is built. Reliable AI products need honest measurement boundaries as much as reliable generation boundaries.

What the workflow taught me

The original task sounded like content automation. The implementation crossed product policy, prompt design, structured generation, media processing, OAuth connections, scheduling, durable execution, distributed side effects, operational tooling, privacy, and analytics.

The reusable lessons are broader than social publishing:

  1. Keep high-volume eligibility decisions deterministic.
  2. Freeze every input and policy that may outlive the model call.
  3. Let models own language, not operational truth.
  4. Make autonomous approval use the same contracts as human approval.
  5. Treat external effects as potentially ambiguous, even inside a durable workflow.
  6. Model partial success when the outside world cannot be rolled back.
  7. Design measurement before automation ships.

The LLM was one important component. The Auto-pilot was the complete system around it.

Lemtika is live at lemtika.com. The case study explains how the product turns one Google Maps link into evidence-backed research and a published website. My article on model selection covers why models should be evaluated by the total work they create across a pipeline.

Hau Vo

Hau Vo

Senior product engineer and software architect.

Building something this touches on

I take ideas to production software. A 30-minute call tells you whether I'm the right person.

Book a call