Skip to content
AI Creative Platform

Ghostviber

Ghostviber is an AI creative platform built around the full songwriting workflow, from the first line to generated audio and artwork. I designed and built the product architecture, multilingual rhyme engine, AI orchestration, credit ledger, asynchronous workflows, storage, and reliability mechanisms needed to turn variable external providers into one coherent user experience.

Status
Active
Role
Founder & Engineer
Category
AI Creative Platform
Source
Closed source

Case study

Ghostviber began with a simple product question:

What would a creative workspace look like if AI supported the entire process of writing a song instead of appearing as a separate chat window?

The answer became much larger than a prompt wrapped in a user interface.

A songwriter may begin with one line, search for a rhyme, ask for a continuation, compare several versions, analyze the text, generate a title, create a cover, or turn an idea into audio. Those actions feel connected to the user, but they rely on very different technical systems.

Some need interactive latency. Others may take minutes.

Some are deterministic and should always produce repeatable results. Others depend on probabilistic models.

Some cost almost nothing to execute. Others create a direct variable cost with an external provider.

Some can be retried freely. Others need idempotency, credit reservations, and careful recovery because the provider may have completed the operation even when the application did not receive the response.

I built Ghostviber from the ground up as both a product and a production system. That included the domain model, backend architecture, AI workflows, subscriptions, credit accounting, real-time rhyme search, generated media, asynchronous processing, storage, caching, deployment, and operational foundations.

The central engineering challenge was to make all of that complexity disappear from the creative experience without hiding it from the system itself.

The product problem

Most AI writing products begin with the model.

They expose a text box, send a prompt, return a response, and rely on the user to move the result into the rest of their workflow.

Songwriting does not work that way.

A song evolves through small decisions. A writer changes one word because the rhythm is wrong. A good line creates a new direction for the verse. A rhyme may be phonetically strong but semantically useless. A suggestion may sound good in isolation while ignoring the vocabulary, tone, or structure established earlier in the song.

The product therefore needed to understand more than the latest prompt.

It needed to support the working context around the song:

  • the current line and surrounding verse;
  • previous versions;
  • the artist’s own vocabulary and phrasing;
  • rhyme and sound relationships;
  • structural and thematic context;
  • generated alternatives that were considered but not accepted;
  • media assets connected to the same creative project;
  • the economic cost of each AI-backed action.

The user should not need to think about providers, queues, token budgets, model limits, storage systems, retries, or billing state.

They should be able to stay inside the act of writing.

That product goal created the architecture.

My role

I designed and implemented Ghostviber as its founder and engineer.

My work covered the complete path from product idea to deployed system:

  • defining the product and its creative workflows;
  • shaping the core domain model;
  • building the backend APIs and application services;
  • designing the multilingual rhyme engine;
  • integrating language, image, and audio generation providers;
  • creating asynchronous media-processing workflows;
  • building subscriptions and a credit-based billing model;
  • implementing the credit ledger, reservations, locks, and settlement logic;
  • introducing retries, fallbacks, validation, and provider failure handling;
  • designing storage and delivery for generated assets;
  • adding caching, operational monitoring, backups, and deployment automation;
  • evolving the architecture as the number of workflows and external dependencies increased.

This was not a project where the product layer and infrastructure layer could be designed independently.

A decision about the user experience often changed the failure model. A billing decision affected queueing and idempotency. A personalization feature required new data-processing workflows. A provider integration created new cost and recovery concerns.

The architecture had to support product iteration without making those dependencies implicit.

The system behind the workspace

The core product is organized around songs and creative workspaces.

A workspace brings together several types of state:

Song
 |
 +-- current text
 |
 +-- structured sections
 |
 +-- version history
 |
 +-- notes and ideas
 |
 +-- AI suggestions
 |
 +-- analysis results
 |
 +-- generated images
 |
 +-- generated audio
 |
 +-- style context
 |
 +-- usage and billing history

Not all of this state has the same meaning.

User-authored text is the source of truth.

An AI suggestion is a candidate, not an automatic mutation.

An analysis result is derived state that may become stale when the text changes.

Generated audio and artwork are asynchronous assets with their own lifecycle.

Style context is built from the user’s work but is not itself part of the song.

Billing records describe economic events and must remain correct even when the creative operation fails.

Separating those responsibilities was essential. Treating the entire workspace as one mutable document would have made versioning, retries, provenance, and partial failure much harder to manage.

Architecture

Ghostviber uses a modular application core supported by focused asynchronous workers and external services.

The high-level architecture looks like this:

Web application
      |
      v
Application API
      |
      +--> authentication and authorization
      |
      +--> songs and versions
      |
      +--> rhyme search
      |
      +--> AI operation orchestration
      |
      +--> subscriptions and credits
      |
      +--> asset metadata
      |
      v
Application database
      |
      +--> durable jobs
      |
      +--> credit ledger
      |
      +--> operation records
      |
      +--> creative state
      |
      v
Message broker
      |
      +--> text generation workers
      |
      +--> analysis workers
      |
      +--> image generation workers
      |
      +--> audio generation workers
      |
      +--> context-processing workers
      |
      v
External providers and object storage

The application core remained a modular monolith rather than being divided into many independently deployed services.

That choice was deliberate.

Ghostviber needed clear boundaries between billing, creative content, AI operations, generated assets, and user identity. It did not need network calls between every domain module.

The modular core kept product iteration fast and transactions understandable. Processes that genuinely benefited from independent execution, such as long-running generation, analysis, and media handling, were moved behind durable asynchronous workflows.

This avoided service sprawl while still isolating workloads with different latency, scaling, and failure characteristics.

A product built from different kinds of computation

One of the most important architectural decisions was not to treat every creative feature as an LLM task.

Ghostviber combines several kinds of computation:

  • deterministic domain logic;
  • heuristic search;
  • cached lookup;
  • language-model generation;
  • structured analysis;
  • retrieval of relevant user context;
  • image generation;
  • audio generation;
  • billing and entitlement checks;
  • background media processing.

Each category has different operational properties.

A rhyme search should respond quickly and consistently.

A generated cover can take longer and complete asynchronously.

A credit deduction must be deterministic and auditable.

A writing suggestion may be probabilistic but still needs structural validation.

A style analysis can be refreshed in the background and reused across later requests.

Using an LLM for every feature would have increased cost, latency, and unpredictability without necessarily improving the user experience.

The product instead uses the simplest mechanism capable of producing the required result.

The multilingual rhyme engine

Rhyme search appears simple until it needs to work well enough to remain useful inside the writing flow.

A dictionary lookup can find words with identical endings. Songwriters often need something broader. Useful results may depend on pronunciation, syllable structure, language-specific spelling rules, near-rhyme relationships, and the sound of the phrase rather than its final characters.

The same heuristic does not transfer cleanly between English, Polish, and Spanish.

Each language has its own relationship between writing and pronunciation. Inflection changes the shape of words. Diacritics matter differently. Common suffixes can create large groups of technically similar but creatively weak results.

I built a multilingual heuristic engine that combines language-specific normalization, indexed rhyme data, and ranking rules designed for interactive search.

A simplified flow looks like this:

Input word or phrase
        |
        v
Language-aware normalization
        |
        +--> orthographic form
        |
        +--> sound-oriented representation
        |
        +--> ending and syllable features
        |
        v
Candidate retrieval
        |
        v
Heuristic ranking
        |
        +--> phonetic fit
        |
        +--> lexical relationship
        |
        +--> language-specific penalties
        |
        v
Ranked rhyme results

The engine is designed for real-time use.

That requirement shaped the implementation more than theoretical linguistic completeness. The system needs to return useful candidates quickly enough that searching for a rhyme feels like part of typing rather than a separate generation workflow.

Frequently used indexes and computed structures are cached. Expensive normalization is separated from request-time ranking where possible. Language logic remains explicit rather than being hidden in one universal similarity score.

The result is not an attempt to prove that two words rhyme in every musical context.

It is a product-oriented search engine that produces useful candidates and lets the writer decide.

That distinction matters. Creative software should support judgment, not pretend to replace it.

AI writing assistance

The writing assistant operates inside the song rather than beside it.

A request may ask for:

  • the next line;
  • several alternative continuations;
  • a title;
  • an inspiration based on the current material;
  • a rewritten fragment;
  • an analysis of the text;
  • a suggestion that follows the artist’s established direction.

The prompt sent to a model is only the final step of a larger operation.

Before execution, the system needs to determine:

  • which part of the song is relevant;
  • how much surrounding text belongs in the context;
  • which user preferences or style signals should be included;
  • which task-specific instructions apply;
  • which provider and model fit the operation;
  • how much the operation costs;
  • whether the user has enough available credit;
  • whether the result must follow a particular structure;
  • how the result should be stored and presented.

After execution, the system still needs to validate the response, preserve its provenance, settle the credit operation, and expose it as a candidate without overwriting the user’s work.

The model call is one dependency inside this workflow.

It is not the workflow itself.

Shadow Analyzer

A generic model can respond to the text placed in its immediate context.

A creative partner needs a more persistent understanding of how the user writes.

Ghostviber’s Shadow Analyzer builds a separate representation of the artist’s work. It processes existing material and extracts signals that can help later operations remain closer to the user’s own direction.

Those signals may include patterns around:

  • vocabulary and phrasing;
  • line construction;
  • recurring themes;
  • structural preferences;
  • the relationship between sections;
  • rhyme and sound choices;
  • characteristics of previously accepted material.

The purpose is not to train a private foundation model for every user.

It is to create a focused context layer that can retrieve relevant signals when a generation task needs them.

A simplified lifecycle looks like this:

User edits or adds creative material
        |
        v
Change is recorded
        |
        v
Background analysis job
        |
        +--> extract style signals
        |
        +--> update structured context
        |
        +--> preserve source relationship
        |
        v
Later generation request
        |
        +--> retrieve relevant context
        |
        +--> combine with current song state
        |
        v
Task-specific model operation

The analysis happens asynchronously because it does not need to block editing.

Generation requests do not load the user’s entire writing history into every prompt. They retrieve a bounded set of relevant context that fits the current task and available model context.

This improves several properties at once:

  • prompt size remains controlled;
  • irrelevant history does not overwhelm the current song;
  • style context can evolve as the user’s work changes;
  • expensive analysis can be reused;
  • the generation path remains responsive;
  • the origin of contextual signals can remain traceable.

The Shadow Analyzer is similar in spirit to retrieval-augmented generation, but its product role is narrower and more deliberate.

The goal is not to search a general knowledge base. It is to help the system bring the right parts of the artist’s own creative identity into the current operation.

Preserving authorship

Personalization creates a product risk as well as a technical opportunity.

An AI system that produces too much content can make the writer feel like an editor of machine output. A system that imitates too aggressively can flatten the artist’s work into a statistical version of their previous choices.

Ghostviber is designed around suggestions, alternatives, and explicit user acceptance.

Generated text is stored separately from the authoritative song state. The application knows whether content was:

  • written directly by the user;
  • generated as a suggestion;
  • accepted into the song;
  • rejected or ignored;
  • produced from a fallback path;
  • created using a particular context and operation.

This separation supports version history and future product behavior without silently rewriting authorship.

It also creates cleaner failure semantics. A failed generation does not corrupt the song. A duplicated job does not need to insert the same line twice. A later model response can be discarded if the user has already changed the relevant part of the text.

The user remains the owner of the creative state.

AI produces candidates around it.

AI orchestration

Ghostviber integrates several classes of AI provider:

  • language generation;
  • text analysis;
  • image generation;
  • audio and music generation.

These providers do not share one operational contract.

They differ in latency, pricing, request limits, result formats, moderation behavior, asynchronous capabilities, and failure semantics.

A useful abstraction therefore cannot pretend that every provider is interchangeable.

The orchestration layer separates common execution concerns from capability-specific behavior.

Common concerns include:

  • operation identity;
  • provider selection;
  • retries;
  • timeouts;
  • fallbacks;
  • usage accounting;
  • result validation;
  • error normalization;
  • structured execution state;
  • observability.

Provider-specific adapters handle:

  • request formatting;
  • authentication;
  • response parsing;
  • provider job identifiers;
  • polling or callback behavior;
  • provider-specific error classification;
  • asset retrieval;
  • capability-specific metadata.

This creates a boundary similar to the one behind RelPrim.

The application defines the meaning of the operation. The orchestration layer provides reusable execution mechanics. The provider adapter translates between that model and the remote API.

Model routing

Not every task benefits from the same model.

A short continuation, a detailed song analysis, a title suggestion, and a structured extraction have different requirements.

Routing decisions can consider:

  • capability;
  • expected latency;
  • context requirements;
  • output structure;
  • provider availability;
  • cost;
  • fallback compatibility.

The purpose of routing is not to chase a theoretically best model for every request.

It is to choose an execution path that satisfies the product contract.

A low-latency writing suggestion may prefer a faster model.

A deeper analysis may justify more processing time.

A structured task may prioritize predictable formatting.

An image or audio operation may have no immediate substitute with equivalent semantics.

Routing remains task-specific because collapsing providers into one lowest common denominator would remove the capabilities that make them useful.

Validation

A provider can return a technically successful response that the product cannot use.

A language model may return empty text, an explanation instead of the requested content, malformed structured output, or a result that violates task-level constraints.

An image provider may complete a job without exposing the expected asset.

An audio operation may return metadata before the generated file is available.

Transport success is therefore not enough.

The orchestration layer validates results before accepting them as completed.

Validation depends on the operation.

A title suggestion may need non-empty text within a reasonable length.

A structured analysis may require a valid schema.

A generated asset may require a retrievable file, recognized format, and persisted metadata.

A writing continuation may need to be separated cleanly from commentary or provider-specific formatting.

Validation failures are classified separately from connection failures. Some may be retryable with adjusted instructions or another provider. Others should terminate the operation and preserve enough evidence for diagnosis.

Retries and fallbacks

Retries are limited to failures where another attempt has a reasonable chance of producing a useful result.

Temporary provider errors, short-lived network failures, or polling interruptions may be retryable.

Invalid input, exhausted account limits, unsupported operations, and deterministic validation failures generally are not.

Fallbacks are capability-aware.

Using another language model for a text suggestion may be acceptable if both providers can satisfy the same product contract.

Switching image providers may change dimensions, style behavior, moderation, or cost.

Switching audio providers after an ambiguous outcome may create duplicate billable work.

The system therefore does not treat fallback as a generic except block.

Fallback eligibility belongs to the operation definition.

When a fallback is used, the execution path remains visible internally. A successful product response should not erase evidence that the primary provider failed.

Synchronous and asynchronous operations

Ghostviber includes operations with very different completion times.

A rhyme search should finish inside the interactive request.

A short text generation may also be handled synchronously when provider latency and product expectations allow it.

Image and audio generation are better modeled as asynchronous jobs.

The API accepts the user’s intention, performs authorization and billing checks, creates a durable operation, then returns a status the interface can track.

Requested
    |
    v
Credit reserved
    |
    v
Queued
    |
    v
Dispatched
    |
    +------> Retry scheduled
    |
    +------> Failed
    |
    v
Provider processing
    |
    v
Result validation
    |
    v
Asset persistence
    |
    v
Credit settled
    |
    v
Completed

The user-facing action and the remote provider execution are not one database transaction.

The workflow needs explicit states because each boundary can fail independently.

Durable asynchronous workflows

Long-running work is delivered through a message broker and dedicated workers.

The request path does not remain open while a provider generates audio or an image. It creates a durable job that can survive application restarts and temporary provider outages.

Each job has a stable identity and lifecycle.

A worker can determine whether the operation:

  • has not started;
  • is currently executing;
  • is waiting for a provider;
  • should be retried;
  • produced a candidate result;
  • completed successfully;
  • failed definitively;
  • requires cleanup or recovery.

Consumers are designed to tolerate message redelivery.

A worker cannot assume that receiving a message means the operation has never been processed before. It checks durable state before starting an external effect and records progress at meaningful boundaries.

This makes queue retries safer and prevents duplicate execution from becoming duplicate product state.

Generated assets

Image and audio generation produce files that live longer than the provider request.

The application therefore separates the provider’s temporary result from the product asset.

A completed media workflow may include:

  1. obtaining the provider result;
  2. validating the file or remote asset;
  3. downloading or transferring it into controlled object storage;
  4. recording content type, size, ownership, and generation metadata;
  5. linking the asset to the correct creative project;
  6. making it available through an authorized delivery path;
  7. cleaning up incomplete or abandoned operations.

The database stores asset identity and lifecycle metadata.

Object storage holds the binary content.

The provider URL is not treated as permanent product storage. Provider links may expire, access rules may change, and the application needs control over retention and delivery.

This boundary also helps with backups and provider portability.

The creative project references a Ghostviber asset, not an implementation detail of the service that generated it.

Version history

Creative work changes continuously.

A writer may explore several versions of a verse, accept one generated line, change it manually, then return to an earlier version.

The system preserves versions without turning every keystroke into an expensive full copy of the workspace.

The important product state includes more than the latest text:

  • what the song contained at a meaningful checkpoint;
  • which section changed;
  • which generated suggestion influenced the change;
  • which derived analyses are now stale;
  • whether a media asset belongs to the current direction or an earlier one.

Version history protects user trust. Experimenting with AI becomes safer when the writer knows that the previous state is recoverable.

It also provides a better foundation for the Shadow Analyzer because changes can be interpreted over time rather than treating the latest snapshot as the only evidence of style.

Credits as a product boundary

AI operations create variable external cost.

A subscription alone does not describe that cost well. A user may perform many low-cost text operations or a smaller number of expensive image and audio generations.

Ghostviber uses credits to create a consistent product boundary around those different provider economics.

The user sees one understandable balance.

Internally, each operation has a defined credit cost connected to its expected resource use and product value.

The difficult part is not displaying the number.

The difficult part is preserving financial correctness while the AI workflow is distributed and asynchronous.

The credit ledger

The credit system is built around a ledger rather than an unstructured mutable counter.

Balance changes are represented as durable entries associated with a reason and an operation.

Entries may represent:

  • subscription credit allocation;
  • purchased credit allocation;
  • reservation for an AI operation;
  • settlement of completed usage;
  • release after a failed operation;
  • administrative correction;
  • expiration or another explicit business event.

The ledger gives the system an audit trail.

When the displayed balance is questioned, the answer does not depend on reconstructing mutations from application logs. The balance can be explained through recorded credit events.

This also makes retries and webhook processing safer. The system can identify whether a particular subscription event or AI operation has already affected the balance.

Reserving credits before execution

An expensive operation should not begin unless the user has enough available credit.

Checking the balance and deducting later is not sufficient.

Two concurrent requests could both observe the same balance and start work that the account cannot afford.

Ghostviber uses a reservation step before dispatching billable execution.

A simplified flow is:

User requests generation
        |
        v
Validate entitlement and operation cost
        |
        v
Lock relevant billing state
        |
        v
Check available credit
        |
        v
Create reservation and durable operation
        |
        v
Release lock
        |
        v
Dispatch external work

The lock protects the decision boundary where available credit becomes reserved credit.

The reservation connects billing state to one logical operation.

When the operation succeeds, the reserved amount is settled.

When it fails before producing a usable result, the reservation can be released according to the operation’s billing policy.

The system does not need to guess which credit deduction belongs to which provider request. They share the same operation identity.

Available, reserved, and consumed credit

A single displayed balance can hide several states.

Conceptually, the billing system distinguishes:

granted credit
    |
    +-- available
    |
    +-- reserved by active operations
    |
    +-- consumed by completed operations

A reserved credit is no longer available for another request, but it has not yet become final usage.

That distinction is necessary for asynchronous operations.

Without it, the product would have to choose between charging before knowing the outcome or allowing unlimited concurrent work against one balance.

Reservation creates a bounded intermediate state.

Like any intermediate state, it needs recovery.

A worker crash cannot leave credit reserved forever. A retry cannot create a second reservation for the same operation. A delayed provider result must still find the correct billing record.

The operation lifecycle and ledger lifecycle therefore evolve together.

Subscription billing

Subscriptions are managed through an external billing provider.

This creates another unreliable boundary.

Webhook notifications can be delayed, duplicated, or delivered out of order. An API response can be lost. A subscription may transition through several states while the product is temporarily unable to process the event.

Webhook handlers are idempotent.

A provider event is identified and recorded before its business effect is applied. Repeated delivery does not allocate the same credits several times.

Subscription state and credit allocation are related but not collapsed into one mutation. The system records the commercial event, updates entitlement, and creates any corresponding ledger entry through an explicit workflow.

This makes reconciliation possible when the billing provider and application temporarily disagree.

Locks and concurrency

Billing correctness depends on controlling concurrent access to shared state.

Locks are used narrowly around decisions that must observe and update a consistent view, such as reserving credits from one balance.

The external AI call never happens while the billing lock is held.

Holding a lock across network I/O would create long contention windows and tie financial correctness to provider latency.

Instead, the system performs the local atomic decision first:

validate
lock
reserve
commit
unlock
dispatch

The external operation then proceeds against the durable reservation.

This reduces contention and creates a recoverable boundary between local financial state and remote execution.

Idempotency across the workflow

A single user action can cross several delivery mechanisms:

HTTP request
    |
    v
Application transaction
    |
    v
Queue message
    |
    v
Worker execution
    |
    v
Provider request
    |
    v
Result persistence
    |
    v
Credit settlement

Any boundary may repeat.

The API request may be submitted twice.

The queue may redeliver.

The worker may retry.

The provider may accept an idempotency key.

The completion step may run again after a crash.

A stable logical operation identity connects these boundaries.

Each stage asks whether its effect has already happened before performing it again.

Idempotency is not implemented as one global duplicate check. It protects specific effects:

  • creating the durable operation;
  • reserving credits;
  • dispatching a provider job where supported;
  • attaching the generated result;
  • settling the ledger;
  • processing billing webhooks.

This is what makes retries compatible with billing and generated assets.

Ambiguous outcomes

The hardest failures are operations whose final outcome is not known.

A provider may accept an image or audio job, create billable work, and fail to return the expected acknowledgement.

The application cannot safely mark the operation as definitively failed.

Starting a new job may create duplicate provider cost.

Releasing the credit reservation may allow the user to spend credit that already funded remote work.

Keeping the operation in progress forever is also unacceptable.

The system preserves ambiguity as an explicit state.

Recovery may use:

  • a provider job identifier;
  • an idempotency key;
  • polling;
  • provider history;
  • a callback;
  • a later reconciliation process;
  • bounded operational review.

The exact path depends on provider capability.

The important decision is not to convert uncertainty into failure merely because failure is easier to represent.

Caching

Caching is used where it improves product latency without hiding correctness requirements.

Suitable data includes:

  • rhyme indexes;
  • normalized language data;
  • frequently requested reference content;
  • derived style context;
  • short-lived provider or configuration metadata;
  • results whose freshness contract is explicit.

Not every AI result should be cached solely because the input appears similar.

Two requests may include different song context, model configuration, style signals, or product intent.

Cache keys therefore need to represent the actual operation contract rather than only the visible prompt.

Derived data also has an invalidation relationship with creative state.

A song analysis can become stale after the text changes. Style context can require background refresh. A cached rhyme index may remain stable across many requests.

The system treats those as separate freshness models instead of applying one generic cache lifetime.

Observability

Ghostviber needs to explain operations that cross the API, queue, worker, provider, storage, and billing system.

One HTTP request log is not enough.

A logical operation carries correlation across the full path.

Useful operational signals include:

  • operation type and identity;
  • provider and model selection;
  • queue age;
  • execution latency;
  • retry attempts;
  • fallback use;
  • validation failures;
  • provider error classification;
  • generated asset persistence;
  • credit reservation age;
  • settlement and release outcomes;
  • lock contention;
  • cache behavior;
  • operations left in uncertain or terminal states.

The goal is not to collect every possible metric.

It is to make the important states visible before a user report becomes the first indication that a workflow is stuck.

User-facing status is part of observability as well.

An asynchronous operation can be queued, processing, completed, or failed. The interface should not show a generic spinner while the backend has already reached a terminal failure.

Reliability scenarios

The design can be understood through the failures it is expected to handle.

A text provider fails temporarily

The operation classifies the failure, retries within a bounded policy, and may use a compatible fallback when the product contract allows it.

The final execution path remains observable.

A provider returns an empty response

The transport succeeded, but validation rejects the result.

The operation may retry when that failure is classified as recoverable. It does not store empty text as a successful creative suggestion.

A queue message is delivered twice

The worker loads the durable operation and checks its state.

It continues the same logical operation or exits without creating another reservation, provider job, or generated asset.

An image job succeeds but asset persistence fails

The provider result remains associated with the operation.

The storage step can retry independently without requesting a second generation.

The worker crashes after reserving credits

The durable operation and reservation remain available for recovery.

Another worker can continue the same operation. A cleanup policy can release reservations that reach a terminal state without billable output.

The worker crashes after completing the provider call

The next execution checks existing provider and operation state before dispatching again.

It tries to persist and complete the existing result rather than repeating the external effect.

Two requests compete for the same remaining balance

The reservation transaction and lock serialize the financial decision.

Only work covered by available credit is admitted.

A billing webhook arrives twice

The provider event identity prevents duplicate entitlement or credit allocation.

A style analysis is stale

The product can continue using the last valid context where appropriate while a new analysis is created asynchronously.

The current song remains available for editing throughout.

Important engineering decisions

Keep the modular core cohesive

The domains need boundaries, but they also participate in coordinated transactions and evolve quickly as one product.

A modular monolith keeps those interactions visible without introducing distributed infrastructure where it adds no product value.

Move long work out of the request path

Image generation, audio generation, and deeper analysis should not depend on one browser connection remaining open.

Durable jobs make latency and provider failure manageable.

Use deterministic systems where they fit

The rhyme engine does not need a model to generate every search result.

Heuristics, indexes, and caching produce faster and more predictable behavior for a narrow, well-defined problem.

Treat AI as an unreliable external dependency

Model output is validated.

Provider failures are classified.

Retries and fallbacks are bounded.

Execution state is preserved.

A model is not considered reliable merely because its SDK returned without raising an exception.

Reserve economic capacity before spending it

Credit checks, reservations, and locks happen before billable work is dispatched.

The system does not begin external work based on an optimistic balance read.

Keep billing and execution connected

Credit entries reference the operation that caused them.

A provider retry cannot accidentally become a second independent charge.

Preserve the user’s authoritative state

AI results remain candidates until the user accepts them.

Background analysis and generation do not silently overwrite the song.

Keep provider details behind capability boundaries

The application works with product operations such as generating a continuation or creating cover art.

Provider adapters handle remote API details without pretending that all providers have identical semantics.

Make intermediate states explicit

Queued, reserved, dispatched, processing, completed, failed, and uncertain are real product and operational states.

Collapsing them into one boolean would remove the information needed for recovery.

Trade-offs

Modular monolith versus independent services

A service per domain could create stronger deployment isolation.

It would also add network boundaries, distributed transactions, schema coordination, and operational overhead before the product needed them.

The chosen architecture keeps the core cohesive while allowing workers and provider-heavy workloads to scale independently.

Immediate responses versus durable execution

Asynchronous jobs require status handling in the interface.

That is more work than keeping one request open.

It is also more honest and recoverable for operations whose duration is outside the application’s control.

Provider abstraction versus provider capability

A single universal provider interface would simplify some code.

It could also erase useful capabilities or imply that fallbacks are always equivalent.

The architecture shares common execution mechanics while allowing adapters to preserve provider-specific behavior.

Personalization versus context size

More user history can provide more style information.

It can also increase cost, latency, irrelevance, and the risk that old material overwhelms the current creative direction.

The Shadow Analyzer uses bounded, relevant context instead of treating all past work as mandatory prompt input.

Credit precision versus product simplicity

The user should see an understandable balance.

The backend needs a richer model with grants, reservations, settlements, and releases.

That internal complexity is necessary to keep the public model simple without making it financially incorrect.

Cached analysis versus freshness

Reusing analysis improves latency and cost.

It introduces a period where derived context may trail the latest edit.

The system makes freshness explicit and refreshes derived state asynchronously instead of blocking every creative action.

Reliability versus unnecessary machinery

Not every operation needs a queue, fallback, circuit breaker, reservation, and reconciliation process.

Adding every reliability mechanism everywhere would make the platform slower and harder to understand.

Policies are matched to the consequences of the operation.

What I would avoid

Treating the product as a collection of prompts

Prompt quality matters, but it does not solve billing, asynchronous execution, versioning, personalization, generated assets, or recovery.

Using one generic AI client for every capability

Text, image, audio, and structured analysis have different contracts.

A lowest-common-denominator abstraction hides decisions the application needs to make.

Deducting credits after execution without reservation

Concurrent requests could spend beyond the available balance.

Failures between generation and deduction would create further inconsistencies.

Holding billing locks during provider calls

External latency should not extend a critical financial section.

Reserve locally, commit, release the lock, then execute remotely.

Treating a timeout as proof of failure

The provider may have accepted the operation.

The result needs reconciliation before expensive side effects are repeated or credits are released.

Letting generated output overwrite user work

AI suggestions should remain separate until explicitly accepted.

Running long generation inside the API request

This couples remote provider latency to browser and server connection lifetime.

Reaching for an LLM where indexed search is better

A rhyme search requires interactive latency, predictable ranking, and language-specific behavior.

A deterministic engine is a better foundation for that task.

Reconstructing financial state from logs

Credits need a ledger.

Logs can support diagnosis, but they should not be the source of truth for a balance.

Hiding fallback use

A usable result does not mean the primary provider is healthy.

Fallback execution should remain visible to operations and future product decisions.

What building Ghostviber taught me

The first lesson is that an AI product is still a distributed system.

The model may be the most visible dependency, but product reliability depends on everything around it: operation identity, queues, provider behavior, validation, storage, billing, context assembly, caching, and recovery.

The second lesson is that variable-cost infrastructure turns reliability into a financial concern.

A duplicate job is not only wasted computation. It may spend provider budget and user credit. A timeout may leave both the technical and economic outcome unknown. Billing cannot be designed after the AI workflow. It is part of the workflow.

The third lesson is that personalization needs its own system.

Adding more text to a prompt is not a durable memory architecture. Relevant context has to be analyzed, stored, refreshed, retrieved, bounded, and connected to the current task.

The fourth lesson is that deterministic engineering still matters inside AI products.

The multilingual rhyme engine provides value precisely because it is fast, purpose-built, and understandable. Not every intelligent feature needs a generative model.

The fifth lesson is that the best abstractions preserve differences.

Providers share execution concerns but not identical capabilities. Creative outputs share a workspace but not identical lifecycles. Credit operations share a ledger but not identical business reasons.

Forcing them into one generic model would produce simpler diagrams and weaker behavior.

Ghostviber is ultimately one product experience built on several systems with different meanings of success.

A rhyme result succeeds when it is useful and immediate.

A writing suggestion succeeds when it respects the current creative context.

A generated asset succeeds when the provider result is validated, stored, and connected to the project.

A credit operation succeeds when the ledger remains explainable under concurrency and failure.

An AI workflow succeeds when the user receives a coherent outcome without having to understand any of those boundaries.

That coherence is the real system.

  • Reliability InfrastructureOpen sourceActive

    RelPrim

    Creator & Maintainer

    RelPrim is an open-source Python reliability layer for operations that cross process, network, or provider boundaries. It brings retries, timeouts, fallbacks, circuit breakers, validation, and execution reporting into one explicit operation model without hiding the domain decisions that make recovery safe.

    • Python
    • Reliability policies
    • Structured execution
  • Payments & IntegrationsPast Work

    Making Payment Workflows Reliable

    Payment webhooks can arrive late, more than once, or after an ambiguous timeout. This case study explores how durable ingestion, idempotent processing, and explicit recovery paths protect financial correctness at the boundary with external providers.

    • Idempotency
    • Webhooks
    • Async processing

Let's talk systems

Building a system that has to remain predictable under pressure?

I help teams shape the systems their products depend on, especially early on or when reliability becomes critical. I also write and speak about these problems. Tell me what you’re building and where it hurts.