Skip to content

Character creation is composed from versioned recipe capabilities

Character creation needs to support materially different journeys without a new frontend and backend branch for every product variation. Examples include a visual character assembled from questionnaire choices, an initial real-identity character created from one photograph, and a subsequent real-identity character improved with optional body and angle references. Future journeys may reorder those steps, add a paywall, reuse a form as an editor, change a workflow, or insert another validation step.

Encoding each journey as application control flow would couple product iteration to coordinated Brisket, Sirloin, and Brain releases. It would also make labels such as “VI”, “RI Initial”, and “RI Subsequent” into domain discriminators even though they describe product recipes rather than different character storage models. The shared contract carries the durable Real/Virtual identity category that Sirloin stores on the character (proto/tbone/v1/character_recipe.proto:9-30), while creation-method provenance remains the pinned recipe key, version, and digest (proto/sirloin/v5/character_recipe.proto:34-42).

The design must satisfy four forces at once:

  1. Product authors must be able to compose supported creation flows and configure reviewed management features from data.
  2. Sirloin must remain authoritative for state, authorization, billing, assets, and transitions.
  3. Recipes must not inject arbitrary frontend code or make Brain the owner of the character lifecycle.
  4. Unreleased Characters V2 work must remain invisible to customers who do not have the feature flag, without changing the existing CCV4 route.

The detailed runtime topology, concurrency model, and known production deferrals are documented in the Character Recipe Engine. This ADR records the higher-level capability decision and the rules for extending it.

Character creation and recipe-backed management use versioned recipe capabilities, with deliberately different flexibility. Creation recipes arrange code-owned stages, controls, actions, transitions, and conditions. Management recipes select and configure code-owned Name, Likeness, and Settings features, or launch a reviewed named flow. CAPABILITY launches a flow with a server-applied grant; FLOW launches a standalone non-granting flow and cannot reuse the creation flow, including through another flow. A standalone flow with an account-visibility gate cannot transition into another flow; its protected continuation remains in the same flow so eligibility can be rechecked. Creation flows containing NSFW_UNSUBSCRIBED_ONLY or KYC_UNVERIFIED_ONLY cannot leave their flow, and no account-gated capability-granting flow can leave its flow. This keeps the new adult prerequisites recheckable without changing the existing generic UNSUBSCRIBED_ONLY creation transition into a granting flow. Both surfaces may reference bounded fields, asset roles, billing policies, and immutable Brain workflow pins, but recipes do not define executable code or weaken service-owned invariants.

The T-Bone CharacterRecipe envelope contains:

  • a stable route key and customer-facing catalog entry;
  • the durable Real/Virtual identity category produced by the recipe;
  • named presentation flows, one selected by creationFlowKey, and a bounded management feature/launcher catalog;
  • workflow contract pins;
  • recipe-local billing policies;
  • protected policy values; and
  • a terminal output schema (proto/tbone/v1/character_recipe.proto:21-30).

Sirloin stores canonical immutable versions and routes a recipe key to one active version. Publication verifies the canonical bytes and validated digest, allocates a new version, and atomically moves the active route (apps/sirloin/internal/pkg/storage/characterrecipepublication.go:221-342). A running session remains tied to its pinned version; publishing a later version does not silently rewrite that session.

Catalog order and names are presentation data. The current seedable fixtures demonstrate six creation routes across two durable identity types: four RI routes and two VI routes. Route keys do not become additional character types:

Fixture key Customer title Identity type Demonstrated composition
initial-vi-sfw Custom AI influencer Virtual Female questionnaire, preparation and gallery acceptance; adult enablement uses an 18+ subscription, pubic-hair settings, generated preview, and explicit acceptance.
initial-vi-male-sfw Custom male AI influencer Virtual Male questionnaire and the same creation flow; adult enablement adds penis-size and pubic-hair settings.
initial-ri-sfw Initial RI SFW Female Real Female initial creation, plus existing-character adult enablement with subscription and account KYC gates.
digital-twin-sfw Subsequent RI SFW Female Real Female subsequent creation with required frontal face and optional full-body and angled-body references before preview generation, plus the same adult-enablement management flow.
initial-ri-male-sfw Initial RI SFW Male Real Male initial creation, plus the same adult-enablement management flow.
digital-twin-male-sfw Subsequent RI SFW Male Real Male subsequent creation with required frontal face and optional full-body and angled-body references before preview generation, plus the same adult-enablement management flow.

The fixture catalog entries are source evidence for those names and entry stages (apps/flank/app/fixtures/character-recipes/initial-vi-sfw.json:1-23, apps/flank/app/fixtures/character-recipes/initial-ri-sfw.json:1-24, apps/flank/app/fixtures/character-recipes/digital-twin-sfw.json:1-24, apps/flank/app/fixtures/character-recipes/initial-ri-male-sfw.json:1-24, and apps/flank/app/fixtures/character-recipes/digital-twin-male-sfw.json:1-24).

Layer Owns Must not own
Flank / recipe JSON Authoring, section editing, validation feedback, immutable publish and rollback requests, workflow-pin bump assistance, and reference fixtures. Customer session state, billing decisions, or workflow execution.
Sirloin Canonical recipes, publication, account-scoped sessions and characters, action availability, authoritative transitions, uploads and asset provenance, billing reservation/settlement, execution rows, and capability-projected reads. The full Brain node graph or customer rendering.
Brisket A generic creation runner over reviewed stage/control variants; code-owned management feature layouts; local draft UX, accessibility, subscription presentation, and Characters V2 views. Authoritative progression, arbitrary recipe-provided components or management journeys, billing authority, or Brain calls.
Brain Published CHARACTER_COMPUTE graphs, allowed-node enforcement, Bull execution, terminal output projection, and completion notification. Recipe routes, character/session ownership, entitlement, or billing.

Flank exposes a dedicated character-recipe editor that works with route versions, section buffers, publish, rollback, and workflow-pin bumps (apps/flank/app/routes/character-recipes.tsx:55-112). Brisket accepts only the registered kinds and variants in its trusted registry (apps/brisket/src/features/characters-v2/recipe/capabilities.ts:12-82) and rejects unknown or malformed recipe data instead of treating JSON as executable UI (parseCharacterRecipe in apps/brisket/src/features/characters-v2/recipe/parse-character-recipe.ts, apps/brisket/src/features/characters-v2/recipe/validate-character-recipe.ts:35-67).

flowchart LR
  A["Flank: author recipe"] -->|"draft / validate / publish"| S["Sirloin: recipe + session authority"]
  B["Brisket: trusted recipe runner"] -->|"action id + revision + inputs"| S
  S -->|"pinned workflow UUID + digest + bound inputs"| R["Brain: CHARACTER_COMPUTE"]
  R -->|"terminal outputs + asset metadata"| S
  S -->|"authoritative stage, operation and projection"| B

The contract intentionally uses a finite vocabulary:

Capability What the recipe can choose Guardrail
Stage Form, upload, review, asset picker, confirmation, or status; plus a reviewed variant and presentation copy. Brisket and Sirloin both reject unsupported kinds or variants.
Field Text, option grid, chips, color, multi-select, or media upload with validation and visibility conditions. Requiredness, media limits, keys, conditions, and upload shape are validated before publication.
Action Save, upload, execute, select asset, authorize, or local; label, role, bindings, running stage, entitlement, and successful transition. Sirloin resolves the action from the pinned recipe; the client cannot invent semantics.
Transition Stay, next visible stage, back through history, or explicit stage. Targets must exist in the same flow and conditional chains require an unconditional fallback.
Condition Exists, equals, all, any, or negation over allowed session paths. Conditions are declarative and path-validated; they are not expressions or scripts.
Workflow pin Recipe-local workflow id mapped to a published Brain UUID, graph digest, input bindings, output schema, and asset-output cardinality. Execution uses the immutable UUID and digest, never a workflow name or latest version.
Billing policy Recipe-local credits and included-use count, referenced by catalog display and/or an action. Declaring a catalog price does not charge; only executing an action that references the policy can reserve credits.

The wire vocabulary is defined in proto/tbone/v1/character_recipe.proto:104-148, proto/tbone/v1/character_recipe.proto:150-221, proto/tbone/v1/character_recipe.proto:223-324, and proto/tbone/v1/character_recipe.proto:326-377. Adding a new arrangement of existing capabilities is a recipe change. Adding a genuinely new stage kind, control, action semantic, condition operator, or trusted component is a contract and consumer change and must not be simulated through unvalidated props.

Stage order is useful but not sufficient to determine progression. Sirloin owns the current stage and visited-stage history. It evaluates action conditions, back barriers, explicit transitions, and return-to-caller behavior (apps/sirloin/internal/pkg/storage/characterrecipecapabilities.go:356-383, apps/sirloin/internal/pkg/storage/characterrecipecapabilities.go:406-451, apps/sirloin/internal/pkg/storage/charactersessionaction.go:327-369, apps/sirloin/internal/pkg/storage/charactersessionaction.go:503-544).

The following semantics are load-bearing:

  • NEXT selects the next visible stage and requires an unconditional fallback.
  • STAGE targets a named stage in the same flow.
  • BACK follows server history and respects barriers. The action role controls presentation: BACK is reserved for the header control, while SECONDARY with a BACK transition is a visible body action that returns through the same history.
  • STAY performs an intentional action without implying navigation.
  • return_to_caller_if_present lets one local form return to the stage that opened it, while retaining a declared NEXT fallback when used as the entry flow.
  • An automatic action is a server-owned AUTHORIZE finalization with workflow and billing-policy references. It must require an active subscription, be unique within its stage, and leave the stage. Publication rejects other automatic action shapes and automatic STAY actions (apps/sirloin/internal/pkg/storage/characterrecipecapabilities.go:183-190, apps/sirloin/internal/app/services/characterrecipecapabilityvalidation.go:444-450, apps/sirloin/internal/app/services/characterrecipecapabilityvalidation.go:532-545, apps/sirloin/internal/app/services/characterrecipecapabilityvalidation.go:661-667).

Brisket may optimistically show a recipe-authored running stage, but it still waits for the server session to confirm progression. It distinguishes stage-leaving actions from intentional STAY actions so a successful non-navigating action does not remain falsely pending (apps/brisket/src/features/characters-v2/views/creation/recipe-runner/components/action-buttons/index.tsx:14-60, apps/brisket/src/features/characters-v2/views/creation/recipe-runner/stage-actions/hooks/use-stage-action-controls.ts:147-173).

Local data is presentation state, not authority

Section titled “Local data is presentation state, not authority”

presentation.*.local_data supports temporary browser-owned values such as a photo held before subscription. It cannot participate in server conditions, billing, workflow inputs, or persisted session state (proto/tbone/v1/character_recipe.proto:107-112). A LOCAL action is still sent to Sirloin for the authoritative transition; local values do not turn the browser into the navigation authority (proto/tbone/v1/character_recipe.proto:219-223).

Initial RI starts at the paywall, which is its only unsubscribed-only stage. After checkout, the validation upload stage asks the user to select a photo and pins the reference-validation workflow (apps/flank/app/fixtures/character-recipes/initial-ri-sfw.json).

Older pinned recipe versions may still begin with a pre-paywall upload and contain the legacy postPaywallUploadStageId property. Brisket accepts those recipes only when they otherwise satisfy the current recipe schema. It ignores the declaration and treats their post-paywall upload as manual. During the forward-only POC, incompatible versions must be discarded or republished. Sirloin retains the property allowlist only so compatible legacy versions remain readable (apps/sirloin/internal/app/services/characterrecipepropsvalidation.go).

Compute is bound by contract, not by stage name

Section titled “Compute is bound by contract, not by stage name”

An execute/upload/authorize action references a recipe-local workflow id. Sirloin resolves its value and asset bindings into a validated input snapshot, verifies the pinned Brain workflow UUID and graph digest, and only then reserves billing and creates the queued execution (apps/sirloin/internal/pkg/storage/characterexecution.go:319-365). The full node graph remains in Brain; the recipe stores only the immutable pin and the contract projection (proto/tbone/v1/character_recipe.proto:293-313).

Brain runs CHARACTER_COMPUTE through its existing workflow execution machinery and returns terminal outputs and asset metadata through the character-completion webhook. Sirloin binds those assets against the recipe contract; Brain does not write character or billing state (apps/brain/src/modules/domain/workflow/services/sirloin-webhook.service.ts:51-92).

Therefore workflow invocation is attached to an action, not inferred from a particular stage position. An author can make either Continue or Skip start provisioning, provided each action has the required workflow, input bindings, billing policy when applicable, running stage, and successful transition. Moving a stage does not implicitly move a charge or compute call.

Account routing, entitlement, and billing are separate

Section titled “Account routing, entitlement, and billing are separate”

Stage-level stage_visibility defines account-owned prerequisites. EVERYONE is unconditional; UNSUBSCRIBED_ONLY is visible without an active paid subscription; NSFW_UNSUBSCRIBED_ONLY is visible without an active full-access (18+) subscription; and KYC_UNVERIFIED_ONLY is visible until the account has a usable approved identity, including the identification ID and selfie required by character workflows, or an explicit Strip bypass. Historical age-only approval does not satisfy this identity gate. The reviewed confirmation.paywall, confirmation.full-access-paywall, and confirmation.kyc-verification renderers correspond to those three gates. The ordinary paywall keeps the existing subscription checkout layout; full-access has its own stage variant and layout instead of branching the ordinary paywall view.

Sirloin resolves stage visibility on Start, Get, and before and after actions, and prunes skipped account gates from Back history. The same prerequisite resolution applies to creation, capability-granting, and standalone management flows, so checkout or KYC completion can advance the current flow without a client-authored Continue action, while a later eligibility loss rewinds it to the earliest visible account gate in that flow. Each account gate requires a later unconditional EVERYONE successor and cannot carry a back/history barrier (apps/sirloin/internal/app/services/characterrecipecapabilityvalidation.go:153-188). Brisket starts checkout or KYC from the reviewed renderer, then refetches the session and renders the authoritative stage Sirloin returns.

Adult-enablement KYC uses the account-scoped CHARACTER_ADULT_ENABLEMENT purpose. It does not persist a legacy pending RI/VI type, and an approved callback does not create the legacy KYC gift character (apps/sirloin/internal/app/services/kyc/startkycverification.go:56-70, apps/sirloin/internal/app/services/characters/ondatowebhook.go:704-725).

An action-level active-subscription entitlement is server authorization for a protected operation. It neither selects checkout UI nor changes stage routing, and it is not itself a credit charge. Protected actions may only live on EVERYONE stages, and every authored path to them must first cross a subscription gate appropriate to the protected operation; publication rejects a branch that reaches or jumps to a protected action before that gate. A billing_policy_id on the action selects the charge policy for that specific operation (apps/sirloin/internal/app/services/characterrecipecapabilityvalidation.go:131-190, proto/tbone/v1/character_recipe.proto:269-291).

For a billed action, Sirloin validates inputs first, then reserves the account’s first-character allocation or credits before it inserts the execution. Every billed action that invokes a character.provision workflow can consume that one-time global allocation, regardless of whether its action kind is AUTHORIZE or EXECUTE. Insufficient credits reject the action before compute starts (reserveCharacterActionBilling in apps/sirloin/internal/pkg/storage/characterbilling.go, apps/sirloin/internal/pkg/storage/characterexecution.go:319-365). Successful terminal completion captures the reservation; failure releases it (settleCharacterBilling in apps/sirloin/internal/pkg/storage/characterbilling.go). This prevents a recipe from finalizing expensive work and charging afterward.

The catalog’s billing policy reference exists only to show the matching price before the user starts. It must not be used to infer where charging happens.

Rendering and asynchronous states are recipe-aware but code-owned

Section titled “Rendering and asynchronous states are recipe-aware but code-owned”

Brisket’s runner derives the current renderer from the stage kind and reviewed variant, keeps the server stage authoritative, and shows a stable unsupported state if the pinned stage is absent (apps/brisket/src/features/characters-v2/views/creation/recipe-runner/stage-renderer/index.tsx:69-95, apps/brisket/src/features/characters-v2/views/creation/recipe-runner/index.tsx:92-108). When a session is running, action controls are locked so repeated clicks cannot submit the same transition. The processing-status renderer deliberately keeps only plain text draft fields editable while non-text inputs and actions remain locked (apps/brisket/src/features/characters-v2/views/creation/recipe-runner/index.tsx:49-50, apps/brisket/src/features/characters-v2/views/creation/recipe-runner/stages/status/processing/index.tsx:119-154).

Each recipe-runner stage view owns the arrangement of its fields, media, and controls. Shared presentation primitives remain reusable, but progression stays in the generic runner and Sirloin. Management uses an explicit feature kind. Name, Likeness, and Settings each have a code-owned Brisket component, while CAPABILITY and FLOW launch reviewed named flows in the generic runner. Direct launcher section URLs canonicalize to the management overview; the launcher has no empty focused panel. Sirloin publication and Brisket parsing enforce matching renderer/field/action boundaries, including the bespoke recipe-runner stages and bounded management shapes (apps/sirloin/internal/app/services/characterrecipevalidation.go:603-887, apps/brisket/src/features/characters-v2/recipe/validate-character-recipe.ts:244-748).

COMING_SOON management sections are display-only entries with recipe-owned title, description, and optional icon. They have no flow, summary, availability condition, or discard behavior. Sirloin projects them as unavailable with reason coming_soon; Brisket keeps them visible as disabled buttons with a “Coming soon” label and returns direct section URLs to the overview. Existing management kinds retain their behavior (proto/tbone/v1/character_recipe.proto).

Both VI recipes place an adult capability launcher between Name and Enhance me. Their adult flow reuses the full-access paywall, settings form, processing reveal, and success renderers. Accepting the preview is a synchronous SAVE to success; Adjust settings returns to the form for another derivation. Female VI derivation preserves existing body proportions without a bust-size input. Existing RI graph pins and identity-verification flows remain unchanged (apps/flank/app/fixtures/character-recipes/initial-vi-sfw.json, apps/flank/app/fixtures/character-recipes/initial-vi-male-sfw.json, apps/brain/src/modules/domain/workflow/poc-fixtures/character-compute.fixtures.ts).

Sirloin checks the stored character type when granting adult capability. Virtual characters use a character-scoped virtual:<character-id> anchor and instant verification; this does not occupy a human identity’s RI slot or claim KYC approval. The VI preview must contain all three NSFW roles generated successfully in the current session, derived from the corresponding generated SFW references for that character. RI identity, attestation, and slot checks retain their existing rules (apps/sirloin/internal/pkg/storage/characteradult.go, apps/sirloin/internal/pkg/storage/charactersessioncommit.go).

The stage title and description define its ready-state copy. Recipe props may provide loading, processing, failure, and finalization-failure overrides; renderers supply conservative fallbacks through stageStateCopy in apps/brisket/src/features/characters-v2/views/creation/recipe-runner/stage-copy.ts. Photo validation is one shared stage variant for face, full-body, and alternate-angle roles. Its passed title and description come from the stage, while processingTitle, processingDescription, and successLabel are validated recipe overrides. Older pinned versions fall back to their own stage copy, not renderer-owned product wording. The supplied photo remains visible during validation, and success means that it passed requirements—not that a character or dataset is complete (apps/brisket/src/features/characters-v2/views/creation/recipe-runner/index.tsx:123-163, apps/brisket/src/features/characters-v2/views/creation/recipe-runner/stage-renderer/index.tsx:150-165, apps/brisket/src/features/characters-v2/views/creation/recipe-runner/stages/photo-validation/index.tsx:25-142, and apps/sirloin/internal/app/services/characterrecipepropsvalidation.go:118-126).

Current VI recipes show one generated character gallery. Appearance changes and re-rolls generate a replacement gallery, then return to the mirror reveal. Accepting the gallery commits the character and opens success. The discarded multi-candidate VI recipe and its dedicated compute workflows are not retained; the generic asset-picker component remains reusable.

The fixture JSON files are executable examples and contract-test inputs. They are not a production migration system. The bootstrap seed planner fills only an empty or draft-only route and deliberately skips a route with a published active version (apps/flank/app/lib/character-recipe-seed.ts:10-36). The script likewise refuses to overwrite a published recipe and requires intentional changes to be made through Flank or after explicitly clearing disposable local data (apps/flank/scripts/seed-character-recipes.ts:1-8, apps/flank/scripts/seed-character-recipes.ts:43-68).

POC Brain workflow fixtures are also opt-in. The startup service returns without touching workflow storage unless BRAIN_CHARACTER_COMPUTE_FIXTURE_SEED_ENABLED=true; when enabled, it creates and publishes missing fixtures but skips existing published rows (apps/brain/src/modules/domain/workflow/poc-fixtures/character-compute-fixture-seed.service.ts:12-70). Preview and production must leave both recipe and Brain fixture seeding disabled.

The promotion order is:

  1. Author and publish a Brain workflow with purpose CHARACTER_COMPUTE.
  2. Record its UUID, graph digest, input binding contract, output schema, and asset outputs in the recipe workflow pin.
  3. Save the recipe as a draft and resolve every validation issue.
  4. Publish the Sirloin recipe version to move the route.
  5. Test through the Characters V2 feature flag before any broader rollout.

Strict typed management clients must ship after every active and rollback-routable recipe route contains the Name, Likeness, and Settings feature catalog. The current forward-only POC may discard old local data; a released compatibility window would instead require retaining the legacy Brisket renderer until route migration ends.

Adult Likeness slots are not part of the direct visual-only Likeness panel in this milestone. The adult-enablement FLOW already supports grouped adult uploads, as described in the recipe engine. Before a recipe exposes adult slots in a direct panel, Sirloin must derive slot visibility from the character’s effective adult capability and omit inaccessible adult slot metadata and signed asset URLs from the customer projection. Brisket must render that server-resolved view rather than infer eligibility or hide already-delivered adult assets.

Publishing Brain alone never changes a live recipe. Publishing the recipe is the explicit ship gate. This store split is governed by Character compute graphs pin by Brain workflow UUID.

Use this decision table before changing code:

Desired change Recipe-only? Required work
Rename a recipe, stage, field, option, button, or state copy Yes Edit catalog/stage/action data; publish a new recipe version.
Reorder unconditional stages Usually Recheck NEXT, conditional fallbacks, running stages, back barriers, and final stage validity.
Insert a supported form, upload, confirmation, review, picker, or status stage Usually Use a registered kind/variant and valid actions/bindings.
Make Continue or Skip start compute Yes Put the workflow and optional billing policy on that exact action; define running and success destinations.
Reuse a creation form as an editor Yes Use a LOCAL action with NEXT plus return_to_caller_if_present; do not duplicate the form.
Reorder management cards or change their bounded copy/options Yes Keep exactly one Name, Likeness, and Settings feature and satisfy that feature’s validated envelope.
Add a new management interaction or lifecycle No Add a reviewed feature kind, Sirloin validation/runtime behavior, a dedicated Brisket component, Flank support, and cross-consumer tests. Do not author management stages or transitions to emulate it.
Change Brain compute internals without changing its contract Brain workflow only, then repin Publish a new workflow UUID and explicitly bump the recipe pin.
Change workflow inputs, outputs, or asset roles No single-file change Update Brain graph contract and recipe pin together; validate every consumer.
Explain to a customer why compute declined their input No single-file change Return the verdict and its message in the workflow’s declared output and render that; do not surface the execution error field, which carries unfiltered engine text.
Let a compute step reject input as an expected outcome No single-file change Complete the workflow successfully with a declining verdict; reserve execution failure for faults. Give the accept-only asset roles minCount: 0.
Add a new UI interaction that no registered variant supports No Add a reviewed contract capability, Sirloin validation/runtime semantics, Brisket renderer, Flank authoring support, and tests.
Add arbitrary JavaScript, component names, URLs, or condition expressions through props No Rejected by design; add a bounded typed capability instead.
Make a recipe bypass billing, authorization, ownership, or asset validation No Forbidden; those are Sirloin invariants.

Before publishing a changed recipe, verify these invariants:

  • Every stage/action/field key is stable and unique in its scope.
  • Every transition has an explicit kind and a reachable valid target.
  • Conditional stage chains end in an unconditional fallback.
  • Automatic actions are unique, entitled, and stage-leaving.
  • Workflow and billing behavior is attached to the intended action rather than inferred from layout.
  • Required value and asset bindings are satisfiable at the action’s source stage.
  • Running stages are unconditional and accurately describe the in-flight work.
  • Output schemas and asset roles/cardinality match the pinned Brain workflow.
  • A workflow that can decline declares its verdict field as required in outputSchema, and the asset roles it produces only when accepting use minCount: 0 so a decline is not rejected at commit.
  • Presentation media uses approved first-party paths and alt text.
  • Existing session compatibility is considered before deleting or renaming keys.

Characters V2 has two Brisket gates. The characters route renders CCV4 unless the characters-v2 PostHog flag is true (apps/brisket/src/app/(main-layout)/characters/page.tsx:1-28), and the Characters V2 tRPC router independently rejects direct access for accounts without that flag (apps/brisket/src/server/api/routers/characters-v2.ts:25-40). Consequently, the recipe UI and API are not exposed merely because the code and recipe rows exist.

This isolation does not by itself declare the POC production-ready. Public rollout still depends on the reliability and capacity deferrals in the Character Recipe Engine, including durable Brain completion delivery and dispatch idempotency. Feature-flag removal is a separate release decision.

  • Product teams can create materially different onboarding journeys by composing a bounded vocabulary instead of adding recipe-specific service branches.
  • VI/RI labels remain presentation concepts; character storage and lifecycle stay uniform.
  • Stage order, compute, entitlement, and billing are explicit and independently reviewable.
  • Workflow changes are versioned and pinned, so a Brain publish cannot silently alter a customer flow.
  • The same forms and reference-update flows can be reused without copying UI code.
  • Invalid or unsupported recipe capabilities fail before publication or at a stable customer boundary rather than executing partially.
  • The feature can ship dark behind Characters V2 gates while CCV4 remains the default.
  • Recipe publication becomes a real release boundary and needs the same review care as code.
  • Flexibility is bounded by the trusted capability registry. A genuinely new interaction still requires coordinated contract, validation, and renderer work.
  • Stage/action JSON can be structurally valid yet product-confusing. Automated validation cannot replace flow walkthroughs.
  • Changing stable keys can strand pinned sessions. Forward-only POC cleanup is acceptable while unreleased; post-release compatibility requires an explicit migration policy.
  • Brain and recipe fixtures are useful for local POC bootstrap but are not an operator-safe production provisioning mechanism.
  • Recipe-only regression: route the recipe key back to a previously published version that still passes the current publication and workflow-pin checks.
  • Compute regression: republish a recipe version pinned to the previous Brain workflow UUID and digest.
  • Customer-surface regression: disable the characters-v2 feature flag; CCV4 remains the route fallback.
  • Do not roll back by editing immutable published bytes, following latest workflow by name, or mutating sessions in place.
  • Hardcode one React/backend flow per recipe. Rejected because every product rearrangement would require coordinated releases and duplicate session, billing, and error handling.
  • Add VI/RI/subtype columns and switch statements throughout the stack. Rejected because these labels describe recipe provenance, not different persistence or lifecycle semantics.
  • Let recipes name arbitrary React components or execute scripts. Rejected because recipe publication would become code deployment and bypass review, security, and compatibility boundaries.
  • Make Brain own the entire onboarding and character lifecycle. Rejected because Brain is the compute engine; it should not own customer session, recipe routing, entitlement, billing, or durable character authority.
  • Infer compute and billing from stage names or positions. Rejected because reordering a recipe would silently move side effects. Actions explicitly own workflow and billing references.
  • Always use the latest Brain workflow by name. Rejected because a workflow publish would change active funnels without a recipe review or rollback pin.
  • Automatically overwrite published recipes/workflows from fixtures on startup. Rejected because deployment would become an implicit production migration and could replace operator-authored definitions.