1. Overview
Ontum Studio is an internal tool that generates on-brand marketing drafts for each of Ontum Education's product lines (Kosh, Kul, Kavach, OnboardAI) plus a cross-product "General EdTech" channel — grounded in real product documents, aware of what's already been posted, and refined through a feedback loop before anything is treated as final.
2. Problem statement
Content for Ontum's five channels is currently created without a shared memory of what's already been said — so there's no built-in way to know whether a new draft repeats a past angle, contradicts an earlier claim, or leaves an obvious follow-up story untold. Voice and grounding-in-fact also vary by whoever's writing and how recently they've reviewed the underlying product notes.
3. Goals
- Generate platform-appropriate drafts (LinkedIn, X, X-thread, blog, mailing list) grounded only in real product documentation — never fabricated stats or quotes.
- Maintain a per-product memory of finalized posts, and make new drafts explicitly decide whether they build on a specific past post or open a genuinely new angle.
- Let the writer control tone/feel per draft and iterate via feedback before anything counts as final.
- Keep a lightweight, explicit record of which topics/features have already been covered per product, independent of AI similarity judgments.
- Scale document grounding via retrieval once a product's document/post volume outgrows a single prompt (phase 2 — see the accompanying technical spec).
4. Non-goals (v1)
- No direct publishing/scheduling to LinkedIn, X, etc. in v1 — output is copied out manually. (Planned for v3 via MCP — see §11.)
- No performance-based feedback loop in v1 — the AI doesn't yet learn from how posts actually perform. (Planned for v3 — see §11.)
- No automated retrieval (pgvector) until document/post volume justifies it — v1 uses plain context-stuffing.
- No multi-language support.
5. Users
Internal tool, used by multiple people across Ontum — not a solo tool — spanning roughly marketing, program management, and the founding team. That's a real requirement, not a footnote: Phase 1 needs genuine Supabase Auth with RLS scoped per user (or role, if roles end up mattering), not the permissive single-user policy originally assumed — see the updated note in §7.
6. Feature requirements
6.1 Product folders
Five folders — Kosh, Kul, Kavach, OnboardAI, General EdTech — each with its own document library, tone setting, and post history. Switching folders scopes everything to that product.
6.2 Document library (per product)
- Multi-file upload (PDF, DOCX, TXT/MD), parsed to plain text client-side at upload time.
- Documents listed, viewable, deletable per product.
- All of a product's document text is available to the generation step (concatenated in v1; retrieved via pgvector once volume requires it).
6.3 Generation
- Inputs: platform (LinkedIn / X / X-thread / blog / mailing list), angle (product update / field story / engineering deep-dive), optional tone/feel free-text.
- Output grounded strictly in that product's documents — no invented statistics, quotes, or facts.
- Before writing, the model must decide: build on one specific past finalized post (only if a concrete, specific connection exists) or open a new angle (default).
- The decision and a one-sentence rationale are shown to the user alongside the draft.
6.4 Feedback & iteration
- After generation, the draft is pending, not yet part of memory.
- A feedback box lets the user request changes; "Refine" sends the current draft + feedback back to the model and replaces the output, tracked as a revision.
- Any number of refinement rounds are allowed before finalizing.
6.5 Finalize (memory commit)
- A "Finalize draft" action is the only thing that commits a draft into that product's saved history.
- Finalizing a previously-finalized draft (reopened and refined further) updates that same record rather than duplicating it.
- Only finalized drafts are used as build-on candidates and coverage-ledger input for future generations — refinement rounds that are abandoned leave no trace in memory.
6.6 Coverage ledger
- On finalize, extract 1-3 short topic tags describing what the post covered (e.g. "enclosure venting redesign").
- Stored per product, pulled in full at generation time as an explicit "already covered" list — this, not similarity search, is the primary defense against duplicate coverage.
6.7 Retrieval (phase 2)
- Once a product's documents/post history stop reliably fitting in context: chunk + embed documents (Voyage AI), similarity-search both document chunks and past posts via pgvector.
- Full technical design lives in the accompanying technical spec.
7. Non-functional requirements
- Security: Anthropic and Voyage API keys held server-side (Supabase Edge Function secrets) — never in client code once this moves off the local/artifact prototypes. Since multiple people use this tool, Supabase Auth with RLS scoped per user (or role, if roles end up mattering) is a v1 requirement, not a later add-on. Documents may contain non-public product/pilot information, and some of it may fall under partner NDAs — sensitivity level and Storage bucket access are being confirmed with the team before this moves off local prototypes.
- Latency: Acceptable generation time still to be pinned down with the team — a single API round trip today, one more once retrieval lands.
- Cost: A monthly ceiling for combined Anthropic + Voyage usage is being set once retrieval is added, rather than guessed at upfront.
- Reliability: Supabase free/pro tier uptime is acceptable for an internal tool; no HA requirement for v1.
8. Success criteria
Being finalized with the team; the current working list:
- Drafts finalized per week, per product (adoption signal).
- Qualitative: fewer instances of two posts covering the same story/feature unintentionally.
- Time from "need a post" to finalized draft, vs. current ad hoc process.
9. Architecture reference
Full technical design (Supabase schema, Edge Function logic, retrieval layer) lives in a companion technical spec — this PRD intentionally doesn't duplicate it.
10. Risks & open questions
- Fabrication risk: thin documentation for a product (e.g. Kavach's current placeholder-heavy KB) increases the chance the model either over-generalizes or produces vague, ungrounded copy. Worth flagging as a known limitation until real field notes are loaded.
- Judgment reliability: the build-on/new-angle decision and topic extraction are both model judgment calls, not deterministic — worth spot-checking early outputs rather than trusting blindly.
- Model drift: hardcoded model strings (e.g. claude-sonnet-5) will need updating as Anthropic ships new versions — not a one-time setup.
- Cost scaling: retrieval adds a second API bill (Voyage) — worth revisiting the "when to build this" threshold in the app spec before committing.
- Auto-posting risk (Phase 3): MCP-based posting means the tool can take real, public, hard-to-undo actions on real accounts. The human-confirmation step needs to be a genuine review, not a rubber-stamp click — worth being deliberate about the UI here when that phase starts.
11. Phasing
- Phase 1 (MVP): folders, document library, generation, feedback/iterate, finalize-gated memory, coverage ledger, multi-user auth/RLS. Plain context-stuffing, no retrieval. Built on Supabase + one Edge Function.
- Phase 2: pgvector retrieval for documents and past posts, once volume justifies it.
- Phase 3:
- Social platform MCP integration — connect to LinkedIn/X (and others as relevant) via MCP so a finalized draft can be posted directly from the tool, rather than copy-pasted out. Posting always requires an explicit human confirmation step per post — this is an assist, not an autonomous posting agent. Which platforms come first, and whether the Wix blog and mailing list need their own publish integrations (since those likely go through different systems, CMS vs. ESP), is still to be decided.
- Performance feedback loop — pull engagement/performance data for posted content (likes, clicks, replies, whatever each platform's API exposes) and feed it back into generation, so the model has a signal for what's landing per product/platform/angle, not just what's already been said. What "performance" means well enough to act on is still an open question — raw engagement counts are a thin signal alone, and this likely needs a minimum sample size or time window before it's trusted as feedback.
12. Open design question raised by Phase 3
Auto-posting and performance feedback both imply the tool holds live credentials to real social accounts and can take real public actions — a different risk profile than the read/write-your-own-database tool Phase 1 is. Worth treating Phase 3 as its own smaller PRD/spec pass when the time comes, particularly around: what "human confirmation" actually requires (a click, or a review window), and whether performance feedback should ever silently change future drafts' tone/angle without a human noticing the shift.
13. Evals: validating generation quality
The first piece of Phase 1 built out past the PRD stage was an eval system for judging generated content quality, starting with OnboardAI and Kul social content — since "grounded, on-brand, non-fabricated" (§6.3) is a claim that needs a way to check it, not just assert it.
13.1 Rubric first
Defined what "good" means for social content across six judgment dimensions, each scored 1–5 with anchors at 1, 3, and 5:
- Hook strength — does the opener earn attention?
- Clarity & scannability — can the point be grasped in 2 seconds of skimming?
- Brand voice fit — does it sound distinctly "us," not generic AI marketing-speak?
- Platform-native feel — does it read as native to the stated platform?
- Factual/product accuracy — is the framing accurate, not just technically true?
- CTA effectiveness — is the call-to-action natural, not bolted-on?
Alongside this, a separate set of deterministic pass/fail checks was defined: character limits, required elements (CTA, hashtags), banned terms, and platform-format fit. These gate shipping regardless of judgment scores.
13.2 LLM-as-judge prompt
Turned the rubric into a system prompt that scores any given post against the six dimensions and returns structured JSON — a 1–5 score plus a one-line reason for each dimension. This is the core scoring engine the rest of the system calls.
13.3 Runnable eval script
run_eval.py wires the deterministic checks and the LLM judge together into one pipeline:
- Run deterministic checks first — free, instant, and a hard block on shipping if failed.
- Call the LLM judge on the content against the rubric.
- Aggregate into a verdict: ship as-is, ship with light edit, or regenerate/rewrite.
13.4 Test fixtures from real data
This is where most of the actual work went, in two rounds.
Good examples. Took 7 real, already-published posts (Kul and OnboardAI, across LinkedIn
and the Wix blog) and split each into a sources component (the real facts — numbers, dates, partner
names — grounding the post) and a draft component (the actual published text). judgeFocus
for these is set to "no scripted failure — calibration check": if the judge scores these low, the judge
itself is miscalibrated, not the content.
Bad examples. Built 3 synthetic drafts on top of the same real sources, each engineered with a specific, named failure:
- Hallucinated features (e.g. a nonexistent AI auto-grading engine, a fabricated solar power source).
- Fabricated or inflated stats (invented placement numbers and success rates).
- Invented or dropped partnerships (fake partners named, real partners omitted).
- Unprofessional tone (slang, excessive emoji, unearned hype).
judgeFocus for these spells out exactly what should be caught (e.g. "claims X, unsupported by
the source above"), so these test judge sensitivity rather than content quality.
13.5 Format iteration
The fixture schema was reshaped twice as the design was refined, ending on a single flat
gradedExamples array — label: "good"/"bad", one draft string,
sources, and judgeFocus — replacing an earlier nested sources/priorDrafts
structure. Simpler to loop over in the runner.
Net result
A working judge, a script to run it, and 10 labeled examples (7 good, 3 bad) to sanity-check that the judge rewards what real good posts do and penalizes what bad ones do — before trusting it on live, unlabeled generations.
Appendix
- Working prototype (Claude.ai artifact): folder/KB/generate/feedback UI, validated interaction model.
- Standalone local version: same UI running against a user-supplied Anthropic API key, for testing outside Claude.ai.
- Technical build spec: internal, not published here.
- Eval system: rubric, LLM-as-judge prompt,
run_eval.py, and 10 labeled test fixtures for OnboardAI/Kul content generation (§13).
Where this stands. Phase 1 implementation is underway; the first piece built past the PRD was the eval system described in §13 — a rubric, an LLM-as-judge scorer, and a labeled set of good/bad test fixtures for OnboardAI and Kul content, so generation quality can be checked rather than assumed. [Add further updates here as Phase 1 progresses — what else got built vs. this draft, any decisions that changed once engineering started, what you'd revise about the PRD itself in hindsight.]