Docs · page 04 of 09
Brands and brand bodies
Running five Pages, or five clients, means one idea has to arrive five times in five different voices — without being written five times. Studio splits that into two objects: the post, which is the idea, and the publication, which is the thing that actually goes out.
The model
Workspace
└ Brand = audience + voice + brief; owns accounts across platforms
├ account (facebook · Page id)
├ account (instagram · IG id)
└ account (linkedin · your own profile)
Post — the SOURCE. One idea, written once.
└ Publication = (post, platform, account) ← the unit that goes out
brand recorded on the row: which brand owns that account
text resolved per field (below)
media the post's shared pool, unless this brand picked its own
submitted a snapshot of what was submitted, taken just before the send
A brand is the authored unit. You write one body per brand, and every account under that brand publishes that body. Ten brands means ten bodies per post, not one hundred.
Where your brands come from
There is nothing to create. A database backfill turns every connected account into its own brand: each Facebook Page becomes a page brand named after it, and your LinkedIn or TikTok connection becomes a personal brand. Five Pages already connected become five brands with zero action.
The backfill runs with the migrations, at server start. An account you connect after that gets its brand the next time the server restarts, not the moment you connect it — so if you have just added a Page and Settings shows no brand for it, that is why.
An Instagram account hangs off the Facebook Page it belongs to, so the Page's brand covers it; it never gets a brand of its own. The Facebook connection itself gets no brand either, because that token unlocks the Pages rather than being a destination.
To rename a brand or write its brief: open the account menu → Settings, find the Brands card, edit the name field, and press Save. The note beside the button reads Saving…, then Saved., then clears. An empty name is refused with a brand needs a name.
The brief, and what reads it
The brief is a free-text box under each brand: Who reads this, what it is about, what tone. Three or four lines. The box stops at 2,000 characters.
The brief is not decoration — it is the instruction Rewrite for this brand reads. A rewrite is grounded in exactly three things, and nothing else:
- The brand's brief — who reads it, what it is about, what tone. With no brief, the model is told to write plainly and specifically instead.
- Up to five of that brand's own published posts, in full — the register, sentence length and vocabulary to match (never to copy). A brand with no published history has none, and the brief carries the voice alone.
- The tightest character limit among that brand's platforms — LinkedIn 3,000, Instagram 2,200, TikTok 2,200, Facebook 63,206.
The workspace voice profile is not part of a rewrite. It shapes the source post you write in Compose; it never reaches a brand body. Your personal LinkedIn profile is a brand like any other, and leaving its brief empty does not hand the rewrite your workspace voice — it just leaves the rewrite with less to go on.
Nothing reformats text per platform at send time, either. The same body goes to every account under the brand; the character limits are enforced by the rewrite prompt and by Compose's pre-flight, not applied as a transformation.
Per-field resolution
Each field of a publication resolves through three levels, and a field wins at the first level where it is set:
per-destination override (one platform + account) → brand body → the source post.
A brand body carries text and hashtags together. The moment you fork, both are copied from the source and from then on belong to the brand — a later typo fix on the source reaches every brand that never forked, and the ones that did get a staleness flag instead of a silent overwrite. Media is the exception: a brand keeps using the post's shared pool until something picks media specifically for it.
Good to know — the top level exists in the database (
publications.body_text) but no screen or endpoint writes it yet. Today everything you author in Compose is a brand body.
Working a brand's own body in Compose
Save the draft first. The brand controls need a post id; on a brand-new unsaved draft they do not appear at all.
Open a saved post in Compose and look under Publish to — the list of ticked destinations beside the Save button. Beside some accounts there is one extra control:
- Write a different one — this brand still follows the source. Clicking it saves the source (if you have unsaved edits), copies that exact text and tags into a new brand body, and opens it in its own tab.
- ★ own body — this brand already has a body row; click to open its tab. A ⚠ stale badge sits beside it when the source has moved on.
The control appears beside your LinkedIn row, and beside each Facebook Page and Instagram row when the workspace has more than one Page. It does not appear on TikTok, and it does not appear on Facebook or Instagram in a one-Page workspace — those brands exist, but Compose has no way to fork them yet.
A brand tab is titled ★ <Brand> — <post title> and carries a banner: Media, destinations and the schedule follow the source post. Only the text and tags here belong to <Brand>. Inside a brand tab the media tray, the pre-flight box and the apply sheet are hidden, and the destination ticks are shown but not clickable — all three belong to the source.
The first fork is deliberate and starts as a copy. After that, autosave never prompts: Compose saves two seconds after you stop typing, silently, to whatever tab you are editing — source or brand. Nothing asks "apply this to the others?" at save time.
To push source text into brands that already diverged: on the source tab, under Publish to, a line reads how many brands have their own body, with Apply this text to… beside it. The brands are listed unticked by default — a brand that diverged did so on purpose. Tick the ones that should take it and press Apply; with none ticked you get Tick the brands that should take this text.
To undo a fork entirely, use Follow the source again in the brand tab. It confirms first, because the brand's text is deleted.
Staleness
The stale flag compares canonical content fingerprints of the source: whitespace is collapsed and hashtags are lowercased, de-duplicated and sorted, so reformatting a paragraph or reordering tags never trips it. It is computed at read time from the live source text, not from a stored column, so it is right the moment you look.
The brand tab's banner is bordered amber normally and red when the body is stale, and its sub-line changes to ⚠ The source text changed since this was written. Keep yours, or take the new source.
Three buttons sit in that banner at all times, stale or not:
- Re-copy the source — replace this body with the source's current text.
- ↻ Rewrite for this brand — keep the idea, change the words.
- Follow the source again — delete this body (confirms first).
Rewrite for this brand
The rewrite keeps the idea and changes the words, grounded as described above. It invents no facts, names, numbers or first-person experiences that are not in the source, and it never publishes anything.
Which engine answers depends on the workspace:
- The owner's workspace queues it on the free path and spends nothing. The toast reads Queued on the free path — <Brand>'s body lands in this tab when the drain runs. In practice the open tab does not refresh itself for a brand body: once the drain has run, close the brand tab and reopen it from ★ own body, or reload Compose, to see what was written.
- Any other workspace spends one credit and answers immediately with a proposal you accept with Use this or throw away with Discard. Balance and ledger live in Settings → Rewrite credits. There is no self-serve purchase during the beta — the Studio owner grants credits to a workspace by hand.
Until a billing key exists on the server, that second path answers 503 with the rewrite engine is not configured on this server, and the Rewrite credits card says so in advance. Out of credits is 402 with no credits left (balance 0); a source post with no text is 422 with the source post has no text to rewrite. A model call that fails or returns nothing refunds the credit.
Per-brand format defaults
Instagram and TikTok need a picture or a video. For a brand that has an Instagram account, Settings → Brands shows Instagram, when a post has no media: with two options — Make a quote card from the text or Leave it — Compose asks me (the default). The card is the opening line rendered on a 1080×1080 card by ffmpeg, at no cost and with no model, and it is selected for that brand only, so the source can stay text-only for LinkedIn.
That default is applied automatically in one place: a draft created by Pollinate or by the unattended seeder below. Everywhere else it is Compose's pre-flight that offers Make a quote card for <Brand> at the moment it matters.
TikTok has no zero-cost adaptation, so it has no default of its own; the pre-flight says TikTok needs a video; add one under Media, or untick TikTok for this post.
Auto-seeding from your own hits
A hit is a publication that performed, ranked against its own brand on its own platform — the Hits view in the main nav is where you see them, and Pollinate there carries one to another brand by hand.
Each brand row in Settings also carries the switch Let hits from this brand seed my other brands — one draft per brand, never published by itself, off by default. You choose what counts as a hit — top 10% / 25% / 50% of that brand's own posts over the last 14 / 30 / 90 days — so a small Page's best post can outrank a big Page's average one. A (brand, platform) group with fewer than three measured publications is never ranked and never seeds.
The seeder runs unattended on the server about two minutes after start and every six hours after that; Hits can also run it on demand. It makes drafts only, carries a hit to your other brands (never back to the brand it came from), seeds a given hit into a given brand once ever, and stops at four new drafts per workspace per run.
Validation: Compose blocks, the scheduler does not
Compose checks every destination against the body it will actually get — its brand's, or the source — and its own platform limit. A problem disables Publish and Schedule, puts Fix first: … on their tooltips, and lists each problem in a red box under Publish to, for example ⚠ Taara AI: Instagram needs a picture or a video, and this post is set to text only or ⚠ Taara AI: 3,210 chars, Instagram allows 2,200.
The scheduler never blocks. At send time it sends the publications that are valid and records the rest, one row each, with the error and a snapshot of what was attempted. One bad destination does not hold up the other nine.
What brands do not do yet
- Reassigning accounts between brands — the backfill's assignment stands; there is no screen or endpoint to move an account to another brand.
- A fork for every destination — TikTok, and Facebook/Instagram in a one-Page workspace, have brands but no "Write a different one" control in Compose.
- Per-publication stagger — every publication fires at the slot time. The
send_atcolumn exists and is unused. - Same-brand-other-platform seeding — auto-seeding carries a hit to your other brands only, not to another platform under the same brand.