Inspiration

Letting a language model post under a brand's name is a one-way door. A wrong claim about pricing, delivery, or ownership is public before anyone can read it. The usual fixes — bolt a confidence score onto the model, or park a human in a review queue that can't see what the model was looking at — don't actually close the door. We wanted the opposite default: the model never publishes, and the thing that makes that trustworthy is the plumbing, not the prompt.

What it does

Nyx Persona is a human-gated social reply console for a real independent software studio's Threads account. A genuine reply lands via webhook; GPT-5.6 drafts an answer bound to an approved persona and approved facts, and cites the evidence it used plus its own uncertainties; an operator reads that evidence and presses one button; and only then does deterministic server code publish the exact text that was approved — returning the provider's own receipt (permalink, media id, insights).

  • /demo (no login) normalizes three provider payload shapes — Instagram comment, Facebook page comment, Threads reply — into one review workflow. Generate a live GPT-5.6 draft, edit it, approve it, read the signed simulated receipt. The page has no capability to publish.
  • The Threads fixture escalates instead of answering: it carries a prompt-injection attempt, and the untrusted social text can influence wording only — never evidence, persona state, capabilities, or a publish target.
  • /live is the sign-in-gated operator console that publishes to the real public account.

How we built it

Next.js 16 (App Router) and React 19 on Vercel, TypeScript in strict mode, Zod 4 at every boundary, Neon Postgres through Drizzle ORM, Auth.js (GitHub OAuth) for the operator gate. No client state library, no component library.

The model is gpt-5.6 through the OpenAI Responses API over plain fetch: a strict json_schema output contract, integrated omni-moderation in score mode, store: false, low reasoning effort, a hard token cap, a 7-second timeout, zero retries, and a hashed safety_identifier (never an operator identity). The provider is the Threads Graph API v1.0 — webhook ingress on the replies field, container→publish for replies and posts, quote posts, reposts, per-media insights, and the rolling publishing quota.

The safety properties are enforced in code, not asked for in a prompt:

  • No publish without a human pressing approve.
  • Approval is bound to a sha256 of the exact final body, re-checked in SQL.
  • Publish is a claimed lease (TX1) then a receipt (TX2) — not a retry loop.
  • An ambiguous outcome becomes publish_outcome_unknown and is never retried blindly; absence is only trusted after 30s, while provider confirmation is trusted at once.
  • A claim the evidence doesn't support blocks approval.
  • Diagnostics carry paths, codes, hashes and lengths — never payload values.

The demo video's narration is voiced with OpenAI's gpt-4o-mini-tts; everything else shown is the product itself.

Challenges we ran into

  • Reporting truth under an unreliable provider. A publish call can succeed while the response is lost. We modeled that explicitly as publish_outcome_unknown and reconcile it from provider evidence rather than guessing — a confirmed publish is trusted immediately, an absence must age.
  • Keeping untrusted text from steering the system. Social text is reparsed and confined to a wording-only zone; it can't select evidence, persona state, capabilities, or targets.
  • Discovery is gated by Meta. Keyword search, profile discovery and third-party mentions need Advanced Access and App Review — proven by direct probe, not assumed, and documented rather than faked.

Accomplishments that we're proud of

  • One genuine reply travelled the full path — webhook → inbox → GPT-5.6 draft → human approval → real Threads publish — and kept its provider receipt.
  • The approval is cryptographically bound to the exact body that publishes; you can't approve one string and ship another.
  • 456 unit tests, 31 real-database race tests on an isolated Neon branch, and 15 Playwright specs, none of which can reach OpenAI or Meta by default — everything that spends credits or touches a public surface is opt-in.

What we learned

Trust in an AI that acts in the world comes from the boundaries around the model, not the model's own confidence. The valuable engineering was the state machine, the lease, the hash-bound approval, and the honest "unknown" — the parts that decide what happens when the model or the network is wrong.

What's next for Nyx Persona

A second real connector once two genuinely exist (Instagram/Facebook are honest recorded fixtures today), an editor for personas and approved facts (both are read-only and migration-seeded now), inbound reply-approval mirroring, and — if Meta grants Advanced Access — topic-driven discovery. Everything deferred is written down in TODOS.md with its reason.

Built With

Share this project:

Updates